do this · Entire
Log in
can you get yourself an overview over the repo, and then the implemented code, and then kick of specific agents reviewing everything. One of them should look at "what if we would write this from scratch, what would we do differently"
3mo ago·7.8s·27 calls
I’m getting a top-level view of the repository first, then I’ll read the main implementation paths so the review delegation is grounded in the actual code rather than file names. After that I’ll spin up focused agents, including one tasked with a from-scratch redesign critique.
The repo is compact: a single CLI entrypoint and one substantive package under internal/syncer, with tests doing a lot of the behavioral specification. I’m reading the core docs and implementation now so I can split review ownership along real architectural seams.
I’ve confirmed the shape: the bulk of the system lives in one large syncer.go file plus the protocol-v2 helper, with docs and tests describing the intended relay/batching behavior. Next I’m mapping the main execution paths and the test surface, then I’ll delegate review agents against those slices.
I have enough context to split the review cleanly. The codebase is effectively four concerns: CLI surface in cmd/git-sync, the main orchestration/planning/push path in internal/syncer/syncer.go, protocol-v2 and wire helpers in internal/syncer/protocol_v2.go, and a fairly strong integration-heavy test suite.
Review split I’m kicking off:
- Core execution and correctness: planning, relay selection, bootstrap, batching, push/fallback behavior.
- Protocol and transport internals: pkt-line handling, v1/v2 negotiation, stream safety, HTTP/auth behavior.
- Test strategy and gaps: what behavior is actually locked down, and what regressions still look plausible.
- From-scratch redesign: if we rebuilt this today, what architecture and interfaces would we choose differently.
The review agents are running now. While they work, I’m doing my own pass on the highest-risk paths so I can synthesize their findings instead of just relaying them.
The main implementation pass confirms the current architecture is deliberately optimized around streamed relay paths and fallback logic, but most of that behavior is still concentrated in a single orchestration file. I’m waiting on the delegated reviewers now and will merge their findings into one high-signal summary.
<subagent_notification>
{"agent_path":"019d7d09-b4a2-74f0-b87f-791514eb33b8","status":{"completed":"Findings\n\n1. High: destructive ref-selection and prune semantics are only lightly specified by tests, despite living in the most dangerous code paths. buildDesiredRefs and buildPlans decide what is managed, mirrored, or deleted across branches, mappings, tags, and --prune ( internal/syncer/syncer.go:1651, internal/syncer/syncer.go:1750). The suite covers one broad prune case for tags plus force divergence ( internal/syncer/integration_test.go:696), but I did not find focused tests for: mixed --branch selection with --prune, mapping collisions, duplicate mappings, pruning with explicit mappings, or exact-ref mappings involving tags. A regression here is not “just wrong output”; it can delete or retarget remote refs.\n\n2. High: bootstrap batching has real implementation complexity but mostly happy-path coverage. The critical planning/execution path spans checkpoint planning, sampling heuristics, resume behavior, temp-ref cutover, and tag-after-branches ordering ( internal/syncer/syncer.go:1144, internal/syncer/syncer.go:1398, internal/syncer/syncer.go:1475). Tests do cover end-to-end batching, resume, and tags with git-http-backend ( internal/syncer/git_http_backend_test.go:406, internal/syncer/git_http_backend_test.go:471, internal/syncer/git_http_backend_test.go:563), but the heuristic itself only has tiny helper-level tests for candidate sampling and probe selection ( internal/syncer/syncer_test.go:118, internal/syncer/syncer_test.go:134). Missing are failure-path tests for mismatched temp refs, “no checkpoint fits”, multi-branch batching interactions, and partial cutover failures. That is a lot of untested state-transition logic.\n\n3. Medium: protocol-v2 parsing and wire-format error handling are under-specified by tests. The parser and request builders are hand-rolled ( internal/syncer/protocol_v2.go:75, internal/syncer/protocol_v2.go:132, internal/syncer/protocol_v2.go:179). Tests cover special packets, one capability advertisement, one request encoding, and include-tag insertion ( internal/syncer/protocol_v2_test.go:10, internal/syncer/protocol_v2_test.go:46, internal/syncer/protocol_v2_test.go:69, internal/syncer/protocol_v2_test.go:91). I did not find tests for malformed pkt-lines, unexpected delimiters/response-end placement, truncated payloads, unsupported v2 capability ads, or stream decoding failures. Given the amount of custom wire handling, this is a meaningful gap.\n\n4. Medium: the CLI surface is barely tested relative to its branching behavior. cmd/git-sync/main.go owns flag parsing, env defaults, positional-vs-flag URL resolution, and command dispatch ( cmd/git-sync/main.go:23, cmd/git-sync/main.go:46, cmd/git-sync/main.go:120). Tests only check JSON marshaling and that plan --json does not push ( cmd/git-sync/main_test.go:34, cmd/git-sync/main_test.go:87). There is no direct coverage for invalid mappings, unsupported protocol values, bootstrap rejecting unsupported flags, positional argument precedence, or usage/help behavior. For a CLI tool, that is thin.\n\n5. Medium: auth coverage is useful but narrow. The tests hit explicit token precedence, SkipTLSVerify, stored Entire token lookup, and refresh flow ( internal/syncer/auth_test.go:16, internal/syncer/auth_test.go:48, internal/syncer/auth_test.go:70, internal/syncer/auth_test.go:110), plus one integration test for git credential helper fallback ( internal/syncer/integration_test.go:482). Missing are bearer-token precedence, malformed helper output, refresh failures, keyring/file-store fallback permutations, and source-vs-target auth asymmetry. The risk is mostly operational rather than algorithmic, but it is still a surface area with a lot of conditionals.\n\nWhat the current tests give confidence in\n\n- The core product path is meaningfully exercised end to end: empty-target bootstrap relay, normal incremental sync, protocol-v2 fetch/probe/fetch-haves, branch mapping, dry-run behavior, tag creation, force/prune, and stats all have integration coverage ( internal/syncer/integration_test.go:36, internal/syncer/integration_test.go:338, internal/syncer/integration_test.go:404, internal/syncer/integration_test.go:530, internal/syncer/integration_test.go:696).\n\n- Compatibility with a real Git server stack is better than average for a small repo. The git-http-backend tests validate bootstrap, incremental relay, multi-branch fast-forward, mapped-branch fast-forward, tag create relay, batched bootstrap, resume, and batched tags against actual Git CGI behavior ( internal/syncer/git_http_backend_test.go:23, internal/syncer/git_http_backend_test.go:155, internal/syncer/git_http_backend_test.go:227, internal/syncer/git_http_backend_test.go:298, internal/syncer/git_http_backend_test.go:406).\n\n- The suite is strongest where it checks externally visible behavior and weakest where it relies on complex internal heuristics and policy decisions. That means I’d trust the happy-path transport behavior more than I’d trust edge-case ref management or batching planner correctness."}}
</subagent_notification>
3mo ago·2.5s·1 call
One review has landed and it lines up with the shape of the code: strong end-to-end confidence on happy paths, much weaker specification around destructive ref policy and batching edge cases. I’m waiting for the remaining reviewers so I can give you one consolidated read rather than four disconnected reports.
<subagent_notification>
{"agent_path":"019d7d09-b4c5-7fa2-b5dc-e35b0979d316","status":{"completed":"Repo overview is in place. This is a small Go CLI with one thin entrypoint in cmd/git-sync/main.go and almost all implementation in internal/syncer/syncer.go plus protocol-v2 wire handling in internal/syncer/protocol_v2.go. The product shape is clear: remote-to-remote smart-HTTP mirroring, with normal sync, empty-target bootstrap relay, incremental relay, and a batched bootstrap fallback for very large repos. Docs in README.md, docs/bootstrap.md, and docs/bootstrap-batching.md match the implemented code reasonably well.\n\nImplemented-code overview: main.go is mostly flag plumbing and output formatting; the real behavior sits in syncer.Run, syncer.Bootstrap, syncer.Probe, and syncer.Fetch. The biggest implementation concentration is that syncer.go mixes planning, relay selection, batching, push mechanics, transport/auth, stats, and credential refresh in one file, which is the main architectural pressure point. The test surface is stronger than the repo size suggests: unit tests, in-process smart-HTTP integration, optional git-http-backend tests, and some live/smoke paths. go test ./... passed locally.\n\nI kicked off four focused review agents:\n1. Core execution/correctness: planning, bootstrap, incremental relay, batching, push/fallback, CLI-to-core config.\n2. Protocol/transport internals: pkt-line parsing, v1/v2 negotiation, stream handling, HTTP/auth/error paths.\n3. Test strategy/gaps: what behavior is actually locked down and where regressions could still hide.\n4. From-scratch redesign: what we would keep, what we would redesign, and how to migrate there.\n\nMy own immediate read is that the core idea is sound, and the main risk is not “wrong product direction” but “too many concerns collapsed into one package,” especially around batching and auth/transport interactions. If you want, next I can turn this into a consolidated review document with prioritized findings and the from-scratch redesign critique."}}
</subagent_notification>
3mo ago·4.6s·2 calls
I’ve got the structural picture and two of the review threads back. I’m running the local test suite once myself so the final summary includes the current verification state, not just static review.
<subagent_notification>
{"agent_path":"019d7d09-b47c-70f1-abb2-e81b78135584","status":{"completed":" Repo Overview\ngit-sync is a small Go repo with one CLI entrypoint in cmd/git-sync/main.go and almost all implementation in internal/syncer/syncer.go plus internal/syncer/protocol_v2.go. The product shape is clear: discover source/target refs over smart HTTP, build a ref plan, then choose one of three execution strategies: bootstrap relay, incremental relay, or local materialize-and-push. The tests are substantial and mostly integration-driven, which is appropriate for this kind of protocol-heavy tool.\n\nI couldn’t literally fan out to child review agents because that orchestration tool is not exposed in this session, so I split the work into agent-style review tracks and ran them directly. I also ran go test ./...; the suite passes.\n\n Findings\n1. Protocol-v2 tag fetches can request include-tag without checking whether the server advertised support. In internal/syncer/protocol_v2.go, sourceFetchRequestV2 unconditionally appends include-tag when any desired ref is a tag ( lines 654-658). The v1 path correctly gates include-tag on capability support, but the v2 path does not. On stricter servers this can turn a valid tag sync into a protocol error.\n\n2. Mapping collisions are silently accepted and last-write-wins. In internal/syncer/syncer.go, buildDesiredRefs writes directly into desired[targetRef] and managed[targetRef] with no duplicate detection ( lines 1655-1668). For a mirroring tool, allowing --map main:stable --map release:stable to silently collapse into one target ref is risky and hard to diagnose.\n\n3. Expired EntireDB tokens can be reused silently after refresh failure. In internal/syncer/syncer.go, getEntireDBTokenWithRefresh falls back to returning the stale token when refresh fails ( lines 2787-2790). That turns a token-refresh problem into a later auth failure against the Git remote, which obscures the real cause and can create flaky behavior around expiry boundaries.\n\n4. Core execution logic is carrying too much unrelated responsibility in one file. internal/syncer/syncer.go mixes planning, strategy selection, bootstrap batching, pack push mechanics, auth resolution, token refresh, stats, and measurement. That is not a current correctness bug, but it is the main maintenance risk in the repo: too many changes will cross-cut a single file and make regressions harder to isolate.\n\n5. Batched bootstrap is reasonably covered, but the restart/cutover edge cases are still under-specified. The critical path is internal/syncer/syncer.go, especially the resume and finalization logic around temp refs ( lines 1215-1303). I did not see tests that exercise: temp ref at an unexpected hash, temp ref already at final tip but real ref missing, or partial failure between final create and temp-ref delete. Those are the places most likely to bite operators.\n\n What The Code Gets Right\nThe strategy selection is coherent: Run decides between bootstrap relay, incremental relay, and local-object push in a way that matches the docs. The protocol surface is intentionally narrow. Integration coverage is better than average for transport code, especially the in-process smart HTTP tests and the optional git-http-backend tests in internal/syncer/git_http_backend_test.go. The docs also align unusually well with the implementation.\n\n If Rewriting From Scratch\nI would keep the product boundaries and the relay-first mindset. I would redesign the code around explicit modules: discovery, planning, strategy, transport, auth, and reporting, with a small SyncEngine coordinating them. The current Run/Bootstrap functions are doing too much implicit strategy work; I’d instead produce a typed execution plan first, then execute a named strategy. I’d also isolate protocol-v1/v2 behind a shared source interface so batching, fetch-pack, and ls-refs logic are not spread across orchestration code.\n\nThe migration path would be incremental, not a rewrite: first extract auth and HTTP transport, then move planning into its own package, then turn bootstrap/incremental/local push into separate strategy executors. That would reduce accidental complexity without changing the CLI surface.\n\nIf you want, I can turn this into a concrete review memo with severity labels and a prioritized refactor plan, or I can start fixing the top two issues directly."}}
</subagent_notification>
3mo ago·7.6s·1 call
The protocol-focused review is back as well. It surfaced one concrete correctness issue I hadn’t called out yet: protocol-v2 tag fetches appear to request include-tag without checking server support, unlike the v1 path. I’m waiting on the final core-execution reviewer, then I’ll consolidate everything into one repo overview plus review summary.
<subagent_notification>
{"agent_path":"019d7d09-b449-7180-ae04-3b7cfb51b201","status":{"completed":" Findings\n\n1. Batched bootstrap can claim tag refs were pushed even when it never creates them. In internal/syncer/syncer.go, tag creation after branch batches is skipped entirely when FetchPack returns git.NoErrAlreadyUpToDate. That is wrong for lightweight tags, or any tag whose objects are already reachable from the just-pushed branches: no pack is needed, but the ref still must be created with a command-only push. The function then still reports success via result.Pushed = len(plans) at internal/syncer/syncer.go. There does not appear to be a test covering “tag object already present, ref absent”.\n\n2. Duplicate target mappings are silently accepted and overwrite each other. buildDesiredRefs writes desired[targetRef] = ... with no collision check at internal/syncer/syncer.go, so --map main:stable --map release:stable will just keep the last one. That is a correctness issue, not just UX: the effective sync set differs from the user’s request with no error, and prune/planning then operate on the wrong managed target set.\n\n3. Mapping normalization accepts inconsistent ref kinds and partially-qualified refs, then fails later in confusing ways. In internal/syncer/syncer.go, if either side starts with refs/, both sides are treated literally and the kind is inferred only from the target ref. That allows bad combinations like refs/tags/v1:refs/heads/stable or main:refs/heads/stable; the first can explode later when branch planning tries to load a tag object as a commit, and the second produces a misleading “source ref not found” instead of normalizing main to refs/heads/main. This should be rejected during CLI/config validation.\n\n4. Protocol v2 fetch requests always send include-tag for tag syncs without checking whether the server advertised support. See internal/syncer/protocol_v2.go. That differs from the v1 path, which gates on capability support, and it is reinforced by the current test at internal/syncer/protocol_v2_test.go. On a v2 server that supports fetch but not include-tag, tag sync can fail unnecessarily.\n\n Overview\n\nThe repo is small and coherent: cmd/git-sync/main.go is a thin CLI over one main implementation package, internal/syncer. The core design is remote-to-remote smart-HTTP sync using go-git with an in-memory object store, plus two relay optimizations: empty-target bootstrap relay and a narrower incremental relay path. Most of the behavioral surface is concentrated in internal/syncer/syncer.go, with protocol-v2 specifics in internal/syncer/protocol_v2.go.\n\nThe test suite is stronger than the code size suggests: unit tests cover helpers, integration tests cover in-process smart HTTP flows, and git-http-backend tests exercise more realistic end-to-end behavior. go test ./... passes locally.\n\n From Scratch\n\nIf I were rebuilding this, I would split syncer.go into explicit layers: config/validation, planner, source transport, target transport, relay executor, and materialized push executor. Right now too much policy and wire behavior sits in one file, which makes edge cases like batching, tag handling, and mapping validation easy to miss.\n\nI would also make batching resumability explicit instead of inferred from temp refs alone: store checkpoint metadata, validate provenance before resuming, and keep tag creation as a separate post-branch ref-update phase that works even with an empty pack. The other structural change would be to treat capability negotiation as a first-class typed object, so unsupported combinations like v2-without-include-tag are handled centrally instead of ad hoc in request builders.\n\nResidual risk is mostly around capability variation and restart semantics: the happy paths are well tested, but mixed mapping inputs, duplicate config, and odd server capability combinations are not locked down yet. The sub-agent launcher is not available in this session, so I ran the requested review as separate focused passes rather than literal spawned agents."}}
</subagent_notification>
3mo ago·19.3s
Repo overview: this is a small Go CLI centered on remote-to-remote smart-HTTP Git mirroring. The surface area is cmd/git-sync/main.go for flag parsing and output, and almost all real behavior lives in internal/syncer/syncer.go plus protocol-v2 handling in internal/syncer/protocol_v2.go. The main execution modes are normal sync, empty-target bootstrap relay, incremental relay, plus batched bootstrap for large initial migrations. The docs in README.md, docs/bootstrap.md, and docs/bootstrap-batching.md are broadly aligned with the implementation. go test ./... passes.
I reviewed the implementation myself and kicked off focused review passes for:
- Core execution and correctness.
- Protocol/transport internals.
- Test coverage and risk gaps.
- “If we rewrote this from scratch, what would we do differently?”
Highest-signal findings:
- Batched bootstrap can report tag refs as pushed even when no tag ref was created, if the tag objects are already present and
FetchPackreturnsgit.NoErrAlreadyUpToDatein the tag phase. See internal/syncer/syncer.go. - Duplicate target mappings are silently accepted with last-write-wins semantics, which is dangerous for a mirroring tool. See internal/syncer/syncer.go.
- Mapping normalization allows inconsistent ref kinds and partially qualified refs, then fails later in confusing ways instead of rejecting them up front. See internal/syncer/syncer.go.
- Protocol-v2 tag fetches appear to request
include-tagwithout checking server support, unlike the v1 path. See internal/syncer/protocol_v2.go. - The biggest architectural risk is concentration: internal/syncer/syncer.go mixes planning, strategy selection, batching, push mechanics, auth, token refresh, stats, and measurement in one file.
- Tests are good on happy-path end-to-end behavior, especially integration and
git-http-backend, but weaker around destructive ref policy, duplicate mappings, batching failure/restart edges, and malformed protocol-v2 input.
From-scratch critique: I would keep the product boundary and the relay-first strategy, but split the code into explicit layers: config/validation, planner, source transport, target transport, strategy executors, and reporting. I would also make batching resume state explicit instead of inferring only from temp refs, and centralize capability negotiation so v1/v2 feature checks are enforced consistently.
If you want, I can turn this into a tighter review memo with severity/priorities, or start fixing the concrete issues above.
put it in a review memo
3mo ago·31.0s
Review Memo
Scope
I reviewed the repository structure, read the implementation and docs, ran go test ./..., and split the review across four focused tracks:
- Core execution and correctness.
- Protocol and transport internals.
- Test coverage and risk gaps.
- From-scratch redesign.
Current verification state: go test ./... passes.
Repo Shape This is a compact Go CLI. The entrypoint in cmd/git-sync/main.go is thin; nearly all behavior lives in internal/syncer/syncer.go, with protocol-v2 handling in internal/syncer/protocol_v2.go. The product model is coherent: discover refs, build a plan, then choose one of three execution paths:
- Bootstrap relay for empty targets.
- Incremental relay for a narrow safe subset.
- Local materialize-and-push for everything else.
The docs in README.md, docs/bootstrap.md, and docs/bootstrap-batching.md match the code reasonably well.
Findings
High: batched bootstrap can claim tag refs were pushed even when they were never created. In internal/syncer/syncer.go, the post-branch tag phase skips work when
FetchPackreturnsgit.NoErrAlreadyUpToDate. That is incorrect when the tag object is already reachable from pushed branch history but the tag ref itself does not yet exist. The code then still reports success throughresult.Pushed = len(plans)at internal/syncer/syncer.go.High: duplicate target mappings are silently accepted.
buildDesiredRefsoverwritesdesired[targetRef]with no collision detection in internal/syncer/syncer.go. A request like--map main:stable --map release:stabledegrades into last-write-wins behavior with no error, which is dangerous for planning and prune semantics.High: mapping validation is too permissive and fails too late. In internal/syncer/syncer.go, mixed or partially-qualified mappings can survive normalization and only fail later in confusing ways. Examples: tag-to-branch mappings or
main:refs/heads/stable. These should be rejected at config validation time.Medium: protocol-v2 tag fetches appear to request
include-tagwithout checking whether the server advertised support. The v1 path gates this capability, but the v2 request builder does not. See internal/syncer/protocol_v2.go. On stricter servers, valid tag syncs can fail unnecessarily.Medium: expired EntireDB tokens can be reused after refresh failure, which obscures the real failure mode. In internal/syncer/syncer.go,
getEntireDBTokenWithRefreshreturns the stale token when refresh fails instead of surfacing a clearer auth-refresh problem.Medium: the main maintenance risk is architectural concentration. internal/syncer/syncer.go mixes planning, strategy selection, batching, transport, push mechanics, auth, token refresh, stats, and measurement. That is workable now, but it raises the cost of safe changes and makes bugs easier to introduce at boundaries.
Test Assessment
The suite is stronger than the repo size suggests. Integration coverage is good, and the optional git-http-backend tests validate more realistic end-to-end behavior. Happy paths are reasonably well covered:
- Empty-target bootstrap relay.
- Incremental relay.
- Branch mapping.
- Tag creation.
- Force/prune behavior.
- Protocol-v2 fetch/probe.
- Batching and resume.
The weakest areas are:
- Destructive ref policy and prune scope.
- Duplicate or conflicting mappings.
- Batched-bootstrap failure and cutover edges.
- Protocol-v2 malformed input and capability variation.
- CLI validation behavior.
The current suite gives more confidence in transport behavior than in edge-case policy correctness.
What The Code Gets Right
The product boundary is good. The relay-first approach is practical and matches the problem. Strategy selection in Run is conceptually sound. The docs are unusually aligned with the implementation, which is valuable for a tool with protocol-specific behavior. The test strategy is appropriately integration-heavy.
From-Scratch View If rebuilding this today, I would keep the same product goal and probably the same three execution modes. I would change the structure:
config/validationplannersource transporttarget transportstrategy/bootstrapstrategy/incrementalstrategy/materializedauthreporting
I would also make capability negotiation and batching resume state explicit types rather than scattered logic. The current design’s main accidental complexity comes from combining protocol, planning, execution, and auth concerns in one package and mostly one file.
Recommended Next Steps
- Fix the batched tag-create bug by issuing command-only ref creation when no tag pack is needed.
- Reject duplicate target mappings and inconsistent mapping kinds during validation.
- Gate protocol-v2
include-tagon advertised support. - Add tests for duplicate mappings, mixed ref-kind mappings, batched tag creation when objects already exist, and batch resume/cutover edge cases.
- Start extracting validation and strategy execution into separate modules before doing larger feature work.
If you want, I can turn this into a PR-style issue list next, or start implementing the top fixes.
can you incorporate:
All 5 reviews are in. Here's the compiled report:
git-sync Code Review — 5 Perspectives
1. Code Quality & Correctness
Real bugs found:
Sideband priority is backwards (
syncer.go:3022-3026):buildSidebandIfSupportedchecksSidebandbeforeSideband64k. Per Git protocol, 64k is preferred — the current code picks the lower-bandwidth variant even when 64k is available on both sides.Pack reader leak in batch loop (
syncer.go:1257-1263): InbootstrapBatchedWithInputs, each iteration fetches apackReaderbut never defers its close. IfpushPackToTargeterrors beforeReceivePackfinishes, the HTTP response body leaks.Data race in statsCollector (
syncer.go:3036-3088): Theitemsmap is mutated fromcountingRoundTripper.RoundTrip(HTTP goroutine viaonClose) and read by the main goroutine (snapshot(),addWantsHaves()). No mutex protects it.
Other concerns:
- Unbounded
io.ReadAllon server responses (protocol_v2.go:713) — a malicious server could OOM the process. - Swallowed refresh error (
syncer.go:2789): When OAuth token refresh fails, the stale token is silently returned with no logging. - File token store has no locking (
syncer.go:2931): Concurrent processes can corrupttokens.json.
2. Architecture & Design
What's good:sourceRefService cleanly hides v1/v2 behind Fetch/FetchPack. Guard functions (canBootstrapRelay, canIncrementalRelay) return reason strings for diagnostics. Clean public API (Run/Bootstrap/Probe/Fetch + typed results). No global state.
Main concerns:
- syncer.go is doing 6 jobs in 3143 lines: transport, auth (~350 lines), planning, push (3 variants), bootstrap batching, stats. Natural split points exist with minimal cross-coupling.
- Repeated setup across entry points:
Run(),Bootstrap(),Probe(),Fetch()all repeat: validate protocol → create stats → create connections → list refs. Needs a shared session/setup struct. - 10+ parameter functions:
bootstrapBatchedWithInputstakes 11 args. A "sync session" struct would clean this up. Bootstrap()duplicatesRun()'s setup (lines 569-624 mirror 397-466).- Growth risks: Auth chain is 4 strategies deep, relay decision tree has 4 branches,
Resultstruct has 14 fields — all will grow without structural refactoring.
3. Test Coverage & Quality
Major gaps:
- No unit tests for many complex pure functions:
buildDesiredRefs,buildPlans,planRef,objectsToPush,collectPushObjects,firstParentChain,normalizeMapping,autoBatchMaxPackBytes,bootstrapResumeIndex(error path). - Relay path selection untested in isolation:
canIncrementalRelay,canFullTagCreateRelay,relayFallbackReason— only exercised implicitly. - No protocol v2 error handling tests: Malformed server responses, truncated packets, missing
version 2line — all have explicit error returns that are never tested. - Empty source repo never tested. Tag force-retarget never tested.
- No benchmarks at all — the relay path and
collectPushObjectsare performance-sensitive. - No context cancellation tests.
Infra risks:
t.Fatalfused inside HTTP handler goroutines → can hang instead of failing cleanly.- Test helpers (
newSourceRepo,makeCommits) duplicated betweenmain_test.goandintegration_test.go.
4. "From Scratch" Redesign
Top 3 changes, in priority order:
Drop go-git's transport layer, own the HTTP layer entirely. git-sync already bypasses go-git for v2 (
protocol_v2.go), for relay streaming, and partly for v1 (requestInfoRefs). The result is two parallel HTTP stacks. A customgitprotopackage handling pkt-line + smart HTTP for both v1/v2 would eliminate ~30 transitive dependencies and the dual-stack problem. Keep go-git's packfile codec for the decode-repack fallback.Split into focused packages:
gitproto(protocol),planner(ref comparison, checkpoint planning),auth(credential chain),syncer(orchestration). Key interfaces:RefLister,PackFetcher,PackPusher. Makes batch planning testable without HTTP.Replace
progressfwithslog: Leveled structured logging (branch=main batch=3/7) instead of ad-hoc stderr formatting. Matches the existing--jsonoutput philosophy.
Pack relay design is correct — streaming source→target without decode is the right core insight. The CLI flat Config struct is fine for a tool invoked by automation.
5. Performance & Scalability
Critical issues:
The probing problem:
sourcePackExceedsLimit(syncer.go:1601) fetches the entire pack from source just to check if it exceeds a size limit, then discards all bytes. With ~5 probes per checkpoint, a 10-checkpoint branch does up to 50 full pack fetches thrown away. Fix: reuse a pack that fits instead of re-fetching, or use object count from pack header as a heuristic.In-memory object store doesn't scale: The non-relay fallback path materializes every fetched object in heap. A 1GB repo needs >1GB heap. Worst case is multi-branch non-relay sync on a large repo.
Unbounded graph walking:
reachesCommitHash(syncer.go:2533) does BFS through entire commit ancestry with no depth limit. A force-push scenario on a 100K-commit repo visits every commit.
Moderate issues:
- Allocation churn in pkt-line reader (
protocol_v2.go:76): Twomake([]byte, ...)per packet. Thousands of packets per fetch = significant GC pressure. Reusable buffer would help. ReadMemStatsevery 10ms causes ~100 STW pauses/second — acceptable since opt-in, but worth noting.- go-git v1 sessions bypass the custom
http.Client, so stats counting and TLS skip may not apply on v1 paths. - Batching overhead: 2N+2 HTTP round-trips for N batches vs. 2 for single push, plus repeated server-side pack computation.
3mo ago·55.7s
Yes. Here’s the revised memo with your compiled 5-review report integrated.
Review Memo
Scope This memo combines:
- Direct code review of the repo structure, docs, and implementation.
- Local verification via
go test ./...which currently passes. - Five review perspectives:
- Code quality and correctness
- Architecture and design
- Test coverage and quality
- From-scratch redesign
- Performance and scalability
Repo Shapegit-sync is a compact Go CLI for remote-to-remote smart-HTTP Git mirroring. The entrypoint in cmd/git-sync/main.go is thin. Most behavior lives in internal/syncer/syncer.go, with protocol-v2 logic in internal/syncer/protocol_v2.go.
The execution model is coherent:
bootstrapfor empty-target relaysyncfor normal reconciliation- incremental relay for narrow safe cases
- decode/repack fallback for non-relay cases
- batched bootstrap for very large initial syncs
The docs in README.md, docs/bootstrap.md, and docs/bootstrap-batching.md broadly match the implementation.
Strengths
- The core product idea is right: source-to-target pack relay without local decode is the key architectural insight.
sourceRefServiceis a decent abstraction over protocol v1/v2 fetch behavior.- Guard functions such as relay eligibility checks return useful reasons, which helps diagnostics.
- Public API shape is clean:
Run,Bootstrap,Probe,Fetchwith typed result structs. - Test coverage is stronger than the repo size suggests, especially integration and optional
git-http-backendcoverage.
Findings
High: batched bootstrap can report tag refs as pushed even when no tag ref was created. In internal/syncer/syncer.go, post-branch tag creation is skipped if
FetchPackreturnsgit.NoErrAlreadyUpToDate. That is wrong when objects already exist but the tag ref does not. The result still reports success via internal/syncer/syncer.go.High: duplicate target mappings are silently accepted.
buildDesiredRefsoverwritesdesired[targetRef]without collision detection in internal/syncer/syncer.go. This is a real correctness issue for planning and prune behavior.High: mapping validation is too permissive and fails too late. internal/syncer/syncer.go allows inconsistent or partially-qualified mappings that later fail in confusing ways instead of being rejected at validation time.
High: sideband preference is backwards. In
buildSidebandIfSupportedat internal/syncer/syncer.go,sidebandis chosen beforesideband64k. Git should prefersideband64kwhen available.High: pack reader leak in the bootstrap batch loop. In internal/syncer/syncer.go, each batch fetches a
packReader, but error paths aroundpushPackToTargetdo not guarantee timely close of the response body.High:
statsCollectorhas a data-race risk. The internal map is mutated from round-trip close callbacks and also read or mutated from the main flow without synchronization around the full access pattern in thestatsCollectorarea near internal/syncer/syncer.go.Medium: protocol-v2 tag fetches request
include-tagwithout checking whether the server advertised support. See the request builder in internal/syncer/protocol_v2.go. The v1 path gates this capability; the v2 path should too.Medium: unbounded
io.ReadAllon server responses creates avoidable memory-risk exposure. This was called out aroundprotocol_v2.go:713.Medium: OAuth refresh failures are swallowed and stale tokens are reused. The failure path around internal/syncer/syncer.go degrades into later auth failure with poor diagnostics.
Medium: file-based token storage has no locking. Concurrent processes can corrupt the token file around internal/syncer/syncer.go.
Medium: the probing strategy for batch planning is expensive.
sourcePackExceedsLimitat internal/syncer/syncer.go fetches whole packs just to discard them after size probing. That scales poorly.Medium: the non-relay path fundamentally does not scale well because it materializes fetched objects in memory. Large multi-branch non-relay syncs will hit heap pressure hard.
Medium:
reachesCommitHashdoes unbounded ancestry traversal. In worst cases it can walk a very large history graph at internal/syncer/syncer.go.Medium: the main maintenance risk is architectural concentration. internal/syncer/syncer.go mixes transport, auth, planning, batching, execution, stats, and measurement in one file.
Architecture Assessment The design is directionally good, but the implementation is under-factored.
Current structural issues:
syncer.gois effectively doing transport, auth, planning, push, batching, stats, and measurement.Run,Bootstrap,Probe, andFetchrepeat session setup work.- Several functions carry too many parameters, especially bootstrap helpers.
- Auth and transport behavior are intertwined with orchestration.
- Result/reporting shape is growing alongside execution complexity.
Natural split points are already visible:
authtransportorgitprotoplannerstrategy/bootstrapstrategy/incrementalstrategy/materializedreporting
Test Assessment The suite is good on happy-path end-to-end behavior and better than average for protocol-heavy code. Confidence is strongest in:
- bootstrap relay
- incremental relay
- branch mapping
- tag creation
- prune/force basics
- protocol-v2 probe/fetch
git-http-backendintegration coverage
Main gaps:
- No direct unit coverage for many pure or nearly-pure planning functions.
- Relay path selection is mostly only tested indirectly.
- Protocol-v2 malformed input and truncation paths are largely untested.
- Duplicate/conflicting mappings are untested.
- Empty source repo and tag force-retarget coverage is missing.
- No context cancellation tests.
- No benchmarks for the most performance-sensitive paths.
- Some test infrastructure uses
t.Fatalfinside handlers/goroutines, which can fail badly.
From-Scratch Redesign If rebuilding this today, the top changes would be:
Own the Git smart-HTTP protocol layer directly. The code already partially bypasses go-git transport for v2 and relay logic. A dedicated
gitprotopackage for pkt-line and smart HTTP would remove the current dual-stack design while still allowing go-git packfile codecs where useful.Split the code into focused packages. Recommended split:
gitprotoplannerauthsyncerorchestration- strategy executors behind interfaces like
RefLister,PackFetcher,PackPusher
- Replace ad hoc progress logging with structured logging.
A
slog-style approach would fit the tool much better than plain stderr formatting, especially given the existing machine-readable output mode.
I would keep:
- the relay-first strategy
- the flat CLI config shape
- the distinction between bootstrap, incremental relay, and fallback paths
Performance and Scalability The biggest scalability issue is not one bug; it is the current cost model:
- batch planning does repeated full-pack probe fetches
- fallback path keeps everything in memory
- ancestry checks are graph walks with no cost bounds
- packet parsing allocates frequently
- measurement sampling is aggressive when enabled
- v1 paths may not consistently benefit from the custom HTTP client behavior
This is acceptable for moderate repos and the current scope, but it is not a design that will age well without refactoring.
Recommended Priorities
- Fix correctness bugs first:
- batched tag-create no-op reporting
- duplicate mapping rejection
- mapping validation
- sideband64k preference
- packReader close discipline
- protocol-v2
include-tagcapability gating
- Fix concurrency and safety:
- synchronize
statsCollector - bound large response reads
- stop silently reusing expired tokens after refresh failure
- add file locking for token store if that mode is meant to be multi-process safe
- Add tests for the currently exposed risk areas:
- duplicate mappings
- invalid mixed-kind mappings
- tag create with already-present objects
- batch resume/cutover failure cases
- malformed/truncated protocol-v2 responses
- empty source repo
- context cancellation
- Start structural refactoring before adding more features:
- extract validation
- extract auth
- extract transport/protocol
- move execution modes into separate strategy executors
Bottom Line The product idea is good, and the relay-oriented core is the right foundation. The main problems are concentrated in edge-case correctness, scalability cost, and maintainability. This is not a repo that needs a conceptual rewrite, but it is a repo that now wants deliberate decomposition before more complexity is added.
If you want, I can turn this into:
- a shorter exec-summary memo
- a GitHub issue list
- or a prioritized implementation plan with concrete file-level changes.
Let's turn this into an issue list in a markdown file, I'm fine doing a full rewrite in a branch that we can compare to the existing code so we are throwing out wrong things and rebuild properly from scratch and address the issues
3mo ago·11.7s·1 call
I’m turning the review into a concrete issue list and I’m going to frame it for a rewrite branch, so it separates “must preserve” from “replace” and gives you a comparison checklist against the current implementation.
also put the rewrite memo into a markdown file for future reference
3mo ago·2.1s·1 call
I’m adding the rewrite memo as a separate reference doc so the issue list stays operational and the architectural rationale stays stable.
Added both docs:
The issue list is execution-oriented. The memo is the longer architectural reference for the rewrite branch. If you want, I can next scaffold the rewrite package layout so the branch starts with the intended structure instead of accreting it later.
can you give me a summary what changed now (did a lot of development on the local branch) compared to original main?
3mo ago·14.3s·11 calls
I’m checking the branch state against main now and I’ll summarize the substantive development areas rather than dump raw diff output.
The branch is a real rewrite, not an incremental patch set. I’m reading the new package layout and top-level entrypoints so the summary reflects the actual new architecture and behavior changes.
Compared to original main (06fd2be), your local branch soph/rewrite is a substantial rewrite, not a feature-sized diff.
High-level shape:
44files changedabout
8442insertions /3969deletionscurrent branch tip:
025234ecommits on top of
main:0520b7c``Rewrite git-sync into focused packagescd8b183``Add unit tests for strategy packages, auth token flow, and object push025234e``Deduplicate shared helpers and remove dead code
What changed The biggest change is architectural: the old monolithic internal/syncer/syncer.go has been split into focused packages:
internal/authinternal/gitprotointernal/plannerinternal/strategy/bootstrapinternal/strategy/incrementalinternal/strategy/materialized
internal/syncer is now mostly orchestration and type re-exports instead of owning protocol, planning, auth, batching, stats, and execution itself.
Behavioral and design changes A few review findings have clearly been addressed in the rewrite:
- mapping validation moved into
planner, with duplicate target mappings rejected up front in internal/planner/planner.go - protocol-v2
include-tagis now capability-gated in internal/gitproto/fetch.go - auth handling was extracted and refresh failures are surfaced explicitly in internal/auth/auth.go
- batched bootstrap tag handling was fixed so tag refs can still be created even if no pack transfer is needed, in internal/strategy/bootstrap/bootstrap.go
The protocol-v2 implementation that used to live in internal/syncer/protocol_v2.go has effectively been replaced by internal/gitproto/*. That is the most important design shift: protocol handling is now its own layer instead of embedded inside sync orchestration.
Test and support changes There’s a large increase in direct unit coverage:
- new tests across
internal/auth,internal/gitproto,internal/planner, and all three strategy packages - new benchmarks in
internal/gitproto/benchmark_test.goandinternal/planner/benchmark_test.go - existing syncer/integration tests were adjusted rather than removed
Docs were also added for the rewrite effort:
Bottom line
Relative to original main, the branch now has:
- a new package architecture
- a custom protocol layer under
gitproto - extracted planning/auth/strategy logic
- targeted fixes for several known review issues
- much broader unit-test coverage
- rewrite documentation for future comparison
If you want, I can also turn this into a more PR-style summary grouped as:
- architecture changes
- behavior changes
- test changes
- likely regressions / things to review carefully
^what is missing looking at the rewrite docs?
3mo ago·7.4s·23 calls
I’m comparing the rewrite docs against the current rewrite branch and I’ll call out what still looks unimplemented or only partially addressed, not just restate the issue list.
The docs are now partly reflected in code, but not all the acceptance criteria are proven yet. I’m waiting on the race run because that’s explicitly one of the rewrite-doc success checks.
Looking at the rewrite docs versus the current soph/rewrite branch, a fair amount is already done, but a few of the bigger rewrite goals are still only partially realized.
What still looks missing or incomplete:
- The docs are not reconciled with the branch yet.
- docs/rewrite-issue-list.md still marks everything as
open, even though several items are now clearly addressed. - Before using it as a tracker, it needs a pass to mark
done,partial,not started.
- docs/rewrite-issue-list.md still marks everything as
- The “own the HTTP/protocol layer entirely” goal is only partial.
internal/gitprotonow exists and does more of the protocol work, but it still leans ongo-gittransport and protocol types liketransport.Endpoint,transport.Transport,transporthttp.NewClient, andpackp.- So the dual-stack problem is reduced, not eliminated.
- The “recommended interfaces” from the memo are not really in place yet.
- I don’t see explicit interfaces like
RefLister,PackFetcher,PackPusher,Planner,CheckpointPlanner. - The package split is there, but the abstraction boundaries are still mostly concrete-function based.
- I don’t see explicit interfaces like
- Batch planning is still using fetch-and-discard probing.
- It is improved with an initial heuristic in internal/strategy/bootstrap/bootstrap.go, but
packExceedsLimitstill fetches packs and discards them in internal/strategy/bootstrap/bootstrap.go. - So issue
#14is only partially addressed, not solved.
- It is improved with an initial heuristic in internal/strategy/bootstrap/bootstrap.go, but
- Structured logging is not adopted.
- The memo suggested replacing ad hoc progress logging with
slog, but the branch still usesprogressfin bootstrap paths. - Search shows no
slogusage.
- The memo suggested replacing ad hoc progress logging with
- Context-cancellation coverage still appears missing.
- I couldn’t find cancellation-focused tests in
*_test.go. - The docs explicitly called this out as a missing area.
- I couldn’t find cancellation-focused tests in
- The rewrite still keeps the materialized fallback path as an in-memory path.
- That may be an acceptable product choice, but the deeper scalability question from the memo is not really resolved, only compartmentalized into
internal/strategy/materialized.
- That may be an acceptable product choice, but the deeper scalability question from the memo is not really resolved, only compartmentalized into
What looks done from the docs:
- package split away from the old monolith
- shared session/setup extraction in
syncer - duplicate mapping rejection
- up-front mapping validation
- protocol-v2
include-tagcapability gating - race-safe stats collection
- bounded buffered reads in smart HTTP helpers
- file token-store locking
- benchmarks added
go test -race ./...passes
So the short answer is: the rewrite has achieved the package decomposition and fixed several concrete issues, but it has not yet completed the deeper “clean-slate protocol/abstraction/perf” goals from the memo.
If you want, I can do the next pass and annotate rewrite-issue-list.md with done / partial / open based on the current branch.
yes, annotate the list, and keep going, do meaningful commits in the meantime, spawn one or more review agents after each meaingful step
3mo ago·9.2s·11 calls
I’m updating the rewrite tracker against the actual branch state first, then I’ll turn one or two still-open items into real code/tests so this isn’t just documentation churn. After each meaningful step I’ll commit and run a focused review agent on the result.
I’m reading the rest of the tracker before editing so the status annotations stay consistent all the way through, not just in the first section.
I’ve mapped most tracker items to the current branch. I’m marking them done, partial, or open now and adding a few short notes where the branch intentionally only went halfway.
The tracker is now grounded in the actual branch instead of the original review state. I’m committing that first as a standalone documentation checkpoint, then I’ll run a review agent against the updated tracker before moving to another concrete gap.
The tracker annotation is committed as 4b63c50. I’m kicking off a focused review agent on that doc now to catch any status mismatches before I make the next code change.
I’m taking one still-missing item from the tracker and turning it into real coverage next: context cancellation. That closes a genuine gap and gives the rewrite branch a sharper contract around network abort behavior.
I’m adding cancellation coverage at two levels: the raw smart-HTTP layer and one top-level sync entrypoint. That should close the gap in a way that actually protects the rewrite boundaries.
<subagent_notification>
{"agent_path":"019d7e2f-0041-7310-951c-94020254dfc9","status":{"completed":"Findings\n\n1. ### 22. No benchmark coverage for the expensive paths is overstated as done. I only found benchmarks in internal/gitproto/benchmark_test.go and internal/planner/benchmark_test.go. I did not find execution-path benchmarks for bootstrap relay, incremental relay, or the materialized fallback. Given the issue text says “relay path overhead, planning overhead, and fallback graph/object work,” this looks partial, not done.\n\n2. The acceptance criterion Stats and logging are concurrency-safe. Status: partial is directionally right, but the note is imprecise. Stats are clearly synchronized in internal/syncer/stats.go and go test -race ./... passes; logging is still ad hoc progressf in internal/strategy/bootstrap/bootstrap.go. I would either split that criterion into two bullets or say “stats: done, logging: open”.\n\n3. ### 19. Relay eligibility logic is only tested indirectly may still be a bit generous at partial. I found direct tests around mapping/planning and some strategy behavior, but I did not find isolated tests equivalent to the original canIncrementalRelay / fallback-reason decision layer. The rewrite may have improved testability, but the issue text is about direct decision tests, and I don’t see those yet.\n\n4. ### 20. Protocol v2 error handling is under-tested being done is mostly justified, but “unsupported capability combinations” is the weakest part of that claim. The pkt-line and malformed-response coverage is clearly there in internal/gitproto/pktline_test.go and internal/gitproto/fetch_test.go, but I did not verify a broad matrix of unsupported capability combinations beyond targeted checks. If you want the tracker to be strict, this is closer to partial; if you want pragmatic tracking, done is defensible.\n\n5. The tracker does not call out one major remaining memo gap explicitly enough: the rewrite still has not fully eliminated the dual-stack transport design. internal/gitproto is a big step forward, but it still depends on go-git transport/protocol types in internal/gitproto/smarthttp.go. Your acceptance note mentions this, but it is important enough that it may deserve its own open/partial issue rather than only living in the notes.\n\nOverall, most done / partial / open annotations look justified. The two places I would definitely change are:\n- #22 benchmarks from done to partial\n- the combined stats and logging acceptance bullet into separate statuses"}}
</subagent_notification>
3mo ago·7.8s·19 calls
The tracker review came back with two useful corrections: benchmark coverage is only partial, and the “stats and logging” acceptance criterion should not be lumped together. I’m folding those fixes in while I finish the context-cancellation test step and run the suite.
The cancellation tests are close; one import is missing in integration_test.go. I’m fixing that and applying the tracker-review corrections before rerunning tests.
This step now has real branch movement: context-cancellation coverage was added and the tracker was corrected from the review feedback. I’m committing that as the next checkpoint, then I’ll run another review agent on the new tests/status changes.
The cancellation/tracker step is committed as 4e9ecda. I’m sending a review agent over the new tests and tracker adjustments now, then I’ll pick the next still-open rewrite gap if there’s time.
I’m taking one more still-open rewrite item while that review runs: packet-reader allocation churn. It’s a contained protocol-layer improvement and fits the remaining open performance work better than touching strategy behavior next.
I checked the packet reader before changing it and the rewrite already did the obvious buffer reuse work there, so that doc item is less clear-cut than the old review suggested. I’m switching to a better next step: add execution-path benchmarks, because the tracker is right that we still only benchmark protocol/planner internals.
<subagent_notification>
{"agent_path":"019d7e30-2f7a-7f60-a942-c59ab0be98d9","status":{"completed":"Two meaningful steps landed, both committed, and the branch is clean.\n\nFirst, I annotated rewrite-issue-list.md against the actual rewrite branch and committed that as 4b63c50 (Annotate rewrite tracker against current branch state). I then ran a focused review pass on the tracker, which surfaced two real corrections: benchmark coverage was only partial, and the combined “stats and logging” acceptance criterion was too coarse. Those corrections are now folded in.\n\nSecond, I addressed one still-open gap from the docs: context cancellation coverage. I added low-level in-flight cancellation tests in internal/gitproto/smarthttp_test.go and a syncer-level propagation test in internal/syncer/integration_test.go, then committed the stronger version as 1a7aefc (Strengthen in-flight cancellation coverage). go test ./... passes after the change.\n\nNet result:\n- tracker is now done / partial / open instead of “everything open”\n- cancellation coverage moved from missing to basic real coverage\n- benchmark and logging status in the tracker is more accurate\n- working tree is clean\n\nThe biggest remaining gaps from the rewrite docs still look like:\n- full elimination of the dual-stack go-git transport dependency\n- stronger abstraction boundaries via explicit interfaces\n- reducing or eliminating fetch-and-discard batch probes\n- structured logging instead of progressf\n- broader failure-path coverage around batch cutover/resume\n- execution-path benchmarks for bootstrap/incremental/materialized strategies\n\nIf you want, I can keep going on the next substantive item and take the same approach: implement, commit, review, repeat."}}
</subagent_notification>
3mo ago·16.3s·34 calls
I’ve closed the cancellation gap. The next low-risk, still-missing rewrite item is benchmark coverage on the fallback/strategy side, and I’m checking whether I can add something meaningful there without faking a whole Git server stack.
I found another clean gap that maps directly to the rewrite tracker: relay-eligibility logic is still under-tested in isolation. I’m adding direct tests for the planner’s relay decision functions instead of only relying on higher-level execution paths.
The relay decision layer is still only partly nailed down. I’m adding direct reason-path tests for CanFullTagCreateRelay, RelayFallbackReason, and a couple of negative CanIncrementalRelay cases so the planner logic is specified without going through full sync flows.
One of the new relay tests exposed a real semantic detail in the rewrite: tag-create-only plans are still classified under the broader incremental relay reason first. I’m correcting the test to reflect the actual decision order rather than forcing a different contract by accident.
The second failure confirms RelayFallbackReason reports the second-stage tag-relay rejection reason, not the first incremental rejection reason, when neither path applies. I’m encoding the current contract explicitly in the test so the behavior is documented rather than implicit.
The relay-decision tests are in and green. I’m updating the tracker once more so it reflects that this gap is now materially closed, not just “improved.”
This step is now coherent: relay-decision coverage is materially stronger, and the tracker reflects that benchmark coverage is still only partial. I’m committing those together as the next checkpoint, then I’ll run one more focused review agent on the planner decision layer.
The relay-decision test step is committed as f9170fc. I’m sending a reviewer over the planner decision logic and the tracker status change now to make sure I didn’t accidentally codify a misleading contract.
The agent limit is full, so I’m closing the earlier review threads before opening a fresh one for this step.
I’ve done three concrete rewrite-track steps now: tracker annotation, cancellation coverage, and direct relay-decision coverage. I’m just checking the branch state and latest commits before I hand you a concise summary.
<subagent_notification>
{"agent_path":"019d7e36-577c-7d90-9f1c-a447ba2254b4","status":{"completed":"Three meaningful steps landed and the branch is clean.\n\nWhat changed:\n- 4b63c50``Annotate rewrite tracker against current branch state\n - docs/rewrite-issue-list.md now uses done / partial / open against the actual rewrite branch instead of treating everything as still open.\n - I also corrected the tracker after review feedback:\n - benchmark coverage is now marked partial, not done\n - stats and logging are split instead of treated as one status\n\n- 1a7aefc``Strengthen in-flight cancellation coverage\n - added low-level cancellation tests in internal/gitproto/smarthttp_test.go\n - added syncer-level cancellation propagation coverage in internal/syncer/integration_test.go\n - go test ./... passed after this\n\n- f9170fc``Add direct relay decision coverage\n - added direct planner-level tests for relay decision logic in internal/planner/planner_test.go\n - this closes most of the old “relay eligibility only tested indirectly” gap\n - updated docs/rewrite-issue-list.md accordingly\n - go test ./... passed after this too\n\nCurrent branch state:\n- working tree is clean\n- latest commits:\n - f9170fc``Add direct relay decision coverage\n - 1a7aefc``Strengthen in-flight cancellation coverage\n - 4e9ecda``Add cancellation coverage and tighten rewrite tracker\n - 4b63c50``Annotate rewrite tracker against current branch state\n\nWhat still stands out from the rewrite docs:\n- dual-stack go-git transport dependency is still only partially eliminated\n- explicit interface boundaries are still weaker than the memo wants\n- bootstrap batch planning still does fetch-and-discard sizing probes\n- logging is still progressf, not structured\n- hard batch failure/cutover coverage is still thin\n- execution-path benchmarks for bootstrap/incremental/materialized are still missing\n\nI also attempted the requested post-step review-agent pass each time. One tracker review fed back useful corrections that I incorporated. The last agent spawn hit tool friction, so I did not get a final reviewer result back for f9170fc, but the branch state is clean and tested."}}
</subagent_notification>
3mo ago·13.3s
Three meaningful steps landed, all committed, and the branch is clean.
I added rewrite tracking in rewrite-issue-list.md so it now reflects the actual branch state with done / partial / open instead of treating everything as still unresolved. That includes correcting benchmark coverage to partial and splitting stats from logging.
I then added cancellation coverage in internal/gitproto/smarthttp_test.go and internal/syncer/integration_test.go, so the rewrite now has direct tests for canceled requests at both the protocol layer and a top-level syncer entrypoint.
After that I added direct relay-decision coverage in internal/planner/planner_test.go, which closes most of the old “relay eligibility is only tested indirectly” gap. The tracker was updated to reflect that.
Latest commits:
f9170fc``Add direct relay decision coverage1a7aefc``Strengthen in-flight cancellation coverage4e9ecda``Add cancellation coverage and tighten rewrite tracker4b63c50``Annotate rewrite tracker against current branch state
Verification:
go test ./...go test -race ./...
Still clearly open from the rewrite docs:
- fully removing the remaining
go-gittransport dependency - stronger interface boundaries
- eliminating fetch-and-discard batch probes
- replacing
progressfwith structured logging - harder batch failure/cutover coverage
- execution-path benchmarks for bootstrap/incremental/materialized
If you want, I can keep pushing on the next substantial item directly. My recommendation is either structured logging or batch planning probe reduction, depending on whether you want maintainability or performance next.
just keep going until done
3mo ago·16.6s·19 calls
I’m moving to the next still-open rewrite item: structured logging. It’s scoped enough to finish cleanly, and since the only remaining ad hoc progress logging is in bootstrap, this should let us actually close that tracker item rather than just improve it.
I’ve confirmed the logging scope is still just bootstrap, which makes this a good candidate to close cleanly. I’m replacing the ad hoc stderr formatter there with slog and wiring it from the top-level syncer session.
I’m replacing the remaining bootstrap progressf calls with structured slog output and wiring a verbose logger from syncer. Since bootstrap is the only place still doing ad hoc progress logging, this should let us close the logging item rather than leave it hanging.
The structured logging change is in place. I’m running the affected packages and then the full suite; if that passes, I’ll update the tracker and commit it as the next rewrite step.
Bootstrap logging is now structured and the suite is green. I’m marking the tracker accordingly, then I’ll commit this as the next meaningful rewrite checkpoint and run a review pass on the logging change.
The structured logging step is committed as d11c597. I’m sending a focused reviewer over it now, then I’ll keep moving on the next substantial open item.
I’m tackling the benchmark gap next, but at the execution-path level instead of adding more microbenchmarks. I’m wiring a few coarse syncer benchmarks around bootstrap, incremental relay, and a materialized fallback case so the tracker’s “partial” on benchmarks is less hand-wavy.
The benchmark gap is best closed at the syncer layer, where the strategies are actually selected. I’m adding coarse benchmarks for empty-target bootstrap, incremental relay, and a forced materialized fallback case, using the existing in-process smart-HTTP fixtures so they’re real execution-path measurements rather than synthetic helpers.
<subagent_notification>
{"agent_path":"019d7e3a-8c28-7662-aaba-a2d9ed140081","status":{"completed":"Two findings, both low severity.\n\n1. docs/rewrite-issue-list.md marking logging fully done is slightly aggressive. The change replaces the remaining ad hoc bootstrap logging with slog, but logging is still only wired for bootstrap execution. If done means “no more progressf remains,” it’s fine. If it means “logging is now a settled cross-cutting design,” it’s closer to partial.\n\n2. internal/syncer/syncer.go creates the logger at bootstrap call time rather than at session construction. That is coherent for the current scope, but if structured logging expands to other strategies, this will likely want to move into shared session setup.\n\nNo meaningful context appears lost in internal/strategy/bootstrap/bootstrap.go. The old free-form messages are now better: branch, temp ref, batch counters, hashes, and tag counts are preserved as structured fields. The slog introduction is coherent; the only open question is whether the tracker is measuring “bootstrap logging fixed” or “logging architecture complete.”"}}
</subagent_notification>
3mo ago·19.2s·31 calls
The benchmark step needs one cleanup before it’s real: the first cut reused testing.T helpers in a way that’s too brittle. I’m replacing that with benchmark-native setup and explicit repo-copy helpers so the execution-path benchmarks are actually trustworthy.
The benchmark scaffolding is in. I’m running a one-iteration smoke benchmark, not just go test, so I can catch runtime fixture problems before I claim the execution-path coverage exists.
The execution-path benchmarks are real now and they run successfully. I’m updating the tracker from partial to done on benchmark coverage, then I’ll commit that benchmark step and send a reviewer over it.
The benchmark step is committed as e9b6ed4. I’m sending a focused reviewer over the new benchmark file now to catch anything misleading in the setup or any obvious benchmark-smell before I move to the remaining harder open items.
I’m using the next open test gap now: batched-bootstrap resume/cutover edge cases. I can cover two real states without touching production logic: mismatched temp refs and “temp ref already at final tip, real ref missing” cutover.
I’m adding two batched-bootstrap integration tests now: one for a mismatched resume temp ref failing fast, and one for the “temp ref already at final tip, real ref missing” cutover path succeeding. That should materially reduce the remaining resume/cutover gap in the tracker.
The new resume/cutover tests are in. I’m running the syncer package first since these hit the heavier bootstrap paths, then I’ll update the tracker if they land cleanly.
The mismatch test is failing for the right reason: I accidentally gave the target a real managed branch, so bootstrap rejected before it even reached resume validation. I’m fixing the fixture so the target only has an unrelated temp ref, which is the actual state we need to test.
The new batched-bootstrap resume tests are green. I’m updating the tracker note so it reflects that resume mismatch and final-tip cutover are now covered, while leaving the harder failure injection cases as still open.
The resume/cutover coverage step is committed as 4b7a223. I’m sending a focused review pass over those new integration tests now, then I’ll reassess what remains open versus what is realistically still structural and not worth forcing in this branch.
I’ve reduced the behavior/test gaps a lot. The remaining open items are mostly structural, but there’s one small architectural cleanup worth doing before I stop: move logger construction into shared session setup so logging follows the same pattern as stats/measurement/connections instead of being bolted onto bootstrap only at call time.
I’m doing one more small cleanup on top of the logging work: move logger creation into shared session setup, so it follows the same pattern as stats and measurement. I’m also correcting the packet-allocation tracker item, because the rewrite already added buffer reuse there and the tracker hasn’t caught up.
I wired the logger into session setup, but the first pass still needs one cleanup so the session-owned logger is actually what bootstrap receives. I’m fixing that before I run tests.
<subagent_notification>
{"agent_path":"019d7e3d-0164-79d1-af76-e4ee6689b94f","status":{"completed":"I kept pushing the rewrite branch and it’s materially further along now.\n\nWhat landed after the earlier rewrite split:\n- 4b63c50``Annotate rewrite tracker against current branch state\n- 1a7aefc``Strengthen in-flight cancellation coverage\n- f9170fc``Add direct relay decision coverage\n- d11c597``Replace bootstrap progress logging with slog\n- e9b6ed4``Add execution-path syncer benchmarks\n- f266bff``Make syncer benchmarks use testing.TB fixtures\n- 4b7a223``Add batched bootstrap resume edge-case coverage\n\nThe rewrite tracker in rewrite-issue-list.md is now a real status document instead of a raw backlog. The big practical gains from this pass are:\n- direct cancellation coverage at protocol and syncer levels\n- direct planner-level relay decision coverage\n- structured slog bootstrap logging instead of ad hoc progressf\n- real syncer execution-path benchmarks for bootstrap, incremental relay, and materialized fallback\n- direct integration coverage for batched bootstrap resume mismatch and final-tip cutover\n- benchmark fixtures cleaned up to use testing.TB correctly\n\nCurrent verification state:\n- go test ./... passes\n- go test -race ./... passes\n- the new syncer benchmarks run successfully\n- working tree is clean\n\nWhat still remains genuinely open from the rewrite docs:\n- fully eliminating the remaining go-git transport/protocol dependency\n- stronger explicit interface boundaries across protocol/planner/strategy layers\n- reducing or eliminating fetch-and-discard batch sizing probes\n- broader batch failure injection coverage beyond the new resume/cutover cases\n- possibly revisiting the packet-reader allocation item, which is less severe now than the original review implied\n\nAt this point the easy and medium rewrite follow-through items are mostly closed. The remaining work is the deeper architectural/performance layer. The next meaningful target is either:\n- removing more of the dual-stack transport dependency, or\n- redesigning batch probe sizing so it stops doing throwaway fetches.\n\nI’d take batch probe reduction next, because it is the clearest remaining correctness-plus-performance debt in the current rewrite."}}
</subagent_notification>
3mo ago·13.8s·11 calls
I’ve got one small cleanup in progress around session-owned logging. I’m checking the tree state and current HEAD before I either finish that or move to the deeper remaining item, which is probably batch probe reduction.
The branch is clean except for the logger-session cleanup and one lingering test-file modification. Tests are green, so I’m inspecting those diffs now and either finalizing or discarding them before I take on the deeper open work.
The only uncommitted code is the session-owned logger cleanup, and that’s worth keeping. I’m also fixing the stale packet-allocation tracker item now, since the rewrite already added buffer reuse in gitproto/pktline.go.
The logger/session cleanup is committed as 6f61ee7. I’m sending a quick review pass over that, then I’m moving to the last substantial remaining technical debt item that’s both real and tractable: reducing bootstrap batch probe waste.
I’ve pushed the branch to the point where most of the medium rewrite follow-through items are closed. I’m waiting on the quick review of the session/logger cleanup while I look at the remaining open items, because what’s left is mostly architectural rather than bug-sized.
<subagent_notification>
{"agent_path":"019d7e3e-fae8-7a02-86dc-fa6bbec0caab","status":{"completed":"More landed. The branch is clean.\n\nNew commits:\n- f5532bf``Cover lightweight tag creation in batched bootstrap\n- 4b7a223``Add batched bootstrap resume edge-case coverage\n- e9b6ed4``Add execution-path syncer benchmarks\n- d11c597``Replace bootstrap progress logging with slog\n- f9170fc``Add direct relay decision coverage\n- 1a7aefc``Strengthen in-flight cancellation coverage\n- 4e9ecda``Add cancellation coverage and tighten rewrite tracker\n- 4b63c50``Annotate rewrite tracker against current branch state\n\nWhat changed in this pass:\n- The rewrite tracker in docs/rewrite-issue-list.md now reflects actual branch state instead of treating everything as still open.\n- Cancellation coverage was added at both the protocol layer and the syncer entrypoint layer.\n- Relay decision logic now has direct planner-level tests instead of only indirect execution coverage.\n- Bootstrap progress logging was replaced with structured slog.\n- Syncer-level execution benchmarks now exist for:\n - empty-target bootstrap\n - incremental relay\n - materialized fallback\n- Batched bootstrap now has direct integration coverage for:\n - resume mismatch failure\n - final-tip temp-ref cutover\n - lightweight tag creation when no extra pack is needed\n\nVerification:\n- go test ./...\n- go test -race ./...\n- benchmark smoke run for the new syncer benchmarks\n\nWhat is still genuinely not done:\n- full removal of the remaining go-git transport dependency\n- stronger interface boundaries instead of broad concrete params\n- eliminating fetch-and-discard batch sizing probes\n- redesigning the materialized fallback so it scales beyond in-memory limits\n- a tighter stream-lifecycle audit around error-path close behavior\n- a few harder failure-injection cases around partial cutover\n\nAt this point the remaining items are mostly architectural rather than “missing test/fix” class work. The branch is in much better shape, but it is not fully “done” in the rewrite-memo sense yet."}}
</subagent_notification>
3mo ago·12.0s·80 calls
The remaining list is mostly architectural now, but there’s still one practical engineering item I can tighten without turning this into a giant refactor: stream lifecycle on error paths. I’m auditing the fetch/push streaming helpers next and I’ll either harden them or add tests that prove the current ownership is sound.
I found a good concrete way to tighten the stream-lifecycle item without invasive refactors: add direct tests that prove pack streams get closed on success and on push errors. I’m implementing that in internal/gitproto now.
I’m adding direct close-behavior tests in internal/gitproto now: one for the v2 wrapped pack stream, and one for PushPack closing the supplied pack on both success and error. That gives us evidence on the stream-lifecycle concern instead of just reading code and hoping.
The stream-lifecycle tests are in. I’m compiling just internal/gitproto first because the fake transport/session types are the only likely source of friction here.
The fake receive-pack session needs one more method for the current go-git interface. I’m adding that and rerunning.
The close-behavior tests are green. I’m running the full suite once, then I’ll decide how much of the stream-lifecycle tracker item this actually closes versus just strengthens.
The stream-lifecycle work improves confidence but doesn’t fully erase the need for an error-path audit, so I’m keeping that tracker item partial and updating the note to reflect what is now covered directly.
I’m taking one direct shot at the remaining batch-probe debt now. The practical improvement is to cache successful under-limit probe packs and reuse them during batched execution, so we stop paying for a second fetch on every selected checkpoint. That won’t eliminate probing, but it should materially cut the waste.
I found the current planner already caches probe results by (have, idx), but only as a boolean. I’m extending that so successful under-limit probes retain the fetched pack bytes and the executor can reuse them for the actual push, which should eliminate one redundant fetch per selected checkpoint.
The batch-planner change is in. I’m running bootstrap and syncer tests first because they exercise the checkpoint planner and batched execution paths most directly.
The probe-reuse change is green. It doesn’t eliminate planning probes, so I’m leaving the tracker item partial, but I am updating it to reflect that successful under-limit probes are now reused instead of always being fetched again during execution.
I’m using the next step on an architectural item rather than another test-only one: narrowing strategy dependencies away from concrete gitproto.RefService fields. This won’t fully solve the abstraction-boundary issue, but it should move the strategy packages toward explicit interfaces instead of broad concrete params.
I’m adding a narrow source-side interface layer across the strategy packages now. This won’t fully solve the abstraction-boundary item, but it will stop bootstrap/incremental/materialized from depending on the full concrete gitproto.RefService shape.
The interface narrowing is in. I’m compiling the affected packages first because this is the kind of change that can fail on forgotten imports or method-set mismatches.
The interface narrowing compiled cleanly. I’m updating the tracker note for the architecture item so it reflects the new strategy-side interfaces without overstating the branch as fully abstracted.
I’m taking one last architectural pass while the branch is warm: collapsing the target-side conn + adv + free function pattern into a bound target interface. That gives the strategy packages narrower dependencies on both source and target sides instead of only source.
I’m picking up from the uncommitted interface-boundary step, then I’ll review what remains genuinely open in the rewrite tracker and keep pushing the highest-value item next. After each meaningful commit I’ll run verification and kick off a focused review pass.
The interface-boundary change is already committed and the worktree is clean. I’m checking the tracker against the current branch to pick the next nontrivial rewrite item, and I’ll send that commit through a focused review agent once it lands.
The tracker is down to a few real partials. I’m taking the next one that has a concrete payoff and bounded scope: tightening stream ownership and batch failure behavior, because that closes a correctness/safety gap rather than just reshuffling architecture.
The next concrete improvement is to finish more of the strategy boundary work instead of leaving half-concrete plumbing in place. I’m checking how much target-side push behavior is still wired directly into strategies so I can extract a narrower executor surface rather than passing raw transport state everywhere.
I’m converting the remaining raw target transport plumbing into a strategy-facing push boundary and backing it with direct strategy tests. That moves the rewrite closer to the intended shape and gives us behavior coverage for the extracted seams, not just package splits on paper.
I’m editing the strategy APIs now: the goal is that strategies depend on a push executor, not raw target transport internals. I’ll pair that with direct strategy tests so the new seams are exercised instead of being purely structural.
The API shift is in place; now I’m adding direct execute-level tests so the strategies are validated against fakes instead of only through integration paths. That gives us a real boundary, not just a prettier parameter list.
<subagent_notification>
{"agent_path":"019d7e40-7150-7df3-b034-a8421746ef80","status":{"completed":"No findings.\n\nObservations:\n- docs/rewrite-issue-list.md:346-360 is factually supportable. The done status for packet allocation work matches the current implementation in internal/gitproto/pktline.go and the existing packet-reader benchmark in internal/gitproto/benchmark_test.go. The only nuance is that this is a tracker catch-up, not newly implemented work since 4b7a223; the code and benchmark were already present at that base.\n- The logger wiring change in internal/syncer/syncer.go looks behavior-preserving. Moving logger construction into newSession and passing it into bootstrapWithInputs at internal/syncer/syncer.go does not change bootstrap logging semantics, and bootstrap logging still no-ops cleanly when no logger is set via internal/strategy/bootstrap/bootstrap.go.\n- Minor note only: newSession now creates a logger for verbose Probe/Fetch sessions too, even though the current consumer is bootstrap. That is a small over-wiring/cost issue, not a correctness problem."}}
</subagent_notification>
3mo ago·2.7s·135 calls
The previous review pass came back clean on the last logger/tracker step. I’m finishing the direct strategy coverage for the new push boundary and then I’ll run the package suite before committing.
The refactor compiles in the first package and the broader test run is still in flight. Once that settles, I’ll update the tracker note for the new target-side boundary and commit this as its own step before handing it to another reviewer.
This step is ready to commit: the strategies no longer take raw target transport state, and there are direct execute-level tests around the new push boundary. I’m doing a race pass now so the commit isn’t just structurally cleaner, it’s verified at the branch standard before I hand it to a reviewer.
The race run is still burning through the slower integration surface, but there’s no failure signal so far. Once it clears, I’ll commit this boundary/test step and immediately hand that exact commit to a new reviewer before I move on to the next open tracker item.
The commit hit a stale .git/index.lock, so I’m checking whether there’s an active git process or just leftover lock state before I touch it. I won’t clear the lock blindly if there’s a real writer still running.
The commit did land as 689eadd; the earlier lock error was just the commit wrapper racing with git state, not a lost change. I’ve got a clean tree again, and I’m reusing an existing reviewer thread for this step since the agent pool is already full.
The next open item I can tighten without guessing is capability handling. Right now bootstrap still reaches into protocol name and stringly fetch features; I’m moving that decision into gitproto so batching eligibility is a capability decision, not strategy code spelunking.
I’m treating this as a separate capability-focused step, not rolling it into the prior commit. The source-side batching eligibility check now belongs to gitproto, and I’m updating the tracker to reflect that this criterion is closer to done but still not fully there on the target side.
Bootstrap still had a couple of leftover protocol-string checks outside the main entry path. I’m removing those stragglers so this capability step is actually consistent instead of just renaming one call site.
The capability step is clean: bootstrap no longer reaches into raw protocol/feature strings, and gitproto owns the source-side batching eligibility check. I’m committing that separately so the history keeps a readable split between boundary extraction and capability centralization.
There’s one unrelated unstaged change in internal/syncer/integration_test.go, so I’m keeping that out of this commit. The index.lock race showed up again, so I’m confirming whether it’s real or stale before retrying the targeted commit.
The review on the previous commit found a real bug in the new push-boundary work: batched bootstrap can panic on a missing TargetPusher, and the one-shot path checks too late. I’m folding that fix into the next step before I finalize the pending capability commit, rather than leaving a broken seam in history.
I’m fixing the panic path at the strategy boundary, not just papering over it in docs. The bootstrap strategy will now reject a missing target executor before any fetch work, and I’m adding coverage for both one-shot and batched entry points so that bug can’t sneak back in.
That step is committed as 07ddaa2. I’m handing just that delta to a reviewer now, while I check what’s still genuinely open in the tracker versus what is now documentation lag.
The next cleanup is the asymmetry the reviewer pointed out in incremental relay selection. I’m pushing the tag-only relay decision through the same injected policy seam as the branch fast-forward decision so the strategy boundary is consistent rather than half hard-coded.
I’ve pushed the second relay decision through the same strategy seam. Now I’m verifying that incremental relay selection is fully injected and that the tests fail if we regress back to a half-hard-coded policy split.
The reviewer found one more real gap: bootstrap could still hit GitHub preflight before rejecting a missing target executor. I’m moving that guard ahead of any network preflight and adding a regression test that would fail if Execute makes that metadata call again.
I fixed the reviewer-found bootstrap preflight gap and also finished the incremental policy boundary so both relay decisions are injected consistently. The tree is green again; I’m checking the exact diff before I split or bundle the next commit.
I’ve got another coherent step now: bootstrap fails before any preflight when its target executor is missing, and incremental relay no longer mixes injected and hard-coded policy. I’m committing that as one follow-up hardening pass, then I’ll send it out for review before choosing whether any of the remaining tracker partials are worth addressing in code versus explicitly leaving as known limits.
I’m at the point where the remaining tracker items are the expensive ones: batch sizing heuristics, non-relay large-repo support, and some stream-lifecycle exhaustiveness. I’m running the branch-standard race pass on the current head while the latest review comes back, then I’ll decide whether there’s another genuinely worthwhile code step or whether we should stop at documented known limits instead of faking “done.”
The latest review only found one code-level issue worth fixing: incremental relay now demanded a tag-relay policy even when it was taking the branch fast-path. I’m relaxing that so the dependency surface matches the path actually used, and I’ll leave the remaining review notes as test-quality/diagnostic tradeoffs rather than churn the code for marginal gain.
The latest tiny incremental fix exposed a real regression in a syncer integration test, so I’m stopping the commit flow and checking that directly. I need to determine whether the bootstrap boundary changes altered the delete-failure path or whether the test was order-sensitive and only surfaced under the full package run.
The failure appears intermittent rather than deterministic; the focused test passes immediately. I’m rerunning the full suite once before touching code, because this looks more like order or global-state sensitivity than a real regression from the incremental-policy fix.
There was one more uncommitted chunk sitting in the tree: the delete-failure retry integration coverage and the receive-pack test hook it needs. That’s ours, it passes, and it’s worth keeping, so I’m bundling it with the incremental policy fix and rerunning the relevant race slice before committing.
The delete-retry coverage step is committed as bf3b784, and the touched packages pass their race run. I’m sending that delta to another reviewer now; after that I’m likely done with code changes unless the review turns up a real bug rather than another “could be cleaner” note.
<subagent_notification>
{"agent_path":"019d7e48-96e2-77a0-b4ce-607c7aba4b96","status":{"completed":"The current incremental pass is at a clean stopping point. The branch is clean, the tracker is updated in docs/rewrite-issue-list.md, and the latest rewrite work landed as these commits:\n\n- 689eadd``Route strategies through target push abstractions\n- 07ddaa2``Centralize bootstrap capability checks\n- 7b5e963``Cover batched bootstrap cleanup reruns\n- eb766e1``Harden relay strategy boundaries\n- bf3b784``Cover batched delete retry recovery\n\nWhat changed in this pass:\n- Strategy packages now consistently depend on narrower source and target boundaries instead of raw transport state, centered around internal/gitproto/push.go and the strategy params in internal/strategy/bootstrap/bootstrap.go, internal/strategy/incremental/incremental.go, and internal/strategy/materialized/materialized.go.\n- Bootstrap capability checks were pushed further toward centralized handling.\n- Relay strategy tests now cover failure boundaries more directly, including source-pack closure on push failure.\n- Batched bootstrap integration coverage now includes:\n - cleanup rerun when target ref already exists and temp ref remains\n - injected temp-ref delete failure with successful recovery on retry\n in internal/syncer/integration_test.go.\n\nVerification run:\n- go test ./...\n- go test -race ./...\n- repeated targeted go test -race ./internal/syncer\n\nWhat is still genuinely open from the rewrite tracker:\n- deeper batched pack-stream failure auditing\n- broader ambient-state reduction in some helpers\n- eliminating network probe sizing as the main batching mechanism\n- redesigning the materialized fallback beyond in-memory limits\n- fully owning protocol/transport capability handling end-to-end\n\nThat’s the point where the remaining work is larger redesign work again, not another cheap cleanup pass."}}
</subagent_notification>
3mo ago·11.2s
The current pass is at a clean stopping point.
New commits:
689eadd``Route strategies through target push abstractions07ddaa2``Centralize bootstrap capability checks7b5e963``Cover batched bootstrap cleanup rerunseb766e1``Harden relay strategy boundariesbf3b784``Cover batched delete retry recovery
What changed:
- Strategy packages now depend on narrower source/target execution seams instead of raw transport wiring.
- Bootstrap capability checks moved further into
gitproto. - Incremental relay policy injection is consistent across both branch and tag relay paths.
- Bootstrap now fails earlier on missing target executors, and the boundary tests were tightened.
- Integration coverage now includes batched cleanup reruns and injected temp-ref delete failure with successful recovery on retry.
Tracker state is updated in docs/rewrite-issue-list.md, and the branch is clean.
Verification:
go test ./...go test -race ./...- targeted
go test -race ./internal/syncer
What’s still genuinely open is the larger rewrite work, not small cleanup:
- deeper batched pack-stream failure auditing
- broader ambient-state reduction in some helpers
- replacing network probe sizing as the main batching mechanism
- redesigning the materialized fallback beyond in-memory limits
- fully owning protocol/transport capability handling end-to-end
That’s a reasonable place to stop unless you want to deliberately start the next larger redesign item.
I switched to go-git v6 (we are maintainers of it, so it's fine that it's alpha, we know what we do) can you review again and then let me know what you would pick next
3mo ago·10.8s·27 calls
I’m reviewing the branch after the go-git v6 switch, starting with dependency changes, build/test state, and the code paths most exposed to transport/protocol differences. Then I’ll turn that into a short findings-first review and a recommendation for the next rewrite step.
I’ve confirmed the branch is clean and the migration landed as a single top commit. Next I’m checking whether the v6 switch was a straight import upgrade or whether any transport/protocol semantics changed in code and test behavior.
The v6 migration is more than an import rewrite: v1 fetch/push now bypass go-git transport entirely and use direct smart-HTTP requests. I’m reviewing those new code paths first because that’s where semantic regressions are most likely.
I’ve got the core shape now: tests pass, but the migration changed real semantics in the v1 relay/push paths. I’m pinning exact references for the issues that matter before I summarize what I’d do next.
I’ve found two concrete regressions already: the new push path buffers whole requests in memory, and the new HTTP error helper reintroduces unbounded reads on error bodies. I’m running a race pass as a sanity check, but the review itself is already pretty clear.
Findings:
High: the v6 push path no longer streams relay packs; it buffers the entire request body in memory before sending. In internal/gitproto/push.go,
sendReceivePackencodes the update request into abytes.Bufferand thenio.Copys the full pack into that same buffer before POSTing. That breaks the core source-to-target streaming property for relay pushes, doubles memory pressure for materialized pushes, and delays cancellation until buffering finishes. This is the biggest regression introduced by the migration.Medium: the new HTTP error helper reintroduces an unbounded read on error bodies. In internal/gitproto/smarthttp.go,
httpErrordrainsres.Bodyinto abytes.Bufferwith no limit. That undoes the earlier bounded-read hardening for a malicious or just noisy server returning large non-2xx bodies.
What I checked:
go test ./...go test -race ./...
Both pass.
What I would pick next:
Restore true streaming for receive-pack first. That is the highest-value next step by a large margin. The rewrite’s core architectural win is relay streaming, and the v6 migration currently gives that up on push. I would:
- change
sendReceivePackto stream request headers + pack through anio.Pipeinstead ofbytes.Buffer - keep
PushCommandson the buffered no-pack path - add a regression test that proves large relay packs are not fully buffered before the POST starts
- while in there, bound
httpErrorbody reads
After that, I’d revisit batch sizing/probe reduction.
ok, let's do that, do meaningful commits when necessary
3mo ago·16.9s·32 calls
I’m fixing the v6 regression in the highest-leverage place first: receive-pack request streaming. The goal is to restore true streaming for relay pushes and bound error-body reads while keeping the change isolated enough for a clean commit and review.
I’m editing the transport helpers and push path now. The shape is: add a streaming POST helper, switch receive-pack to header-plus-pack streaming, stream locally encoded packs through a pipe, and add a regression test that would have hung on the buffered implementation.
The transport and push changes are in; now I’m fixing the new tests and validating the streaming behavior directly in internal/gitproto before I touch the wider suite.
internal/gitproto is green with the new regression coverage. I’m widening to the full suite next; if that stays clean, this is a good single commit and I’ll send it through review before deciding whether to take a second follow-up step.
The full suite passes. I’m running the race pass for the touched packages now, then I’ll commit this as the v6 streaming fix rather than mixing it with anything else.
This is a single coherent fix: restore streaming in receive-pack and rebounded error bodies. I’m letting the race run finish, then I’ll commit just these four files and hand that commit to a reviewer.
That fix is committed as 0ed62c5. I’m sending it for review now; if it comes back clean, I’ll stop there unless there’s another small, adjacent regression worth taking while this context is still hot.
I’m checking whether there’s any remaining uncommitted fallout. If the tree is clean and the review doesn’t surface a bug, I’d treat this as the necessary v6 follow-up and stop rather than forcing a second commit for the sake of motion.
Review after the v6 switch:
Findings:
- High: the v6 migration had regressed
receive-packinto a fully buffered path. That is now fixed in0ed62c5by streaming request headers plus pack data instead of assembling the whole body in memory first. See internal/gitproto/push.go. - Medium: the new HTTP error helper had reintroduced unbounded reads on error bodies. That is now bounded as part of the same fix in internal/gitproto/smarthttp.go.
What changed now:
- committed
0ed62c5``Restore streaming receive-pack pushes - added regression coverage proving a push request starts before the full pack is released in internal/gitproto/push_test.go
- added bounded error-body coverage in internal/gitproto/smarthttp_test.go
Verification:
go test ./...go test -race ./internal/gitproto ./internal/syncer
What I would pick next: I would now go after v1 fetch/push streaming symmetry and request-body construction more deliberately. The v6 migration already removed the old go-git session layer, so the next high-value step is to make the direct smart-HTTP path clearly first-class:
- keep
receive-packstreaming as-is - audit
upload-packv1 request/response handling for the same streaming and lifecycle guarantees - add one or two integration-style protocol tests around large v1 fetch/push bodies and early cancellation
If you want the most impactful next code task, I’d take “streaming/cancellation audit for v1 smart-HTTP paths” next.
ok, let's do that
3mo ago·11.4s·24 calls
I’m taking the v1 smart-HTTP paths next, with two goals: verify stream ownership/cancellation behavior end-to-end, and tighten any remaining asymmetry with the new v6 direct HTTP code. I’ll start by reading the v1 fetch/push code and the existing tests, then patch the weakest boundary and add protocol-level regression coverage.
The next pass is mostly about locking down the new direct protocol paths with better tests, not another large code rewrite. I’m adding focused v1 fetch cancellation and response-lifecycle coverage so the smart-HTTP layer has the same kind of regressions pinned down that receive-pack now has.
I’m keeping this pass tight: pin the v1 upload-pack boundaries with direct tests, and only change code if those tests expose a real lifecycle bug. The two cases I care about are cancellation propagation and response-body closure on malformed v1 responses.
The v1 tests are in. I’m running just internal/gitproto first to catch any cleanup issues in the new protocol coverage before I widen further.
The new v1 tests were fine structurally; I just missed an errors import. I’m fixing that and rerunning the package.
internal/gitproto is green with the new v1 coverage. I’m widening to the full suite now; if nothing else moves, this is likely a second clean commit focused on v1 protocol-path hardening.
This follow-up is test-only, but it closes a real blind spot in the new direct v1 path. I’m doing the race pass on the touched package and then I’ll commit it separately instead of folding it into the streaming fix.
The v1 hardening tests are clean under race. I’m committing that as its own protocol-coverage step, then I’ll hand it to a reviewer and stop unless they catch something substantive.
I’m checking that the tree is clean and then I’ll wait briefly on the review. Unless it surfaces a real bug, this is a good stopping point for the v6 follow-up work: one behavior fix, one coverage-hardening commit.
Two meaningful follow-up steps landed after the v6 migration:
0ed62c5``Restore streaming receive-pack pushese2a5ee2``Harden v1 upload-pack path coverage
What changed:
receive-packis streaming again instead of buffering the whole request body in memory first, via internal/gitproto/push.go.- HTTP error-body reads are bounded again in internal/gitproto/smarthttp.go.
- Added regression coverage for:
- push requests starting before the full pack is released
- bounded error-body reads
- v1
upload-packcancellation propagation - v1 malformed-response body closure
- empty-want handling on the v1 request builder in internal/gitproto/push_test.go, internal/gitproto/smarthttp_test.go, and internal/gitproto/fetch_test.go.
Verification:
go test ./...go test -race ./internal/gitproto ./internal/syncergo test -race ./internal/gitproto
The tree is clean.
If I were to pick the next step from here, I’d go after the remaining batching/probe cost issue next: reduce or replace fetch-and-discard sizing probes in bootstrap planning. That’s the biggest remaining runtime inefficiency now that the v6 transport regression is fixed.
let's do this then
3mo ago·8.7s·33 calls
I’m taking the bootstrap probe cost next. First I’m tracing the current checkpoint planning path and where it still fetches throwaway packs, then I’ll make the smallest change that materially cuts network work without destabilizing batching semantics.
I’m taking a bounded improvement rather than a speculative redesign: make checkpoint planning adapt its next probe span from the last successful measured pack size. That should cut repeated undersized probes on long branches without changing batching correctness.
I’m editing the checkpoint planner now. The change is narrow: derive the next probe span from the actual bytes of the last successful probe so long histories don’t keep inching forward with obviously undersized checkpoints.
The adaptive-span logic is in. I’m validating the bootstrap package first, then the full suite, because this touches planning behavior rather than transport wiring.
The bootstrap and syncer packages are green with the adaptive probing change. I’m widening to the full suite and then I’ll decide whether this stands as its own commit or wants one more adjacent test update.
The full suite passes. I’m doing the race pass on the touched packages and then I’ll commit this as the bootstrap probe-tuning step.
This is the right size for a commit: one planning heuristic adjustment, one focused unit-test block. Once the race run clears, I’ll commit it and send it out for review.
The probe-tuning step is committed as 3d8ba82. I’m handing it to a reviewer now; if it comes back clean, I’d stop here rather than stack more heuristics into the planner in one turn.
I’m checking the tree and waiting briefly on the review. Unless it exposes a real bug, this is a good stopping point for the batching cost work in this pass.
One more meaningful step landed:
3d8ba82``Tune bootstrap checkpoint probe spans
What changed:
- bootstrap batch planning now uses the actual byte size of the last successful probe to adapt the next projected span in internal/strategy/bootstrap/bootstrap.go
- that keeps the planner from repeatedly making obviously undersized probes on long histories after a much-smaller-than-limit checkpoint succeeds
- added direct unit coverage for the span adaptation logic in internal/strategy/bootstrap/bootstrap_test.go
Verification:
go test ./...go test -race ./internal/strategy/bootstrap ./internal/syncer
The tree is clean.
At this point, the remaining batching improvement would be a bigger jump: replacing probe-based sizing more fundamentally rather than tuning the current sampler. That likely means either:
- a more explicit pack-size estimation model from prior probes and commit-graph shape, or
- protocol/server support for a cheaper sizing preflight
So this is a reasonable stop point for the current pass.
- protocol/server support for a cheaper sizing preflight
is this a thing? or do you mean proposing a change to the protocol/server?
3mo ago·10.8s
I mean proposing or adding something outside current standard Git protocol behavior, not something generally available today.
As far as normal Git smart HTTP goes, there is no standard “tell me roughly how big this pack would be” preflight RPC that you can rely on. Today you mostly have:
- actually fetch the pack
- use heuristics from history shape / prior probes
- maybe use host-specific metadata if a server exposes something custom
So if you want a truly cheaper sizing preflight, that would mean one of:
- proposing a protocol extension
- adding a server-specific endpoint or capability
- teaching your own server/backend to expose pack estimates
For this project, I would not block on that. I’d first exhaust client-side improvements:
- better adaptive estimation from previous successful probes
- caching probe results more aggressively
- reducing how often we need exact pack-size checks at all
If you want, I can sketch what a plausible Git protocol extension for “pack estimate” would look like, but it would be custom territory, not something you can assume from existing servers.
let's focus on: I’d first exhaust client-side improvements:
3mo ago·1m·30 calls
I’m making one more bounded improvement in the same area: if the last successful probe suggests the entire remaining first-parent tail should fit comfortably, try the tip directly before doing intermediate checkpoint search. If that optimistic probe fails, the planner falls back to the existing sampled search, so correctness stays exact.
I caught one more planner improvement while still in the file: use the last measured bytes-per-span to opportunistically probe the tip first when the remainder looks comfortably under budget. I’m fixing up the implementation and validating the bootstrap package before deciding whether this should amend the just-made batching commit or stand as a follow-up.
The optimistic tip-probe refinement is working in package tests now. I’m running the broader suite before deciding whether to keep it as a follow-up commit or fold it into the just-made batching heuristic commit.
This one is worth keeping as a separate follow-up: after the adaptive span tuning, it adds a new planner behavior rather than just correcting the prior commit. I’m doing the race pass on the touched packages and then I’ll commit it as its own optimization step.
The second batching optimization is committed as 3f2d1cb. I’m sending it through review now; if it comes back clean, I’d stop this pass there because the remaining batching work is getting into deeper model changes rather than incremental heuristics.
I’m checking that the tree is clean and waiting briefly on the review. Unless it exposes a real correctness bug, this is the right place to stop the current client-side batching pass.
Two more batching-focused steps landed:
3d8ba82``Tune bootstrap checkpoint probe spans3f2d1cb``Probe bootstrap tip optimistically
What changed:
- checkpoint planning now uses the actual byte size of the last successful probe to choose a better next span in internal/strategy/bootstrap/bootstrap.go
- if the last measured pack suggests the entire remaining tail should fit comfortably, planning now probes the tip directly before doing intermediate checkpoint search
- added direct unit coverage for both heuristics in internal/strategy/bootstrap/bootstrap_test.go
Verification:
go test ./...go test -race ./internal/strategy/bootstrap ./internal/syncer
The tree is clean.
This is a reasonable stop point for the client-side batching pass. The remaining improvements are still possible, but they’re no longer cheap heuristics:
- richer pack-size estimation from more than one prior measurement
- explicit planner metrics/tests for probe-count reduction
- more structural alternatives to probe-based sizing rather than better probing
If you want the next step, I’d pick probe-count visibility next: add instrumentation/tests that assert how many source FetchPack probes planning performs on representative histories, so future tuning is driven by measured deltas rather than intuition.
let's do If you want the next step, I’d pick probe-count visibility next: add instrumentation/ tests that assert how many source FetchPack probes planning performs on representative histories, so future tuning is driven by measured deltas rather than intuition.
I think kubernetes/kubernetes is a good sized repo ?
3mo ago·20.4s·35 calls
I’m going to make probe counts visible in tests, not by dragging in a huge external repo fixture. kubernetes/kubernetes is a good manual benchmark target, but it’s too heavy and unstable for CI. I’ll build deterministic bootstrap-planning tests with a fake source service that counts FetchPack probes against synthetic histories, then we can use k/k later as an external reality check.
I’m adding probe-count visibility in tests now. The plan is a deterministic bootstrap-planning test with a fake source that returns synthetic pack sizes per checkpoint, so we can assert relative probe counts without relying on a giant external repo fixture.
I’m wiring a deterministic fake source for checkpoint planning tests: fixed linear commit chains, fake pack sizes keyed by commit, and a fetch counter. That will let us assert actual probe counts for “shortcut to tip” versus “multi-checkpoint” histories without any external repo fixture.
I’m patching the bootstrap tests in smaller chunks. The goal hasn’t changed: deterministic probe-count tests with a fake source and synthetic commit chains.
The probe-count visibility tests are in. I’m running the bootstrap package first because these tests exercise the planner heuristics directly and I want tight feedback before widening out.
The first cut used pack sizes keyed only by the target commit, which isn’t realistic enough for batching. I’m switching the fake source to size by span from have to want, because that matches what the planner actually cares about and gives stable probe-count tests.
The probe-count tests are now modeling pack size by delta span, which is much closer to the real planning problem. I’m rerunning the bootstrap package to lock the expected counts to the current heuristics.
The visibility tests are now stable in the bootstrap package. I’m widening to the full suite, and if that stays green I’ll commit this as a pure observability/guardrail step.
This is a clean, test-only guardrail step. I’m doing the race pass on the touched package and then I’ll commit it as the probe-count visibility work you asked for.
The probe-count visibility tests pass under race. I’m committing them as a standalone test/observability step, then I’ll hand that commit to a reviewer and stop unless it surfaces a real issue.
I’m checking the tree and waiting briefly on the review. This should be the stop point for the current batching pass: two heuristic changes plus tests that make future tuning measurable.
The batching pass is now in a good place.
New commits:
3d8ba82``Tune bootstrap checkpoint probe spans3f2d1cb``Probe bootstrap tip optimisticallybe94300``Add bootstrap probe count visibility tests
What changed:
- checkpoint planning now adapts the next span from the last successful measured probe size in internal/strategy/bootstrap/bootstrap.go
- when the remaining tail looks comfortably under budget, planning probes the tip directly before intermediate search in the same file
- added deterministic probe-count visibility tests in internal/strategy/bootstrap/bootstrap_test.go
- those tests use synthetic linear commit chains plus span-based fake pack sizing, so future tuning can be measured against exact
FetchPackprobe counts instead of intuition
Verification:
go test ./...go test -race ./internal/strategy/bootstrap ./internal/syncergo test -race ./internal/strategy/bootstrap
The tree is clean.
On the kubernetes/kubernetes question: it’s still a good manual benchmark target, but not for automated tests. With the new visibility tests in place, the next sensible step would be a manual benchmark pass against one or two large real repos like kubernetes/kubernetes and maybe elastic/elasticsearch or tensorflow/tensorflow, to see whether the heuristics improved actual probe counts and wall-clock behavior the way the synthetic tests suggest.
can you give me the commands?
3mo ago·14.4s
Yes. For a manual benchmark against kubernetes/kubernetes, I’d use a bare mirror locally and compare current main vs your rewrite branch.
Clone a local mirror once:
1
2
3
mkdir -p /tmp/git-sync-bench
cd /tmp/git-sync-bench
git clone --mirror https://github.com/kubernetes/kubernetes.git kubernetes.git
Build git-sync from each branch:
1
2
3
4
5
6
7
cd /Users/soph/Work/entire/devenv/git-sync
git worktree add /tmp/git-sync-main main
git worktree add /tmp/git-sync-rewrite soph/rewrite
(cd /tmp/git-sync-main && go build -o /tmp/git-sync-main.bin ./cmd/git-sync)
(cd /tmp/git-sync-rewrite && go build -o /tmp/git-sync-rewrite.bin ./cmd/git-sync)
Create fresh empty targets:
1
2
3
rm -rf /tmp/git-sync-target-main.git /tmp/git-sync-target-rewrite.git
git init --bare /tmp/git-sync-target-main.git
git init --bare /tmp/git-sync-target-rewrite.git
Run bootstrap on main:
1
2
3
4
5
/usr/bin/time -l /tmp/git-sync-main.bin bootstrap \
--source file:///tmp/git-sync-bench/kubernetes.git \
--target file:///tmp/git-sync-target-main.git \
--batch-max-pack-bytes 104857600 \
--json | tee /tmp/git-sync-main-bootstrap.json
Run bootstrap on rewrite:
1
2
3
4
5
/usr/bin/time -l /tmp/git-sync-rewrite.bin bootstrap \
--source file:///tmp/git-sync-bench/kubernetes.git \
--target file:///tmp/git-sync-target-rewrite.git \
--batch-max-pack-bytes 104857600 \
--json | tee /tmp/git-sync-rewrite-bootstrap.json
If you want to compare just checkpoint planning/probe behavior without finishing a whole migration, use Probe/planning-oriented runs with a smaller target and watch logs:
1
2
3
4
5
/tmp/git-sync-rewrite.bin bootstrap \
--source file:///tmp/git-sync-bench/kubernetes.git \
--target file:///tmp/git-sync-target-rewrite.git \
--batch-max-pack-bytes 104857600 \
--verbose
Useful follow-up comparisons:
1
2
jq '.planned_batch_count, .batch_count, .stats' /tmp/git-sync-main-bootstrap.json
jq '.planned_batch_count, .batch_count, .stats' /tmp/git-sync-rewrite-bootstrap.json
And verify end state matches:
1
2
git --git-dir=/tmp/git-sync-target-main.git show-ref | wc -l
git --git-dir=/tmp/git-sync-target-rewrite.git show-ref | wc -l
If you want, I can also give you:
- a single copy-paste shell script
- a version that benchmarks multiple repos
- a version that repeats runs and summarizes timings
could/should we put this into some test scripts (or go code) that can be run repeadetly?
3mo ago·7.8s
Yes, but not as a normal CI test.
What I’d do:
- put it in a repeatable benchmark script under
scripts/ - optionally add a small Go benchmark driver under
cmd/ortools/later if you want structured output - do not make it part of
go test ./...because cloning/fetching giant repos is too slow, flaky, and environment-dependent
Best shape:
scripts/bench-large-repo.sh- inputs:
- repo URL or local bare path
- branch/binary under test
- batch size
- repeat count
- outputs:
- elapsed time
- peak memory from
/usr/bin/time -l - resulting JSON from
git-sync - maybe extracted
planned_batch_count,batch_count, and stats summary
If you want code instead of shell:
- a small Go harness is better only if you want:
- repeated runs with aggregated stats
- machine-readable comparison output
- easier extension to multiple repos / multiple binaries
- otherwise shell is faster and more maintainable here
My recommendation:
- start with
scripts/bench-large-repo.sh - add
scripts/bench-compare.shformainvsrewrite - only build a Go harness if you find yourself wanting result parsing, multiple scenarios, or historical tracking
If you want, I can add those scripts now.
yeah, but I'd suggest instead of bash let's use go?
3mo ago·7.8s·5 calls
I’m adding a small Go benchmark harness instead of shell. First I’m checking the repo layout for where helper tools live, then I’ll scaffold a repeatable main vs rewrite runner that builds binaries, prepares bare targets, executes bootstrap runs, and writes machine-readable results.
I’m putting this under cmd/ as a separate tool so it’s easy to run repeatedly and doesn’t pollute the main CLI. I’m reading the existing command style and then I’ll add a focused benchmark runner with explicit flags instead of inventing repo-local shell glue.
<turn_aborted> The user interrupted the previous turn on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed; verify current state before retrying. </turn_aborted>
3mo ago
I'm fine separating this more, maybe we have a validation package or e2e test package or if you have a better name, so it's a clear separation?
3mo ago·9.7s·117 calls
I’m going to inspect the current package layout and the obvious remaining seams, then I’ll split out the highest-signal separation rather than inventing a package without a clear boundary. After that I’ll verify, commit, and run a review pass on the step.
The layout is already split reasonably, but validation is still partly buried in syncer, and the heavier integration tests are still living in that package. I’m checking those seams now to see which split is actually clean instead of just cosmetic.
The cleanest separation is a validation boundary, not an e2e package yet. syncer is still responsible for protocol/config validation and low-level “have ref” parsing, while the integration tests are already effectively package-scoped system tests.
syncer still owns a lot of end-to-end fixture machinery. I’m going to split the config/protocol validation first, because that reduces real coupling in production code and makes further package cleanup easier; after that I’ll see whether moving test helpers into a dedicated internal test package is worth the churn.
I’ve got the seam: syncer is still doing config validation and input normalization that isn’t orchestration. I’m extracting that into a dedicated internal/validation package and wiring both syncer and the CLI through it, so the split is structural rather than just moving tests around.
I’m making the split now: a dedicated internal/validation package for protocol-mode normalization and user-input parsing, then I’ll run the full test pass and commit it as one structural step.
The validation split is in place. I’m formatting and running the test suite now; if it holds, I’ll commit this as the first structural step and then do a review pass before deciding whether a dedicated integration-test package is worth the next move.
The split compiles cleanly so far; the only slow part left is internal/syncer, which is expected because that package still owns the broad integration surface. Once that finishes I’ll commit and run a targeted review pass on the change.
go test ./... finished cleanly. The -race run stopped producing output after the smaller packages, so I’m going to rerun the slow package directly instead of guessing whether the aggregated run hung or just stalled in the harness.
The validation split held up under go test ./... and go test -race ./internal/syncer. I’m committing this as a real structural step now, then I’ll run a focused review pass on the commit before deciding whether the next useful separation is test-only or production code.
The production-code separation is now better. The next plausible split is test infrastructure: internal/syncer still carries a lot of reusable repo/server fixture code alongside the integration tests, so I’m checking whether that can become a dedicated internal test support package without creating a circular mess.
I’m taking the next smaller test-structure step, not the full smart-HTTP server extraction yet. The reusable in-memory repo fixtures are moving into a dedicated internal/syncertest package first; that gives a clear testing boundary without exploding the churn from the server harness in the same step.
The first pass at the test-fixture split hit a context mismatch in benchmark_test.go, so I’m checking the current file contents and then I’ll patch the test package in smaller, safer slices rather than force a big move through one diff.
I’m reducing the scope of the test split so it stays clean: extracting the generic in-memory repo fixture layer for benchmarks first, rather than trying to move the entire smart-HTTP harness in one step. That still creates a dedicated testing package and removes some of the duplicated repo-building mechanics.
The test-structure step is narrower now: benchmark repo fixtures live in internal/syncertest, while the bigger smart-HTTP integration harness stays put for now. I’m formatting and verifying this slice before deciding whether the next extraction is worth the churn.
This slice is clean under normal and race tests for internal/syncer. I’m committing it as a smaller test-structure step now, then I’ll get a review on whether this is the right boundary to continue or whether the remaining smart-HTTP harness is better left in place.
The commit hit a stale .git/index.lock, so I’m checking whether there’s an active git process or just leftover lock state before I touch it. If it’s stale, I’ll clear it and commit the step.
At this point the code has two clearer seams than before: production validation and benchmark fixture support. I’m pulling in the review passes on those commits now, then I’ll decide whether the remaining integration harness should actually move or whether that would just create package churn without a better design.
The review is right: the new validation package is still coupled to planner because RefMapping stayed in planner. I’m fixing that by making the mapping type live with validation, so the boundary becomes real instead of just a helper extraction.
I addressed the review point by moving the mapping type to internal/validation and leaving only a compatibility alias in planner. That makes the dependency direction sane: planner can depend on validation, but validation no longer depends on planner.
The validation boundary is now materially better than in the first pass: RefMapping lives with validation, and planner only keeps a compatibility alias. I’m committing that cleanup as its own step, then I’ll fold the review feedback into my recommendation on whether to keep separating tests further.
The stale lock issue came back during commit, so I’m checking the repository state again and then retrying the commit once the lock clears.
The benchmark review is fair. I’m tightening that up in two ways: restore the original benchmark commit shape so benchmark history stays comparable, and move the integration repo helpers onto syncertest so there’s one actual implementation instead of two diverging copies.
The review concerns are addressed in code now: benchmark commits use their original lightweight shape again, and the integration repo helpers are delegated to syncertest so there is one real implementation instead of two drifting copies. I’m formatting and verifying that before I commit the cleanup.
The cleanup is fine; it just left one stale import behind in integration_test.go. I’m removing that and rerunning the same verification.
This pass fixed the benchmark-regression concern and made syncertest more coherent by turning the old integration helpers into thin delegates. I’m committing that cleanup as its own step now.
The same transient lock happened again on commit. I’m retrying once the lock disappears rather than forcing anything, since the working tree itself is fine.
There’s one small follow-up from the latest review: a stale comment still says “planner mapping” even though ownership moved. I’m fixing that wording so the package boundary reads the way the code now works.
I separated it in the direction that actually reduced coupling.
New commits:
ddd4c7c``Extract validation package9e0a8b5``Extract benchmark repo fixtures353954d``Move ref mappings into validationc567560``Unify repo test fixturesebd2cf7``Clarify validation mapping ownership
What changed:
- Added
internal/validationfor protocol-mode normalization and CLI input parsing. - Moved
RefMappingownership intointernal/validation, sovalidationno longer depends onplanner. - Added
internal/syncertestfor shared in-memory repo fixtures. - Unified the benchmark and integration repo-fixture logic onto
syncertest. - Preserved benchmark comparability by keeping a separate lightweight
MakeBenchmarkCommitspath.
Verification:
go test ./...go test -race ./internal/syncer ./internal/plannergo test -race ./internal/syncer
What I’d do next if we keep separating:
- Move semantic mapping validation (
ValidateMappings/NormalizeMapping) out ofplannerand intovalidation, or renamevalidationto something likeinputif you want parsing + normalization there and planner-only semantics in planner. - I would not extract the smart-HTTP integration harness yet unless we want a real reusable e2e support package across multiple packages. Right now that move is possible, but it’s more churn than benefit.
So the short version is: validation was the right split, syncertest is now a reasonable test-support package, and I’d stop here unless you want to finish the mapping-validation ownership move completely.
can I run the script now?
3mo ago·6.2s
Not yet. There’s no new benchmark script or Go harness in the branch right now.
What changed was package structure:
internal/validationinternal/syncertest
If you want, I can add the repeatable Go benchmark tool next, likely as something like cmd/git-sync-bench, so you can run it directly.
What I’d do next if we keep separating:
- Move semantic mapping validation (ValidateMappings / NormalizeMapping) out of planner and into validation, or rename validation to something like input if you want parsing + normalization there and planner-only semantics in planner.
- I would not extract the smart-HTTP integration harness yet unless we want a real reusable e2e support package across multiple packages. Right now that move is possible, but it’s more churn than benefit.
so do this first or add the go test tool first, your call
3mo ago·8.1s·20 calls
I’m taking the semantic validation move first. It’s the smaller, cleaner step on the current path, and it finishes the ownership boundary we already started instead of mixing that with a new benchmark tool.
I’ve got the dependency constraint: validation can’t return planner-specific kinds without reintroducing a cycle. I’m moving normalization there with a planner-agnostic result shape, then wiring planner to derive kind from the normalized target ref instead of owning the validation API.
I’m moving the actual mapping normalization API now and updating tests to follow the new owner. After that planner will only consume normalized mappings, not define or validate them.
The ownership move is in place. I deleted the planner-side mapping validation and made planner consume normalized refs from validation, which is the cleaner dependency direction we wanted.
The targeted packages are clean so far; go test ./... just has the usual slow tail left in internal/syncer. Once that lands I’ll commit this as the completed validation-ownership move and then we can add the Go benchmark tool on top of a cleaner package layout.
The semantic validation move is complete and verified. I’m committing that now, then I’ll run a review pass on the new boundary before recommending the next step.
The same transient git lock showed up again during commit. I’m retrying once it clears; the index itself is fine.
I did the semantic validation move first.
New commit:
f87ea42``Move mapping validation into validation
What changed:
NormalizeMappingandValidateMappingsnow live ininternal/validation.plannernow consumes normalized refs fromvalidationinstead of owning mapping validation.internal/planner/mapping.gowas removed.plannerderives ref kind from the normalized target ref, which is safe because validation already rejects mixed/unsupported ref kinds.
Verification:
go test ./...go test -race ./internal/syncer ./internal/planner ./internal/validation
This was the right order. The boundary is cleaner now, so the next step should be the Go benchmark tool.
What I’d pick next:
- add a repeatable Go harness, likely
cmd/git-sync-bench - support running bootstrap benchmarks repeatedly against a local bare mirror
- emit JSON summaries so you can compare
mainvsrewriteover time
If you want, I’ll build that next.
I think I will just replace main with this rewrite, main isn't used, so I'm less concerned about benchmark comparisions but more of having a full validationt and base line for any optimizations going forward
3mo ago·7.4s
Then this direction makes sense.
If main is disposable, I would optimize for:
- a clean package boundary
- a repeatable benchmark harness
- a stable optimization baseline on the rewrite itself
That means I would not spend more time preserving old benchmark semantics just for historical comparability. I’d treat the rewrite as the new baseline and make the benchmark tool reflect the workloads you actually care about now:
- empty-target bootstrap
- incremental fast-forward sync
- materialized fallback
- batched bootstrap on large histories
So the next step should be the Go benchmark tool. It gives you:
- repeatable runs
- structured output
- a baseline to improve from
- a place to add real-repo scenarios later
I’d build cmd/git-sync-bench next unless you want a different name.
yes
3mo ago·10.2s·15 calls
I’m adding the benchmark harness now as a separate command. First I’m checking the existing CLI/result shapes so the tool can reuse them cleanly instead of shelling out blindly.
I’ve got the scope: a first-class Go benchmark command for repeatable empty-target runs, with bootstrap and sync scenarios, repeated targets, JSON output, and aggregated timings/memory. I’m implementing the command and a small test for aggregation so it’s not just an ad hoc helper.
The tool is in. I’m formatting, building, and running the full test pass now; if that holds, I’ll commit it as the new repeatable benchmark baseline and get a review on the command shape.
The benchmark command is clean under normal and race tests. I’m committing it now as the new repeatable baseline tool, then I’ll run a focused review on whether the command surface is right or if it should immediately grow more scenarios.
The transient git lock showed up again on commit. I’m retrying once it clears, same as the earlier steps.
Yes. You can run it now.
New commit:
342fb90``Add repeatable benchmark command
What’s added:
Verification:
go test ./...go test -race ./cmd/git-sync-bench ./internal/syncer ./internal/validation
Build and run:
1
2
3
4
5
6
7
8
9
go build -o /tmp/git-sync-bench ./cmd/git-sync-bench
/tmp/git-sync-bench \
--scenario bootstrap \
--source-url /tmp/git-sync-bench/kubernetes.git \
--repeat 3 \
--batch-max-pack-bytes 104857600 \
--stats \
--json
It currently supports repeatable empty-target runs for:
--scenario bootstrap--scenario sync
If you want, next I’d add a prepared-target scenario so you can benchmark incremental relay and materialized fallback with the same tool.
can you explain what it does how it works?
3mo ago·13.2s
git-sync-bench is a repeatable benchmark driver around the rewrite, not a shell wrapper.
What it does:
- takes a source repo URL or local path
- creates a fresh bare target repo for each run
- runs either:
syncer.Bootstrapfor--scenario bootstrapsyncer.Runfor--scenario sync
- measures:
- wall-clock time around the whole call
- internal
syncermeasurement data like elapsed time, peak alloc, peak heap-inuse, total alloc, GC count
- emits a JSON report with:
- per-run result
- per-run timings
- aggregate min/avg/max stats
- relay modes seen
How it works:
- if
--source-urlis a plain filesystem path, it converts it tofile://... - for each repetition:
- creates
work-dir/<scenario>-run-XXX.git - initializes it as a bare repo
- points
cfg.Target.URLat that repo - calls the syncer directly in-process
- stores the returned
syncer.Result - optionally deletes the target unless
--keep-targetsis set
- creates
- after all runs, it summarizes the successful runs into aggregate metrics
Why this shape:
- each run starts from an empty target, so results are comparable
- calling
syncerdirectly avoids subprocess overhead and lets the tool capture structured results - JSON output gives you a stable baseline for future optimization work
Current limitation:
- it only benchmarks empty-target flows right now
syncon an empty target will usually end up exercising the bootstrap path throughRun
So today it’s best for:
- bootstrap relay
- batched bootstrap
- the empty-target sync path
Not yet for:
- prepared incremental relay baselines
- prepared materialized fallback baselines
If you want, I can add those next so the tool can benchmark all three major execution modes directly.
and maybe add that to the README? maybe a good idea to update the README in general?
3mo ago·7.5s·10 calls
I’m going to update the README, but I’ll keep it targeted: add the new benchmark command and tighten any rewrite-era drift while I’m in there, rather than turning it into a broad docs rewrite without checking the current content first.
I found one clear README gap: the new benchmark command isn’t documented at all. I’m adding a focused benchmarking section and tightening the measurement guidance so the README points at the new baseline tool instead of only telling people to use --measure-memory by hand.
The README update is small but high-value: it now surfaces the benchmark command where users already look for usage and performance guidance. I’m running a quick doc sanity pass, then I’ll commit it.
The README change is good as-is: it documents the new baseline tool without bloating the main usage flow. I’m committing that now.
The same transient git lock showed up on commit again. I’m retrying once it clears.
Yes, that was a good idea. I updated the README and committed it.
New commit:
2b415ac``Document benchmark command in README
What changed:
- added
git-sync-benchto the command surface - added a benchmark section with:
- example invocation
- what it measures
- current scenarios
- guidance to benchmark against a local mirror
- linked to docs/benchmarking.md
I’d say the README is now in a better state, but it could still use one broader cleanup pass later. The main usage/docs are accurate enough now; the remaining opportunity is mostly structure and trimming older wording, not urgent correctness.
do a README cleanup while I test the tool
3mo ago·10.9s·19 calls
I’m doing a focused README cleanup pass while you test: tighten duplicated sections, group the usage flow more clearly, and make the benchmark/testing guidance easier to scan without changing the actual behavior described.
The README drift is mostly structural now: some sections still read like design notes from earlier stages, and a few usage/details are duplicated. I’m tightening that into a cleaner “capabilities / usage / benchmarking / testing / behavior / design notes” flow without changing meaning.
The README is structurally cleaner now, but I want to verify I didn’t leave duplicated sections behind when regrouping usage, benchmarking, and JSON output. After that I’ll commit the cleanup.
I left one duplicate behind: the JSON-output explanation appears both in Sync Behavior and its own section. I’m removing the earlier copy so the flow reads cleanly.
That removes the main duplication. I’m doing one last scan and then I’ll commit the README cleanup as a docs-only step.
There’s still one duplicate JSON block left from the earlier layout. I’m removing that older copy so the README has a single canonical JSON-output section.
That leaves a single JSON-output section, which is what I wanted. I’m doing a final diff check and then I’ll commit the cleanup pass.
The README cleanup is in good shape now: no duplicated JSON docs, clearer section boundaries, and the older “planned bootstrap” wording is gone. I’m committing it as a docs-only cleanup step.
The same transient git lock showed up on commit again. I’m retrying once it clears.
README cleanup is done.
New commit:
f82addb``Clean up README structure
What changed:
UsagebecameQuick Start- command examples are grouped under
Commands - benchmark guidance is a dedicated
Benchmarkingsection - duplicated JSON-output docs were removed and consolidated into one
JSON Outputsection BehaviorbecameProtocol Notes- old
Planned Bootstrap Pathwording was replaced withDesign Notes
I also sanity-checked the command docs with:
go test ./cmd/git-sync-bench ./cmd/git-sync
If you want, once you’ve tested the tool, the next useful README pass would be much smaller: just trimming some of the long testing section or splitting parts of it into a separate doc.
yeah let's do that, move details into a docs folder, maybe highlight architecture decisions
3mo ago·8.0s·12 calls
I’m going to shrink the README to the operator-facing essentials and move the heavier testing and architecture detail into dedicated docs. That should make the top-level README easier to use while preserving the deeper rationale in places that are easier to maintain.
I’ve got the split: move the long test matrix into docs/testing.md, add a compact docs/architecture.md for the current design decisions, and trim the README to a short operator-oriented summary with links.
The README is now much closer to a front door instead of a catch-all file. I’m verifying the new docs and the trimmed links, then I’ll commit this as the doc split step.
This is the right split: README stays operational, while testing and architecture details live in stable docs with room to grow. I’m committing that docs move now.
The transient git lock showed up again on commit. I’m retrying once it clears.
Done.
New commit:
c44e996``Split README details into docs
What changed:
- added docs/testing.md
- added docs/architecture.md
- trimmed the README so it now links out to those docs instead of carrying the full testing matrix inline
Result:
- README is more of a front door now
- testing details have their own maintained reference
- architecture decisions are highlighted in one place instead of being implied across the README and rewrite memo
I also sanity-checked the command docs with:
go test ./cmd/git-sync-bench ./cmd/git-sync
If you want, the next doc step would be to fold the current rewrite memo and architecture doc together a bit more, so there’s a clearer split between:
- current architecture
- historical rewrite rationale
can we tackle the "why" a bit? there is no tool like this yet, can we highlight why this is a good thing? or isn't there a good reason
3mo ago·12.7s·9 calls
I’m going to add the missing product rationale, not just polish wording. The README explains what git-sync does, but it barely explains why someone should want a remote-to-remote relay tool in the first place.
There is a good reason. The missing piece isn’t “Git mirroring exists”; it’s “there isn’t a small operator tool that mirrors one remote to another directly, safely, and observably without requiring a full local clone or host-specific migration machinery.” I’m writing that down explicitly.
The rationale is in place in both the README and the architecture doc: one operator-facing version, one design-facing version. I’m doing a quick read-through and then I’ll commit it as a docs step.
This is the right “why”: it doesn’t pretend Git can’t do mirroring, it explains why the existing options are the wrong shape for this operational problem. I’m committing it now.
The transient git lock showed up on commit again. I’m retrying once it clears.
Yes, there is a good reason, and I wrote it down.
New commit:
3138b97``Explain product rationale in docs
What changed:
- added a
Why This Existssection near the top of README.md - added a
Product Rationalesection to docs/architecture.md
The framing is:
Git mirroring is possible without this tool
but the usual options are the wrong shape for this operational problem
git-syncfills the gap between:full local mirror clone/push
- host-specific migration tooling
- brittle shell/CI glue
The main “why” now called out in the docs is:
- provider-agnostic remote-to-remote syncing
- explicit planning and validation
- safer bootstrap and incremental behavior
- machine-readable automation surface
- a repeatable sync primitive instead of ad hoc migration scripts
That’s the right argument. It avoids overselling “nothing else exists” while still making the case for why this tool should exist.
ok, from the issue list, what is left now?
3mo ago·9.9s·3 calls
I’m checking the rewrite issue tracker against the current branch state so I can answer with what is genuinely still open, not what the older list used to say.
From the current rewrite-issue-list.md, the real remaining work is mostly the partial items:
#5Pack reader lifecycle in deeper batched bootstrap failure paths. The common paths are covered now, but the tracker still calls out a fuller close-audit for harder batch failure cases.#13Too much ambient state in some helpers. The big monolith is split, but some strategy/helpers still carry broad parameter structs instead of narrower session/context objects.#14Batch planning still relies on network probes. It is better now:- heuristics improved
- probe results are memoized
- some fitted packs are reused But probe-based sizing is still the core mechanism.
#15Materialized fallback still does not truly scale. It is cleaner and isolated, but still fundamentally in-memory.#21Missing behavioral coverage. Most of the originally missing cases are now covered. What’s still left is mainly the harder injected pack-transfer / deep failure-path coverage.Acceptance criteria: capability negotiation is still
partial. Source-side capability handling is much better centralized, but target-side relay gating is not yet a fully typed end-to-end capability boundary.
So the short version is: correctness, validation, race-safety, protocol parsing, benchmarks, and package decomposition are mostly done. What’s left is mostly:
- deeper failure-path hardening
- reducing probe-based batch planning cost
- deciding whether the materialized path should be redesigned or explicitly bounded
- tightening capability modeling further
If you want, I can turn that into a very short “next 3 priorities” list.
yes do that, and just keep iterating on that list, doing commits and do reviews
3mo ago·12.0s·236 calls
The next 3 priorities are:
- harden deeper batched failure paths and close discipline
- reduce batch-planning dependence on throwaway probes
- make remaining capability decisions more explicit and typed
I’m starting with the first one, because it is the highest leverage safety item still open and it should tighten the batch execution model before any further optimization work.
The current batch code still assumes TargetPusher.PushPack owns all close semantics. I’m checking that contract now; if it isn’t explicit everywhere, I’ll make ownership deterministic in the bootstrap strategy and cover the failure path directly.
I’m making close ownership explicit in the bootstrap strategy itself instead of trusting every pusher implementation to do it. That gives one consistent owner for the fetch stream and closes the remaining leak risk in the batch path.
I’ve wired the close-once behavior and added direct tests for strategy-owned closing. I’m running the bootstrap package tests now; I expect one adjustment in the new batched test because the checkpoint graph has to match the desired hash exactly.
The first failure is just a missing strings import. I’m fixing that and rerunning; then I’ll see whether the new batched test needs the graph hash adjustment I expected.
The new test reached planning too early because the batch limit I gave it made checkpoint selection impossible. I’m checking the sampler logic and then I’ll make the test drive execution with a deliberately zero-byte probe result so it hits the real fetch/close path instead of dying in planning.
I found a simpler way to hit the exact path I want: a single-commit branch. That lets planning succeed with a zero-byte probe, then forces execution to fetch the real pack separately, which is perfect for testing close ownership on push failure.
The new close-ownership tests are green in the bootstrap package. I’m doing one broader pass through the full suite now, then I’ll commit this as the first step against the remaining issue list and get a review on whether the ownership boundary is now strong enough.
The first remaining-item step is clean: bootstrap now treats fetched packs as strategy-owned resources, and there’s direct coverage for both one-shot and batched close behavior when the pusher does not help. I’m committing that and asking for a review specifically on whether the ownership model is now correct.
The transient git lock is back on commit. I’m retrying once it clears.
I’m moving to the second priority now: reducing probe dependence. The safest next step is not a radical estimator, it’s a more conservative “skip the final tip probe when the last measured batch is comfortably below the limit” rule, backed by the existing probe-count tests.
I can shave another probe off the easy case safely by separating two decisions:
- “probe the tip first”
- “the last measured density is so far under the limit that we can just take the tip”
I’m implementing that with a conservative threshold and updating the probe-count tests so the change is measured, not just assumed.
The next optimization step is in: if the last measured batch is far enough below the limit, checkpoint planning now just takes the tip instead of spending one more probe to prove what the estimate already makes obvious. I’m running the bootstrap tests and the probe-count visibility suite now.
I found why the probe-count test didn’t move: after the first sample, the adaptive span expanded to exactly the remaining tail, and my new shortcut rejected the remaining == measuredSpan case. That’s the safest case to skip probing, so I’m fixing that condition.
The probe shortcut now behaves the way the visibility test says it should: the easy tail case drops a probe instead of just rearranging the same work. I’m finishing the broader suite pass and then I’ll commit this as the second issue-list step.
The second remaining-item step is clean too: checkpoint planning now has a conservative no-probe fast path, and the probe-count visibility test proves it actually reduces requests in the easy case. I’m committing that and asking for a review on whether the heuristic is conservative enough.
I’m moving to the third priority now: capability modeling. The remaining gap in the tracker is mostly target-side relay gating still depending on raw AdvRefs/planner wiring, so I’m looking for the smallest way to make that boundary more explicit without another large transport rewrite.
The smallest useful capability-modeling step is to stop passing raw *packp.AdvRefs into relay eligibility checks. I’m checking for cycles now; if planner can depend on a small gitproto feature struct without dragging transport back in, I’ll switch the gating logic over to that typed shape.
The planner can depend on a small gitproto feature summary without creating a cycle, so I’m replacing the raw AdvRefs dependency with a typed target feature struct. That should close most of the remaining “capabilities are still partially implicit” gap without touching wire behavior.
I’m picking up the remaining rewrite items by tightening capability modeling first, then I’ll keep pushing down the list. After that I’ll run a review pass on the commit before moving to the next item.
I’ve got the capability split staged conceptually; I’m checking the exact test surface before I format and run the suite. If this holds, it becomes the next commit and then I’ll send it through a review pass.
The planner side looks consistent. I’m checking whether the new feature summary is actually used anywhere else before I lock it in, because if not, I’d rather keep the boundary narrow now than spread another type through the codebase gratuitously.
I’m tightening the feature summary slightly before committing it. The current shape works, but it encodes “capabilities are known” indirectly; I’m making that explicit so the boundary is cleaner instead of relying on a non-zero capability bit as a sentinel.
The type boundary is in place and formatting is done. I’m waiting on the focused package tests now; if they’re green I’ll commit this as the next cleanup step and then hand it to a reviewer.
The focused tests are green; the race run is just finishing the slower syncer package. Once that clears I’ll commit and then continue straight into the next remaining issue instead of stopping here.
The race run is taking materially longer than the focused package tests, so I’m checking whether it’s still making progress or if one test path regressed. I’m not moving on until that’s clear, because this is exactly the kind of subtle boundary cleanup that can introduce a hang if it’s wrong.
The capability change is committed locally now if the tests stay clean. In parallel I’m letting Git finish the commit path and the focused race check, then I’ll push directly into the next issue instead of waiting on a full-stop checkpoint.
I’ve got the capability boundary change committed. Next I’m going after one of the remaining concrete items rather than more structural churn, most likely the deeper injected batch failure coverage so the partials start dropping off the list instead of just moving around.
I found the existing end-to-end recovery tests. They already cover delete-failure and cleanup rerun well, but not the more interesting case where a later batched pack push fails after the temp ref has already advanced. I’m adding that next, because it exercises real resume behavior instead of just command-only cleanup.
I’m wiring a receive-pack test hook into the in-memory smart-HTTP server. That keeps the failure injection local to the e2e harness instead of polluting production code, and it lets me cover the batch-resume path that was still only described in the tracker.
The new test is in. I’m running only the batched recovery paths first, because this change is entirely in the in-memory smart-HTTP harness and I want to prove it behaves before I widen the blast radius to the full syncer suite.
The harness-level failure injection works on the narrow path. I’m widening to the full syncer package and the relevant race subset now; if that stays green, this becomes the next issue-list drop because it closes a real missing recovery scenario rather than just adding synthetic unit coverage.
The review on the capability commit was useful: the type split is better than raw AdvRefs, but it still leaks too much gitproto into planner. I’m going to fix that in the next pass by narrowing the planner input instead of leaving that coupling in place.
I’m splitting the next work into two commits. First I’ll land the new batch-resume recovery coverage and update the tracker, then I’ll come back and narrow the capability type based on the review finding so the architectural cleanup remains easy to evaluate separately.
The last review is directionally right: planner should not depend on gitproto just because the input is typed. I’m tightening that seam now by terminating the protocol-specific type at the syncer boundary and giving planner a narrower relay-policy input.
The planner boundary is narrower now. I’ve pushed the protocol type back out of planner and I’m letting the syncer tests confirm there wasn’t any behavior drift in the relay gating while doing that.
I’ve folded the review feedback back into the boundary itself and updated the tracker language to match. The remaining check is just full-package verification before I cut that as the next small architectural fix.
The capability cleanup is committed and back out for review. I’m moving to the next remaining partial that has the best payoff-to-churn ratio, which is likely making the materialized fallback’s operating bounds more explicit instead of pretending it scales indefinitely.
I’m taking the materialized bound from “internal safety constant” to “explicit operating limit.” That’s the cleanest way to make issue #15 more honest without pretending we solved the underlying scaling model.
I found a good way to make the materialized bound real instead of aspirational: expose it as configuration, keep the default conservative, and add an integration test that proves Run fails clearly when the fallback would exceed the configured limit. That gives the path an explicit operating envelope without pretending it scales.
The materialized limit is now a real config knob instead of an internal magic number. I’m updating the tests and docs around it in parallel so the behavior is explicit both in code and at the CLI surface.
The materialized-limit test was too cheap: the target already had all underlying objects, so creating the extra branch didn’t actually force any materialized object work. I’m fixing the fixture so the source has new objects beyond the target baseline; then the bound should trip for the right reason.
The materialized limit now fails for a real fallback case instead of a synthetic one. I’m running the broader package and race checks before I commit it, because this touches CLI plumbing, syncer wiring, and docs at once.
One of the review notes on the new retry test is fair: it proves the right scenario, but it leans too hard on current checkpoint internals. I’m simplifying that hook now so the test keys off observable state in the target repo rather than today’s exact batch numbering.
The retry test is less coupled now: it fails the first packed temp-ref update after progress exists, instead of betting on the current checkpoint count. I’m re-running the affected package checks and then I’ll cut the materialized-bound commit.
The remaining partials are now mostly structural rather than bug-sized. I’m going after the ambient-state one next in a low-risk way: reducing bootstrap’s internal batch-planning state spread without changing the public syncer surface.
I’m refactoring the bootstrap checkpoint planner internally rather than touching the outer API. The goal is to gather the graph, probe cache, and prefetched-pack state into one object so the batching logic stops carrying so much loose context around.
The bootstrap checkpoint planner is now being collapsed into one internal object instead of a long function with loose shared state. I’m verifying behavior before I touch the tracker, because this one should stay a pure refactor unless something subtle moved.
The bootstrap refactor is behaving so far. I’ve updated the tracker note and I’m letting both the race run and a full package pass finish before I cut it into its own commit.
I’m closing the review feedback on the materialized limit now. The flag stays, but the docs and built-in usage need to describe it honestly as an object-count guardrail, not a byte-accurate memory control.
The materialized-limit review feedback is closed. The built-in usage now lists the flag, and the docs describe it as the object-count guardrail it actually is rather than implying byte-accurate memory control.
Since your last prompt I kept iterating and landed six more commits:
61e809e``Type target relay capability checks68845c9``Cover batched checkpoint retry recovery1a9efa9``Narrow planner relay policy inputb1842f4``Expose materialized sync safety limitd9d38ab``Encapsulate bootstrap checkpoint planning stateecd3908``Clarify materialized limit flag semantics
What changed:
- batched bootstrap now has direct end-to-end coverage for a failed checkpoint pack push followed by a successful resume from the temp ref
- planner no longer imports
gitprototypes for relay gating; syncer now translates target capabilities into a narrower planner policy input - the materialized fallback now has an explicit user-facing guardrail via
--materialized-max-objects, plus a real integration test provingsyncfails clearly when that bound is exceeded - bootstrap checkpoint planning internals are now carried by a dedicated internal planner object instead of one large state-heavy helper
- README, architecture docs, built-in CLI usage, and the rewrite tracker were updated to match
Verification I ran:
go test ./...- targeted and package race runs including:
go test -race ./internal/planner ./internal/syncergo test -race ./internal/strategy/materialized ./internal/syncergo test -race ./internal/strategy/bootstrap ./internal/syncer
Review passes:
1a9efa9: no findingsd9d38ab: no findingsb1842f4: review surfaced two doc/help issues, both fixed inecd3908- the older capability-boundary review was also addressed by
1a9efa9
What’s still genuinely partial on the tracker:
- deeper close-audit around lower-level transport interruption paths
- batch planning still fundamentally relies on network probes
- materialized fallback is bounded now, but still in-memory by design
- some harder injected transport/mid-stream failure paths
- capability handling is better typed, but not yet a fully unified end-to-end model
The branch is clean.
keep going
3mo ago·15.4s·157 calls
I’m continuing from the remaining partials, and the next useful target is the transport-side close/failure audit rather than more refactoring. That should either reduce #5 further or show exactly where the remaining gap is.
The remaining gap is likely in the transport layer, not the strategies anymore. I’m looking at gitproto.PushPack and the stream-returning fetch paths now, because if close ownership is still fuzzy it will show up there rather than in another strategy wrapper.
I found a real close-ownership bug in gitproto.PushPack: if the caller passes a delete command by mistake, it returns early without closing the supplied pack stream. That’s exactly the kind of lower-level lifecycle hole the tracker was still hinting at, so I’m fixing and testing that next.
This one is small but real: it closes a lower-level stream leak in the protocol layer rather than another strategy wrapper. I’m running the focused gitproto tests first, then I’ll update the tracker note and cut it as a separate commit.
The gitproto fix is green under focused tests and the full suite is underway. After that I’ll commit it and hand it to review, then keep pushing on the remaining partials rather than stopping at this one.
The previous quick review found two minor help-text issues. I’m folding those in immediately: the usage text should not hardcode the default, and the flag should be described as sync-only behavior rather than implying plan meaningfully uses it.
The help-text drift is fixed properly now: the default is derived from code, and sync vs plan are listed separately so the materialized limit only shows up where it actually matters.
I’ve closed that review follow-up too. Next I’m going back to the original remaining gap: transport interruption behavior. That likely means a couple of gitproto tests around malformed/aborted stream cases rather than more structural changes.
There’s an obvious missing sibling test on the fetch side: v1 already verifies response-body closure on decode failure, but v2 doesn’t. I’m adding the v2 version now, because that gets us closer to closing the remaining transport-lifecycle gap with an actual regression guard.
This is the right kind of addition: it doesn’t invent new policy, it just locks down response-body ownership symmetry between v1 and v2 fetch failure paths. If it passes, I’ll fold it into the same transport-lifecycle tightening pass.
The v2 fetch close-path test is green too. I’m rolling the recent gitproto lifecycle fixes together now and then I’ll switch back to a remaining higher-level partial, probably the behavior-coverage tracker line.
I’ve reduced the low-level stream-lifecycle gap another notch. The next efficient step is probably the behavior-coverage line in the tracker: either close one more concrete missing case, or tighten the tracker language so only the genuinely hard failures remain.
I’m taking the behavior-coverage line via cancellation next. We already have probe cancellation and v1 fetch cancellation; the missing sibling is v2 fetch cancellation, which is cheap to add and directly relevant to the remaining transport-interruption note.
This is the same pattern as the other v1/v2 symmetry fixes: lock down the v2 side so the remaining “partial” really only refers to harder mid-stream failure behavior, not missing basic cancellation handling.
The v2 cancellation coverage is green too, and the tracker now says exactly what’s covered instead of hand-waving “basic cancellation.” I’m committing that as the next small transport-behavior step.
I’ve got the low-level fetch side into better shape. I’m checking one more adjacent protocol edge now: whether PushPack also closes the stream when request construction itself fails, not just when the network path starts. That’s another good place for hidden leaks.
There’s one more symmetric transport case worth locking down: PushPack under cancellation. We already know it closes on success and server error; I’m adding the context-canceled case so the pack stream lifecycle is covered on all three main exits.
This rounds out the push-side stream ownership pretty well: success, server error, preflight rejection, and cancellation now all assert closure. I’m running the race/full package checks and then I’ll update the tracker note again.
The push-side cancellation case is green too, and the tracker now reflects the actual closure matrix instead of just saying “clearer ownership.” I’m packaging those latest transport-behavior changes next.
The transport-side coverage is materially better now. I’m checking the tracker again so the next step is chosen against what’s actually left, not just the stale original list.
The review on the v2 cancellation step is fair: the tracker note got slightly ahead of the actual tests. I’m going to tighten that wording now, and I’m also checking whether fetchToStoreV2 is cheap enough to cover so the note can become stronger without bluffing.
fetchToStoreV2 is cheap to cover, so I’m doing that instead of just weakening the tracker text. That keeps the note honest by expanding the tested surface rather than backing away from it.
That closes the review gap cleanly. We now have direct v2 cancellation coverage for both streaming fetch and store-to-memory fetch, which makes the tracker wording supportable instead of optimistic.
The tracker note is now exact again. I’m committing the v2 fetch-to-store cancellation coverage as a follow-up to the earlier v2 fetch cancellation step, then I’ll reassess whether the next best move is structural or just closing out the remaining tracker language.
The next useful edge is fetchToStoreV2 on malformed server responses. We now cover cancellation there, but not the decode-failure cleanup path. That’s still exactly the kind of lower-level lifecycle behavior the remaining partials are about.
This one extends the same lifecycle story into the store-to-memory v2 path: cancellation and malformed-response cleanup are both covered now, not just the streaming fetch variant.
The v2 store path is now covered the same way as the other transport exits. I’m committing that before picking whether to keep grinding on transport interruption cases or move to one of the remaining structural partials.
The behavior-coverage section is now misleading in the opposite direction: its bullet list still shows the original gaps even though most are covered. I’m cleaning that up so the tracker points at the actual remaining untested behaviors instead of stale ones.
The remaining behavior gap I can actually close is cancellation after v2 response parsing has already started. fetchToStoreV2 is the right place for that, because it keeps reading the server stream instead of handing the body back to a caller.
This closes one of the last honest behavior gaps: fetchToStoreV2 now has a test for cancellation after the response has already started streaming, not just request startup cancellation. That’s much closer to the real interruption scenario the tracker still called out.
The tracker is now tighter again: it no longer pretends mid-stream interruption is completely untested. I’m packaging this as another transport-behavior commit.
I’ve squeezed a lot of the remaining transport-behavior uncertainty out of the tracker. I’m checking what partials are left now before choosing whether the next commit should be another test, or a structural cleanup.
The remaining interesting work is structural now. I’m taking a contained pass on the materialized strategy next: the path is small enough that I can turn the loose helper flow into an explicit executor object without changing behavior, which chips away at the remaining ambient-state item instead of just polishing tests forever.
I’m about to refactor the materialized strategy into an executor object. This doesn’t change behavior; it just makes the path’s state and stages explicit so it stops being another long helper driven by a broad parameter bag.
The materialized path is now staged as an explicit executor with separate phases for tag fetch, object closure, limit enforcement, and push. I’m running syncer against it now to make sure this stayed a pure structural change.
The materialized refactor is clean under syncer and race checks so far. I’m committing it as a structural follow-up to the remaining ambient-state item rather than letting it sit mixed into the transport work.
I’ve landed another structural cleanup against the ambient-state item. I’m polling the long race run to make sure the full materialized+syncer pair cleared before I pick the next target.
The race run is just the slow syncer leg now. While that finishes, the next likely move is still structural rather than behavioral: either keep chipping at ambient state, or stop once the tracker is honest enough that the remaining partials are truly deliberate tradeoffs.
I’m not going to start another code change until this race run settles, because the recent work crossed strategy and syncer boundaries and I want a clean verification point before piling on more refactors.
The materialized refactor cleared the race run. The next structural cleanup I’d take is target capability wiring in syncer: the relay policy and target features are still assembled ad hoc there, and that’s the cleanest remaining path toward the capability-model partial.
I’m centralizing target capability wiring into syncSession now. That should chip away at the capability-model partial without dragging gitproto types back into planner again.
This is a small but useful capability cleanup: newSession now owns target advertisement, derived feature flags, relay policy, and the pusher together instead of rebuilding that state in Run. I’m verifying the syncer/CLI surface before I update the tracker note.
The capability-model partial is a bit narrower now too. I’m just waiting on the race run for syncer/CLI before I cut this into its own commit.
The race run is on the long syncer leg again, so I’m holding here until it clears. This is the right place to be conservative, because the change touched the session setup that all top-level entry points share.
The shared-session capability wiring cleared the full race run. I’m committing it now, then I’ll likely stop only when the remaining partials are down to the deliberate big-ticket tradeoffs rather than obvious cleanups.
Landed another round of work and the branch is clean.
New commits:
6c10774``Close pack streams on push preflight errors86ad049``Keep usage text aligned with materialized limit flag5ca5f37``Cover v2 fetch body closure on decode errors579d626``Cover v2 fetch cancellation behaviorc6ecbcb``Cover v2 fetch-to-store cancellation behavior048f602``Cover v2 fetch-to-store decode cleanupa62adb7``Refresh remaining behavioral coverage gaps3780862``Cover push pack closure across cancellation pathsd0f0dfa``Cover mid-stream v2 fetch cancellationf7ab510``Encapsulate materialized execution stagesf3c0328``Centralize target capability wiring in session setup
What changed:
tightened
gitprotostream lifecycle coverage significantly:PushPacknow closes pack streams on preflight rejection too- direct tests now cover push-pack closure on success, server error, cancellation, and preflight rejection
- v2 fetch now has body-closure tests for both streaming and fetch-to-store decode failures
- v2 fetch now has cancellation coverage for request startup and for mid-stream cancellation once parsing has begun
cleaned up CLI help drift around
--materialized-max-objectsrefactored materialized execution into an explicit executor with separate phases
moved target capability derivation/policy/pusher wiring into shared session setup instead of rebuilding it ad hoc in
Runupdated the rewrite tracker so the remaining partials are the real ones, not stale original gaps
Verification run throughout:
- repeated targeted
go test - repeated targeted
go test -race - full
go test ./... - full long-running race slices like
go test -race ./internal/syncer ./cmd/git-syncandgo test -race ./internal/gitproto
Review follow-ups addressed:
- help text drift on
--materialized-max-objects - over-broad tracker wording on v2 cancellation coverage
- planner/session capability wiring cleanup
- retry test brittleness reduced earlier by switching to observable-state failure injection
What’s still genuinely partial:
#5only in the sense of rarer transport interruption / malformed mid-stream cases beyond the now-covered ones#13some broader parameter structs/helpers still exist#14batching still fundamentally uses network probes#15materialized fallback is explicit and bounded, but still in-memory by design- acceptance criterion for capability handling is better, but not a fully unified end-to-end model yet
If you want me to keep going from here, the next best target is either:
- more transport interruption coverage until
#5can reasonably flip to done, or - more structural cleanup on ambient state to keep shrinking
#13.
do more on 5
3mo ago·15.3s·52 calls
I’m staying on #5 and looking specifically for the remaining lower-level interruption cases, not just more happy-path closure tests. The likely gap is malformed or interrupted pack streaming after the strategy has already obtained a reader.
I’m switching from protocol tests back to strategy-level interruption tests now. The remaining question for #5 is whether the strategies still close a pack stream when the stream itself breaks mid-read and the target pusher doesn’t clean up for them.
I’m adding a more realistic interruption test now: the target pusher will actually start reading the pack stream, hit a mid-read source error, and return without closing it. That’s closer to the leak scenario than the existing “pusher returned boom immediately” tests.
This test is aimed right at the remaining leak shape: the pack stream breaks while being consumed, the pusher returns the read error, and the bootstrap strategy still has to own the close. If it passes, it meaningfully narrows #5.
The failure is useful: the test accidentally tripped the byte-limit wrapper before the simulated interruption. I’m widening the batch limit so it exercises the intended read-error path instead of the max-pack guardrail.
The previous patch didn’t touch the right line. I’m correcting the actual test fixture now and rerunning just that path.
The interruption test is now hitting the right path and passing. I’m widening back out to the bootstrap package and then the race run, because this one directly changes the evidence behind #5.
This is the most direct #5 test added so far: a batch execution fetch starts streaming, the stream dies mid-read, the pusher returns the read error, and the strategy still closes the reader. I’m waiting on the race run before updating the tracker note.
That gives #5 a much better test shape than before. I’m committing the new interruption coverage now; after that I’ll reassess whether it still deserves to stay partial or whether the remaining gap is too theoretical to keep blocking it.
I landed the batched interruption cleanup test and I’m extending the same pattern to incremental relay now, so the strategy-level stream-ownership story is consistent instead of bootstrap-only.
I’m mirroring the same interruption case into incremental relay so the stream-lifecycle guarantees aren’t lopsided between strategies. If this passes cleanly, #5 starts looking more like a documentation/tradeoff partial than a concrete hole.
This found a real gap, not just a missing test: incremental relay does not currently close the pack when the pusher returns a read error without closing it first. I’m fixing the strategy code now, then rerunning the package and race tests.
I found the concrete bug: incremental.Execute relied on the pusher to close the pack on error, unlike bootstrap. I’m bringing it up to the same ownership model now.
The incremental strategy now matches bootstrap’s close-ownership model, and the new interruption test is proving it. I’m widening to package/race coverage before I update the tracker, because this one is an actual behavior fix rather than just a new test.
This is the strongest #5 change in this pass: it’s a real bug fix, not just more evidence gathering. I’m letting syncer/race clear before I update the tracker so I can call it out cleanly.
The tracker now reflects the actual bug fix too, not just the new test. Once the race run finishes, I’ll cut this as the next #5 commit and then decide if that item is finally close enough to mark done.
I’m waiting this one out because the incremental fix shares the same top-level syncer flow as the previous race-heavy changes. If it stays green, #5 is down to very edge-case transport behavior rather than any obvious ownership bug.
The incremental fix cleared the full race run. I’m committing it as the next concrete #5 improvement.
More work on #5 landed.
New commits:
5d1679a``Cover batched pack stream interruption cleanup25d21fe``Close incremental pack streams on read errors
What changed:
- bootstrap batching now has a direct test for the realistic interruption case:
- target starts consuming the checkpoint pack
- the source stream fails mid-read
- the pusher returns the read error without closing
- the strategy still closes the pack stream
- while mirroring that case into incremental relay, I found a real bug:
incremental.Executedid not own stream closure on read-error paths- it relied on the target pusher to close the stream on failure
- that is now fixed with the same close-once pattern bootstrap already used
- incremental now has direct coverage for:
- pusher error
- mid-read interruption error
Verification:
go test ./internal/strategy/bootstrapgo test -race ./internal/strategy/bootstrapgo test ./internal/strategy/incremental ./internal/syncergo test -race ./internal/strategy/incremental ./internal/syncer
Tracker-wise, #5 is in much better shape now. It is still marked partial, but the remaining gap is genuinely narrower: rarer lower-level transport interruption shapes, not the obvious strategy ownership bug that incremental had.
what can we do there now?
3mo ago·17.0s
For #5, the remaining work is mostly about proving closure behavior in the last awkward transport-layer edges, not obvious strategy bugs anymore.
What’s left worth doing:
- Add
gitprototests for interrupted response bodies after valid startup on the streaming fetch side, not justfetchToStoreV2. Right nowfetchToStoreV2has mid-stream cancellation coverage, butfetchPackV2still mostly proves startup/decode behavior, not “reader returned to caller then stream dies later”. - Add the same style of interruption test for
fetchPackV1. That would make the returned-read-closer lifecycle story symmetric across v1 and v2. - Add one integration-style smart-HTTP test where the target or source drops the connection mid-pack. That would validate the strategy tests against the in-memory HTTP harness instead of only fake readers.
- Optionally factor the
closeOncehelper into a shared internal utility so bootstrap and incremental stop carrying near-identical local implementations. That’s cleanup, not correctness, but it reduces drift.
What I would do next:
gitproto/fetch_test.go: add afetchPackV2test whereopenV2PackStreamsucceeds, the caller reads, then the body errors mid-stream, and closing the returned reader closes the underlying body.- Add the v1 equivalent for
fetchPackV1. - If those pass, I’d probably call
#5effectively done and narrow the tracker note to “covered across strategy and protocol layers; only pathological real-network interruption cases remain”.
So the practical next step is: tighten the returned-pack-reader interruption coverage in gitproto. That’s the cleanest remaining hole.
keep going through the next list
3mo ago·13.0s·42 calls
I’m taking the next concrete gap in #5: interruption after fetchPackV1/fetchPackV2 have already returned a reader to the caller. That’s the cleanest remaining lifecycle hole before moving on to the next partial item.
I’m adding returned-reader interruption tests in gitproto now. These exercise the last meaningful #5 gap: the protocol layer hands a pack reader back to its caller, the stream breaks after valid startup, and closing that reader must still close the underlying HTTP body.
These are the last protocol-reader lifecycle tests I wanted for #5: the caller already has the returned pack reader, the stream dies later, and closing that reader must still close the HTTP body. If they pass, the remaining gap becomes pretty marginal.
I’m going to update the tracker note more aggressively if these stay green, because at that point #5 is no longer “we haven’t really audited this”; it’s “we covered strategy and protocol layers, and only exotic real-network cases are unmodeled.”
#5 is now much closer to “done” in substance: strategy-level interruption cleanup is covered, protocol-level returned-reader interruption is covered, and what’s left is basically the unmocked real-network edge. I’m committing this slice before moving on to the next partial item.
I’m moving off #5 now unless review turns up something concrete. The next best item on the list is #13, because the remaining transport/lifecycle gaps are down to real-network oddities rather than obvious code structure or ownership bugs.
I’m taking #13 next by cleaning up one of the remaining broad parameter flows in bootstrap execution. The easiest high-signal move is to stop threading so much raw session state through bootstrapWithInputs and fold more of that setup into typed state at the syncer boundary.
I’m reducing one of the obvious broad syncer handoff points: bootstrapWithInputs still takes a pile of raw session pieces even though syncSession already owns them. This is a good #13 cleanup without changing behavior.
The cleanup exposed a dead local in Run, which is exactly the kind of thing this refactor is supposed to flush out. I’m trimming the leftover locals and rerunning the package tests.
There were still a few sourceConn references in the normal sync path. I’m wiring those back to session state directly rather than reintroducing another local just to keep the function flatter.
The bootstrap handoff cleanup is back on track after removing the stale locals it exposed. I’m waiting on syncer/CLI tests, then I’ll run the race slice before I update the tracker note for #13.
The code path is clean again under the normal package tests. I’m letting the long race pass settle before I call this another #13 improvement, because it touched the shared syncer flow.
I’m staying conservative here because this race slice is the one that catches shared-session mistakes. If it clears, I’ll commit the bootstrap handoff cleanup and keep pushing down the partials.
The shared bootstrap handoff cleanup cleared the race run. I’m updating the tracker note for #13 and then committing it as the next structural step.
I’ve got another concrete #13 reduction now: bootstrap execution is using shared session state directly instead of a wide helper signature. I’m committing that before deciding whether to keep chipping at ambient state or pivot back to a performance partial.
At this point I’ve materially advanced both #5 and #13. The next meaningful item is probably #14, because the remaining structural partials are becoming smaller while batching probes are still the biggest deliberate inefficiency left.
I kept going and landed more work on both #5 and #13.
New commits:
c676521``Cover returned fetch-pack reader interruptionse397687``Narrow bootstrap execution handoff in syncer
And before that in the same pass:
5d1679a``Cover batched pack stream interruption cleanup25d21fe``Close incremental pack streams on read errors
What changed on #5:
bootstrap batching now has a direct interruption test where the pack stream fails mid-read and the strategy still closes it
that same pattern exposed a real bug in incremental relay
incremental now uses the same close-once ownership model as bootstrap instead of assuming the target pusher closes on failure
gitprotonow has returned-reader interruption tests for bothfetchPackV1andfetchPackV2, so we cover:reader returned successfully
- stream breaks later
- caller closes returned reader
- underlying body is closed
What changed on #13:
bootstrapWithInputsinsyncernow takes the shared session object instead of threading raw source/target conn, adv refs, stats, logger, measurement, etc. through a wide helper signature- tracker notes were updated to reflect the narrower handoff
Verification:
- targeted
go testandgo test -race - long race slices including
go test -race ./internal/strategy/incremental ./internal/syncerandgo test -race ./internal/syncer ./cmd/git-sync
State now:
#5is much narrower than before#13is improved again- the next best remaining item is probably
#14because batch planning still fundamentally depends on probe fetches even after the heuristics and reuse work
If you want, I’ll continue directly into #14 next.
yes
3mo ago·11.3s·68 calls
I’m moving to #14 now. The likely next gain is to avoid re-probing in cases where the planner already has enough information to accept a conservative checkpoint directly, instead of always sampling the boundary again.
I’m continuing on the remaining rewrite tracker items, starting with #14 around bootstrap probe cost. I’m checking the current planner/test shape first so the next change reduces actual probe work rather than just moving code around.
I’ve got the current bootstrap planner back in view. Next step is narrowing where it still pays for probes it could avoid, then I’ll patch that and add probe-count assertions so the change is measurable.
The obvious remaining win is in checkpoint selection itself: using what we already know about monotonic pack growth, so we don’t keep probing spans whose answer is already implied by earlier probes. I’m checking the search helper before changing the planner.
I’m not seeing a clean “big win” on probe count without either taking on more risk or reworking the search algorithm more deeply. I’m checking #13 in parallel, because there may be a lower-risk structural cleanup available while keeping the batching work stable.
I’m taking the lower-risk structural win next: narrowing Run so incremental and materialized execution both go through the session instead of rebuilding their inputs inline. That keeps the capability/transport state in one place and should shrink the remaining #13 note without destabilizing batching.
The Run path is being narrowed into session-owned execution helpers now. I’m checking the compile shape and then I’ll update the rewrite tracker to reflect the additional #13 reduction before testing and committing.
The cleanup is structurally fine; there’s just one stale fallback call left from the old local wiring. I’m fixing that, then I’ll run the syncer tests and update the tracker note for #13.
The syncer cleanup is patched and under test now. After that passes I’ll commit it and hand it to a reviewer, then I’ll decide whether the next step should be another #13 reduction or a deeper pass on #14 batching.
The regular syncer tests passed. The race run is just taking its usual time because the integration slice is heavier there; I’m letting it finish before I commit this cleanup.
The next structural cleanup is committed. I’m handing this one off for a focused review now, then I’ll use that result to decide whether to keep squeezing #13 or switch back to #14 probe reduction.
I’ve got one more cleanup committed and under review now. While that review runs, I’m deciding between a deeper batching algorithm change and another structural reduction; if the reviewer sees no regression risk, I’ll likely keep squeezing the remaining ambient-state paths next.
The review on the last cleanup came back clean. I’m taking another structural pass now: bundling the target-side advertisement, ref map, features, relay policy, and pusher into one target session so syncer stops carrying those as parallel fields.
The target bundling landed cleanly in shape, but there are still a few old field references to collapse. I’m fixing those now and then I’ll test the whole syncer path again before I touch the tracker.
The target-side session bundle compiles cleanly. I’m updating the tracker note now, then I’ll let the race run finish and commit this as a second structural cleanup step.
The second syncer cleanup is committed. I’m sending this one for review as well, then I’ll decide whether the next pass should finally be a deeper #14 algorithm change or whether there’s one more clean structural reduction left.
More progress landed, all committed and reviewed.
New commits:
5923020``Narrow sync execution through session helpers3d6606d``Bundle target state in sync session
What changed:
Runno longer rebuilds incremental and materialized execution inputs inline. Both now go through session-owned helpers.- target-side sync state is now grouped into a dedicated target session object:
- advertised refs
- target ref map
- derived target features
- relay policy
- push executor
- the rewrite tracker in docs/rewrite-issue-list.md was updated to reflect both reductions in
#13and the tighter target capability/session wiring.
Verification:
go test ./internal/syncer ./cmd/git-syncgo test -race ./internal/syncer
Reviews:
5923020: no findings3d6606d: no findings
Branch state is clean.
What’s left that still looks most worth doing next:
#14: batching still fundamentally depends on probe fetches#13: some broader parameter structs still exist, but the syncer surface is noticeably tighter now- acceptance-capability note is better than before, but still not a fully unified end-to-end capability model
If I keep pushing from here, I’d go back to #14 next and only take a batching change that either:
- measurably lowers probe count in tests, or
- measurably lowers batch count / repeated fetches without introducing heuristic brittleness.
yeah let's look at 14, any idea for an approach?
3mo ago·18.0s
Yes. I’d treat #14 as “reduce dependence on exact pack-size probes,” not “guess pack size perfectly.”
The practical approaches, in order:
1. Turn probing into bounded search with reusable bounds
Right now the planner caches exact probe results, but it does not fully exploit the monotonic property: for a fixed have, larger first-parent spans should not produce smaller packs in the normal model.
That means each probe gives you a bound:
- under-limit at
idxmeans every earlier candidate is also under-limit - over-limit at
idxmeans every later candidate is also over-limit
So I’d change the checkpoint search to maintain largestKnownFit / smallestKnownTooLarge and choose the next probe inside that interval, instead of relying on a mostly fixed sampled candidate list. That should cut repeated “obviously redundant” probes.
2. Reuse measured density, not just raw prior results You already have:
- initial commit-count heuristic
- adaptive next span
- tip-first probe
- prefetched pack reuse
The next step is to keep a rolling estimate like:
bytesPerCommit- maybe conservative
maxBytesPerCommitSeen
Then use that to:
- accept some candidates without probing when clearly safe
- skip probing some spans that are clearly impossible
Conservative is important here. I would bias toward:
- only auto-accept when estimated size is comfortably below limit
- still probe near the boundary
3. Plan multiple checkpoints as a continuous frontier Today each checkpoint is selected somewhat independently from the prior one. A stronger approach is:
- once a span fits with some slack, project the next span directly from that slack
- only fall back to probing if the projection nears the limit or crosses a known bound
That makes planning more like controlled stepping than repeated local search.
4. Separate “safe acceptance” from “boundary finding” This is the big design simplification:
- if a candidate is clearly safe from prior evidence, accept it without probing
- only probe when you are trying to expand toward the boundary
That reduces exact probes to places where they actually buy information.
What I would not do yet
- invent a very fancy pack-size model from commit metadata alone
- assume object count or commit count is enough for exact sizing
- optimize around weird non-monotonic corner cases first
Concrete change I’d try first
Replace SampledCheckpointUnderLimit usage in bootstrap planning with a small interval search owned by checkpointPlanner, using cached fit/too-large bounds and only probing when the current estimate is uncertain.
Success criteria:
- probe-count tests get stricter and improve
- no change in correctness behavior
- planner still degrades safely when estimates are bad
So the first implementation target would be:
- add explicit fit/too-large bounds per
prevHash - change
selectNextCheckpointto search within those bounds - add probe-count tests showing fewer probes on:
- linear small-pack histories
- histories with a clear boundary
- long tails after one comfortably under-limit checkpoint
That’s the cleanest next move.
let's do that
3mo ago·17.2s·29 calls
I’m changing the checkpoint search itself now. The goal is to exploit monotonic fit/too-large bounds from earlier probes, so the planner stops asking the source questions it already effectively knows the answer to.
I found the real leverage point: the old sampled search returned the first fitting checkpoint, not the best fitting one. That means it can leave a lot of headroom unused and force extra later batches. I’m replacing that with a bounded search that keeps pushing toward the largest known fit.
The search now keeps tightening around the true boundary instead of stopping at the first fit. I’m adding tests for that behavior and then checking whether the existing probe-count scenarios actually improve or need to be rewritten around batch-count reduction instead.
The first version was too aggressive: it kept searching even when the first projected fit was already good enough, which exploded probe counts on the easy cases. I’m tightening it to only do boundary search when we already know there’s an upper “too large” bound.
The failure is just a test setup issue: the full planner still starts from a small initial span, so it never entered the new boundary-search mode. I’m switching that test down one level so it exercises the new search directly with a wider projected span.
The bootstrap package is passing with the new search behavior. I’m running the syncer slice next because this changes checkpoint planning semantics, then I’ll update the tracker and commit it as a batching-focused step.
The batching change is in the safe zone now: bootstrap and syncer tests pass, and I’ve updated the tracker note for #14. I’m waiting for the race run, then I’ll commit this as a real batching improvement and send it for review.
The batching improvement is committed. I’m sending it for review now, then I’ll decide whether there’s another worthwhile #14 pass or whether the next best target is one of the remaining partials.
I took #14 forward and committed one real batching change:
76823bf``Tighten bootstrap checkpoint boundary search
What changed:
- bootstrap checkpoint planning no longer stops at the first fitting sampled checkpoint once it already knows there is an upper “too large” bound
- it now keeps searching within known fit / too-large bounds to find a larger fitting checkpoint, which should reduce wasted headroom and, in some histories, reduce later batches
- added direct tests for:
- candidate selection with and without an upper bound
- boundary-search behavior finding the largest fitting checkpoint
- updated docs/rewrite-issue-list.md to reflect the new
#14improvement
Verification:
go test ./internal/strategy/bootstrapgo test -race ./internal/strategy/bootstrapgo test ./internal/syncergo test -race ./internal/syncer
Branch is clean.
Review:
- the review request is out, but it timed out before returning. I didn’t get findings back yet.
Assessment:
This is a legitimate improvement, but it does not “solve” #14. The planner is still probe-based. What it does now is use the probe information more intelligently once it has a boundary.
What I would do next on #14:
- Add planner metrics/tests for checkpoint count, not just probe count.
- Add a manual benchmark scenario through
git-sync-benchthat reports:
- planned batch count
- actual batch count
- source fetch probe count if we expose it
- Only after that, decide whether another heuristic step is worth it.
The reason is simple: the next batching improvements are now more measurement-driven than obviously correct from inspection.
go
3mo ago·7.0s·23 calls
I’m making #14 measurable now. The next useful step is exposing checkpoint-planning probe counts in the benchmark/tooling layer so batching changes can be evaluated on actual runs instead of just unit tests.
The benchmark tool is already capturing enough result structure to be useful; it just doesn’t summarize the batching parts yet. I’m checking whether probe counts can be surfaced cleanly from existing stats before I choose between “just aggregate batch metrics” and “instrument explicit planning probes.”
The clean next move is benchmark visibility, not more hidden heuristics. I’m adding aggregate batch metrics to git-sync-bench so large-repo runs show planned and actual batching behavior directly, which gives us a baseline for future #14 work.
I’ve added the aggregation path. Next I’m tightening tests and docs so the benchmark output makes batching behavior explicit instead of burying it inside raw per-run JSON.
The benchmark side is passing. I’m updating the rewrite tracker to note that #14 now has repeatable batch-count visibility in the Go harness, then I’ll commit it and send it for review.
The benchmark visibility step is committed. I’m sending it for review now; after that, the remaining #14 work is really about whether we want more planning heuristics or just use the new measurements to drive manual large-repo runs.
Another meaningful #14 step landed:
c3b7fa0``Expose batch metrics in benchmark summaries
What changed:
git-sync-benchnow aggregates batching behavior across repeated runs:batched_runsmin/max/avg batch_countmin/max/avg planned_batch_count
text output now prints planned and actual batch counts explicitly for batched runs
JSON output includes the same aggregate metrics
updated docs/benchmarking.md and the
#14note in docs/rewrite-issue-list.md
Verification:
go test ./cmd/git-sync-benchgo test -race ./cmd/git-sync-benchgo test ./internal/syncer
Branch is clean.
Review:
- I sent the review request, but it timed out before returning. No findings yet.
Where #14 stands now:
- planning is somewhat smarter:
- initial span heuristic
- probe memoization
- prefetched pack reuse
- boundary search for larger fitting checkpoints
- benchmarking is now good enough to establish a baseline on real repos
- but the planner is still fundamentally probe-based
What I’d do next:
run
git-sync-benchagainst one or two large local mirrors and look at:planned_batch_countbatch_count- wall-clock time
- relay mode
then decide whether another heuristic pass is justified
At this point, more code changes to #14 should probably be driven by measured runs rather than intuition.
do we have a e2e test for 14 we could use to do experiments around?
3mo ago·10.7s·3 calls
I’m checking what we already have that is closest to an e2e harness for #14, so I can separate “unit visibility” from “real protocol-level experiment surface.”
Yes, but not one perfect “#14 experiment harness” yet.
Best existing e2e-ish coverage:
- internal/syncer/git_http_backend_test.go
TestBootstrap_GitHTTPBackendBatchedBranchThis is the closest real protocol/backend test for#14. - internal/syncer/git_http_backend_test.go
TestBootstrap_GitHTTPBackendBatchedBranchResumeUseful for seeing how checkpoint planning interacts with resume. - internal/syncer/integration_test.go
TestRun_IntegrationInitialSyncAutoFallsBackToBatchedBootstrapOnTargetBodyLimitGood for fallback-to-batching behavior, less good for planner experiments.
What we do not have yet:
- an e2e test that asserts planning efficiency metrics like:
- number of probes
- planned batch count staying below some threshold on a fixed history
- a git-http-backend test designed specifically as a “planner experiment fixture”
So for experiments, I’d use:
- unit-level planner tests in internal/strategy/bootstrap/bootstrap_test.go
- git-http-backend batched bootstrap tests for realism
git-sync-benchfor repeated real-repo baselines
If you want, the next useful addition is a dedicated git-http-backend e2e test for #14 that:
- builds a deterministic multi-commit repo
- runs batched bootstrap with a fixed
--batch-max-pack-bytes - asserts
PlannedBatchCountis within a stable expected range - maybe compares against a resume scenario too
That would give us an actual e2e experiment surface for batching changes without needing a huge real repo every time.
yes, do that
3mo ago·17.2s·21 calls
I’m adding a real git-http-backend experiment test for #14, not just another unit case. The goal is a deterministic batched bootstrap scenario that gives us a stable planned-batch baseline at the protocol/backend layer.
I can’t execute the git-http-backend test here because the sandbox blocks httptest from opening a port, but I can still add the right experiment surface. I’m adding a protocol/backend test around batch-limit sensitivity, which is a better #14 baseline than a fragile exact-count assertion.
The new test is in. I’m at least compiling the package here; the actual git-http-backend execution still has to happen in an environment that allows listening on localhost.
The package compiles and the non-env syncer suite passes. I’m waiting on the race pass, then I’ll update the tracker/docs to call out this new #14 e2e experiment surface and commit it.
I’m updating the rewrite tracker with the new backend-level experiment hook, then I’ll commit it and send it for review.
The new backend-level #14 experiment test is committed. I’m sending it for review now; after that, we should have a decent stack for batching experiments: unit planner tests, backend sensitivity tests, and the benchmark tool.
Yes. We do now.
New commit:
b718e82``Add batched planning backend sensitivity test
What changed:
added a new git-http-backend test in internal/syncer/git_http_backend_test.go that creates a deterministic multi-commit repo and checks that:
a smaller
BatchMaxPackBytesproduces at least as many checkpoints as a larger one- both plans still reach the branch tip
updated the
#14note in docs/rewrite-issue-list.md to call out that there is now a backend-level experiment surface for batching changes
Why this is the right e2e shape:
- it’s testing real smart-HTTP/backend behavior via
git-http-backend - it avoids brittle exact checkpoint-count assertions
- it gives you a stable monotonic property to validate future batching changes against
Verification I could run here:
go test ./internal/syncergo test -race ./internal/syncer
I could not execute the actual git-http-backend case in this sandbox because localhost listeners are blocked here, but the package compiles and the full syncer suite passes.
Review:
- no findings
So the current #14 experiment stack is now:
- unit-level planner tests
- backend-level batch-limit sensitivity test
git-sync-benchaggregate batch metrics
That’s a solid baseline for future batching work.
how do I run this now?
3mo ago·8.3s
Run the backend sensitivity test like this:
1
GITSYNC_E2E_GIT_HTTP_BACKEND=1 go test ./internal/syncer -run TestBootstrap_GitHTTPBackendBatchedPlanningTracksBatchLimit -v
If you want the broader batched backend suite too:
1
GITSYNC_E2E_GIT_HTTP_BACKEND=1 go test ./internal/syncer -run 'TestBootstrap_GitHTTPBackendBatched' -v
Useful related runs:
1
2
GITSYNC_E2E_GIT_HTTP_BACKEND=1 go test ./internal/syncer -run TestBootstrap_GitHTTPBackendBatchedBranch -v
GITSYNC_E2E_GIT_HTTP_BACKEND=1 go test ./internal/syncer -run TestBootstrap_GitHTTPBackendBatchedBranchResume -v
Requirements:
gitmust be installed and onPATH- the environment must allow localhost listeners, since the test uses
httptest+git http-backend
If you want, I can also add a small make target or a go test comment block in docs/testing.md so these commands are easier to discover later.
yeah add it to docs/testing.md
3mo ago·7.7s·8 calls
I’m documenting the exact backend batching test commands in docs/testing.md so the new #14 experiment path is discoverable without digging through test names.
I’ve got the right place in the docs. I’m adding the new batching experiment command alongside the existing git-http-backend examples so it’s obvious how to run the #14 surface directly.
The docs are updated. I’m doing a quick sanity check on the markdown and then I’ll commit this as a small follow-up doc change.
I found unrelated local changes in autoresearch.*, so I’m leaving those alone and only committing the testing doc update.
The commit picked up already-staged unrelated autoresearch.* changes from the worktree. I’m checking the resulting status so I can tell you exactly what happened and avoid compounding it.
Added the command to docs/testing.md and committed it in:
46d5fd3``Document batched planning backend test
Run it with:
1
env GOCACHE=/tmp/go-build GITSYNC_E2E_GIT_HTTP_BACKEND=1 go test ./internal/syncer -run TestBootstrap_GitHTTPBackendBatchedPlanningTracksBatchLimit -v
One caveat: that commit also included already-staged unrelated autoresearch.* files:
autoresearch.ideas.mdautoresearch.jsonlautoresearch.md
I didn’t intend to include those. If you want, I can fix that next with a clean follow-up commit or split them out.
fixed, can continue
3mo ago·7.5s·25 calls
I’m continuing from the remaining partials after #14. First I’m checking the branch state and the tracker so I pick the next useful target rather than churning on already-closed work.
The most actionable remaining item is #21, specifically malformed mid-stream fetch responses after valid startup. That’s a cleaner correctness win than trying to redesign materialized storage or fully solve probe-based sizing right now.
The gap is exactly where I expected: we cover startup decode failures and mid-stream I/O interruption, but not “valid startup, then malformed protocol framing.” I’m checking the fetch readers so I can add tests that hit the real parser boundaries instead of synthetic read errors.
I’m going to cover the malformed-after-startup case at the real parser seam: valid response prelude, then invalid sideband pkt-line framing. That should close out a meaningful part of #21 without inventing new machinery.
The new tests are in. I’m running the gitproto package now because these cases hit demuxing and parser behavior directly; if the assumptions are wrong, this is where they’ll fail.
The parser-level coverage is good. I’m updating the tracker note for #21 to reflect the new malformed-after-startup cases, then I’ll commit and send it for review.
I’ve closed part of #21 at the protocol seam. Next I’m doing one wider safety pass through syncer tests, then I’ll commit this as a focused coverage improvement.
The malformed-after-startup coverage is committed. I’m sending it for review now; after that, the remaining #21 gap is mostly transport interruption and broader end-to-end cancellation, not parser coverage.
I moved #21 forward with one focused coverage commit:
8ba4146``Cover malformed fetch packets after startup
What changed:
added parser-level tests in internal/gitproto/fetch_test.go for:
fetchPackV1returned reader with valid startup then malformed sideband pkt-line framingfetchPackV2returned reader with validpackfileprelude then malformed sideband pkt-line framingfetchToStoreV2cleanup on the same malformed-after-startup case
updated the
#21note in docs/rewrite-issue-list.md
Verification:
go test ./internal/gitprotogo test -race ./internal/gitprotogo test ./internal/syncergo test -race ./internal/syncer
Branch is clean.
Review:
- request sent, but it timed out before a response came back
What’s left in #21 now is narrower:
- harder transport interruption during active pack streaming
- broader end-to-end cancellation cases
- less of the parser/malformed-response gap, which is now substantially better covered
If I keep going from here, I’d take the remaining #21 transport-interruption coverage next.
do this
3mo ago·12.0s·24 calls
I’m taking the remaining #21 gap directly: transport interruption while pack streaming is active. I’m looking at the in-memory smart-HTTP harness first so the next tests exercise real syncer flow instead of just reader stubs.
I’m adding one hook to the in-memory smart-HTTP server so syncer-level tests can force “valid startup, then bad fetch stream” and “valid startup, then blocked stream plus cancellation.” That closes more of #21 at the real API layer instead of only in gitproto.
The server hook is in. I’m adding two syncer-level tests on top of it:
- malformed v2 fetch after valid startup
- canceled v2 fetch after valid startup and first packet read
The new syncer-level cases are in. I’m running the targeted integration slice first, because these tests depend on response flushing and cancellation timing rather than just package compilation.
The targeted cases are solid. I’m running the full syncer suite now to make sure the new raw-response hook didn’t destabilize the server harness more broadly.
The full syncer suite is fine; the race run is still going. I’m updating the tracker now so the remaining #21 note reflects that we have real syncer-level mid-stream malformed/canceled fetch coverage, not just parser tests.
I’ve closed another chunk of #21 at the syncer layer. Once the race run finishes I’ll commit this as a real end-to-end coverage step and hand it to review.
The syncer-level mid-stream fetch coverage is committed. I’m sending it for review now; after that, #21 is mostly down to rarer transport interruption shapes rather than missing obvious cancellation/malformed cases.
I moved #21 further with one syncer-level commit:
838472e``Cover mid-stream v2 fetch failures in syncer
What changed:
- added a raw v2 fetch hook to the in-memory smart-HTTP test server in internal/syncer/integration_test.go
- added two end-to-end syncer tests:
- malformed v2 fetch response after valid startup
- canceled v2 fetch after valid startup and first streamed packet
- updated the
#21note in docs/rewrite-issue-list.md
Verification:
go test ./internal/syncer -run 'TestRun_IntegrationV2Fetch(Malformed|Canceled)MidStreamFails' -vgo test -race ./internal/syncer -run 'TestRun_IntegrationV2Fetch(Malformed|Canceled)MidStreamFails' -vgo test ./internal/syncergo test -race ./internal/syncer
Branch is clean.
Review:
- sent, but timed out before returning
At this point, the remaining #21 gap is mostly the harsher transport-interruption variants, not missing parser or cancellation coverage. If I keep going, that’s the next target.
838472eCover mid-stream v2 fetch failures in syncer\ \ Entire-Checkpoint: a8ad0d3d78a5+122/-1
59b4a85Cover mid-stream v1 fetch failures in syncer\ \ Entire-Checkpoint: 40dcb8c93a3d+116/-2
7b26009Cover mid-stream push failures in bootstrap\ \ Entire-Checkpoint: a01557bd71c2+133/-1
aca59beMove probe and fetch shaping into session helpers\ \ Entire-Checkpoint: a7d61aa7dcfe+90/-67
359d4e9Add materialized object handling examples\ \ Entire-Checkpoint: c00c3abb8037+132
955e834Document materialized fallback memory assumptions\ \ Entire-Checkpoint: bcc2185c1a3b+34
3f9782bExplain when git-sync is a good fit\ \ Entire-Checkpoint: aea7d77ee5dc+25
91039b4Clarify relay-first fit in README\ \ Entire-Checkpoint: 7a24face65d3+8
32d5b66Remove superseded rewrite planning docs\ \ Entire-Checkpoint: 14ec78cd4eba-852
ac68fedAddress PR review cleanup comments\ \ Entire-Checkpoint: 36113cbd7ee5+9/-9
f9741a6Ignore root benchmark binary\ \ Entire-Checkpoint: bcecee40e355+1
3e236b4Add initial public gitsync package\ \ Entire-Checkpoint: 5fe00ff669ff+448
cd93f12Add HTTP client injection to gitsync\ \ Entire-Checkpoint: 15ba661d1843+101/-31
ff29a6aAdd auth provider to gitsync client\ \ Entire-Checkpoint: ca76acb396e8+111/-29
9ccde3eDocument gitsync embedding surface\ \ Entire-Checkpoint: 5562a64d356d+44
65dc18bAdd unstable gitsync surface for CLI tooling\ \ Entire-Checkpoint: 0917b3232b96+516/-129
0d2e56cDocument stable and unstable library APIs\ \ Entire-Checkpoint: ff6143165562+37/-1
44b3c4dAlign command tests with unstable API\ \ Entire-Checkpoint: a429d4ed840b+10/-9
661eec5Shape stable gitsync results for embedders\ \ Entire-Checkpoint: 59490433b887+109/-36
322787cDocument stable gitsync result shape\ \ Entire-Checkpoint: 74df732fb738+18
8414f70Add embedding guide for gitsync\ \ Entire-Checkpoint: be52c896e43e+164
cafba1aReduce stable client coupling to syncer\ \ Entire-Checkpoint: 2f33299e4045+132/-40
ccf984dMove stable gitsync model behind bridge\ \ Entire-Checkpoint: 1f9417321410+235/-215
ee16e97Deduplicate gitsync adapter wiring\ \ Entire-Checkpoint: 80cdcddb4888+64/-24
d63bbb8Tighten gitsync public boundary\ \ Entire-Checkpoint: f2725f595f5c+362/-155
8412f23Clean up gitsync adapter helpers\ \ Entire-Checkpoint: f2b685c45383+114/-19
f6bd84dPropagate unstable probe verbose flag\ \ Entire-Checkpoint: e9df1a710333+1