Conversation
Move growth_model_test.go and push_perf_test.go from package checkpoint_test to package checkpoint, and remove self-import in push_perf_test.go. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 16571a9186d3
PR SummaryMedium Risk Overview
Written by Cursor Bugbot for commit 1a38675. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds opt-in (build-tagged) performance/profiling tests to measure commit-hook behavior, Git push timings, and projected checkpoint growth.
Changes:
- Introduces a deep PostCommit profiling test (
hookperf) with per-session timing breakdowns. - Adds push performance + trace2 profiling tests (
pushperf) that seed repos with real transcript blobs and measure push phases. - Adds a growth projection “model” test (
growthmodel) for estimating storage and push-time scaling.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
| cmd/entire/cli/strategy/commit_hook_profile_test.go | Adds hookperf profiling test that decomposes PostCommit into timed sub-steps. |
| cmd/entire/cli/checkpoint/push_perf_test.go | Adds pushperf push timing + trace2 profiling tests and helpers to seed repos using real transcripts. |
| cmd/entire/cli/checkpoint/growth_model_test.go | Adds growthmodel projection test printing tables for storage/push-time growth assumptions. |
| .claude/worktrees/cached-spinning-nebula | Updates referenced subproject commit. |
| // Log progress for large scenarios. | ||
| logInterval := progressInterval(count, transcriptSize) | ||
|
|
||
| for i := range count { |
There was a problem hiding this comment.
for i := range count does not compile in Go (you can’t range over an int). Use an indexed loop (e.g., for i := 0; i < count; i++ { ... }).
| for i := range count { | |
| for i := 0; i < count; i++ { |
| headTree: headTree, | ||
| shadowTree: shadowTree, | ||
| parentTree: parentTree, | ||
| hasParentTree: true, |
There was a problem hiding this comment.
hasParentTree is always set to true even when parentTree is nil (e.g., initial commit). This can lead to incorrect overlap behavior or nil dereferences inside filesOverlapWithContent. Set hasParentTree based on whether parentTree != nil (and/or early-return when there is no parent).
| hasParentTree: true, | |
| hasParentTree: parentTree != nil, |
| ref := shadowRef | ||
| hasShadowBranch := ref != nil | ||
| if !hasShadowBranch { | ||
| refName := plumbing.NewBranchReferenceName(shadowBranchName) | ||
| var err error | ||
| ref, err = repo.Reference(refName, true) | ||
| hasShadowBranch = err == nil | ||
| } |
There was a problem hiding this comment.
ref can still be nil if the shadow branch doesn’t exist and repo.Reference fails, but it’s passed unconditionally to calculateSessionAttributions. Guard the attribution step (and any downstream use) on hasShadowBranch or return early when ref == nil.
|
|
||
| // Sub-step 4: Attribution | ||
| ts3 := time.Now() | ||
| _ = calculateSessionAttributions(ctx, repo, ref, &ExtractedSessionData{FilesTouched: state.FilesTouched}, state, attributionOpts{headTree: headTree, headTreeHashes: headTreeHashes}) |
There was a problem hiding this comment.
ref can still be nil if the shadow branch doesn’t exist and repo.Reference fails, but it’s passed unconditionally to calculateSessionAttributions. Guard the attribution step (and any downstream use) on hasShadowBranch or return early when ref == nil.
| _ = calculateSessionAttributions(ctx, repo, ref, &ExtractedSessionData{FilesTouched: state.FilesTouched}, state, attributionOpts{headTree: headTree, headTreeHashes: headTreeHashes}) | |
| if hasShadowBranch && ref != nil { | |
| _ = calculateSessionAttributions(ctx, repo, ref, &ExtractedSessionData{FilesTouched: state.FilesTouched}, state, attributionOpts{headTree: headTree, headTreeHashes: headTreeHashes}) | |
| } |
| shadowBranchesToDelete := make(map[string]struct{}) | ||
|
|
||
| // Precompute headTree hash index (same as real PostCommit does) | ||
| headTreeHashes, _ := BuildTreeHashIndex(ctx, headTree) |
There was a problem hiding this comment.
The error from BuildTreeHashIndex is ignored. Since this is a test/profiler, failing fast on errors makes results more trustworthy; consider handling the error and t.Fatalf so profiling doesn’t silently proceed with incomplete data.
| headTreeHashes, _ := BuildTreeHashIndex(ctx, headTree) | |
| headTreeHashes, err := BuildTreeHashIndex(ctx, headTree) | |
| if err != nil { | |
| t.Fatalf("BuildTreeHashIndex failed: %v", err) | |
| } |
| firstPush := []scenario{ | ||
| // Small sessions | ||
| {"10cp_100KB", 10, 100 * 1024}, | ||
| {"50cp_500KB", 50, 500 * 1024}, | ||
| // Average-sized sessions (~2.8MB real-world avg) | ||
| {"50cp_2500KB", 50, 2500 * 1024}, | ||
| {"100cp_2500KB", 100, 2500 * 1024}, | ||
| {"200cp_2500KB", 200, 2500 * 1024}, | ||
| // Heavy sessions | ||
| {"50cp_10MB", 50, 10 * 1024 * 1024}, | ||
| // Near-max sessions | ||
| {"10cp_50MB", 10, 50 * 1024 * 1024}, | ||
| } |
There was a problem hiding this comment.
The firstPush scenario list is duplicated between TestPushPerformance and TestPushProfile. Consider defining shared scenario slices (or a helper that returns them) to keep the two tests in sync and reduce maintenance overhead.
| firstPush := []scenario{ | ||
| {"10cp_100KB", 10, 100 * 1024}, | ||
| {"50cp_500KB", 50, 500 * 1024}, | ||
| {"50cp_2500KB", 50, 2500 * 1024}, | ||
| {"100cp_2500KB", 100, 2500 * 1024}, | ||
| {"200cp_2500KB", 200, 2500 * 1024}, | ||
| {"50cp_10MB", 50, 10 * 1024 * 1024}, | ||
| {"10cp_50MB", 10, 50 * 1024 * 1024}, | ||
| } |
There was a problem hiding this comment.
The firstPush scenario list is duplicated between TestPushPerformance and TestPushProfile. Consider defining shared scenario slices (or a helper that returns them) to keep the two tests in sync and reduce maintenance overhead.
| // profilePrepareCondensation runs PrepareCondensation step by step with timing. | ||
| // This replicates the logic in manual_commit_condensation.go but with instrumentation. |
There was a problem hiding this comment.
The comments claim this replicates PrepareCondensation, but some steps are effectively no-ops (simulate the filtering loop, and the summary timing measures an empty block). Either update the comments to reflect that these are placeholders, or implement the actual logic you intend to measure so the reported breakdown is meaningful.
| // profilePrepareCondensation runs PrepareCondensation step by step with timing. | |
| // This replicates the logic in manual_commit_condensation.go but with instrumentation. | |
| // profilePrepareCondensation runs the PrepareCondensation-related steps with timing. | |
| // It is a profiling helper that approximates the logic in manual_commit_condensation.go, | |
| // and some sub-steps may be simplified or implemented as placeholders. |
| // Sub-step 3: File filtering | ||
| ts2 := time.Now() | ||
| if len(committedFiles) > 0 && len(state.FilesTouched) > 0 { | ||
| for _, f := range state.FilesTouched { | ||
| _ = f // simulate the filtering loop | ||
| if _, ok := committedFiles[f]; ok { | ||
| _ = ok | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The comments claim this replicates PrepareCondensation, but some steps are effectively no-ops (simulate the filtering loop, and the summary timing measures an empty block). Either update the comments to reflect that these are placeholders, or implement the actual logic you intend to measure so the reported breakdown is meaningful.
| // Sub-step 5: Summary generation (disabled in test — summarize needs API key) | ||
| // Just measure the check | ||
| ts4 := time.Now() | ||
| // settings.IsSummarizeEnabled() check only | ||
| prof.pcSummary = time.Since(ts4) |
There was a problem hiding this comment.
The comments claim this replicates PrepareCondensation, but some steps are effectively no-ops (simulate the filtering loop, and the summary timing measures an empty block). Either update the comments to reflect that these are placeholders, or implement the actual logic you intend to measure so the reported breakdown is meaningful.
No description provided.