Conversation
Replace the eager per-index size map with FileSizes: a local source root is read on demand for the files a query actually returns, memoized behind a Mutex so the Arc<CspIndex> shared by MCP calls stays Sync. Git sources still capture sizes at clone time because the temp checkout is gone by search time. Cached loads no longer re-read every indexed file. Deliberate divergence from upstream semble (eager in SembleIndex.__init__); noted in the semble reference doc.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 29 |
| Duplication | 4 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Code Review
This pull request introduces a lazy file size reader (FileSizes) to replace eager computation of all indexed file sizes, reading local source roots on demand per result with a memoized cache. Telemetry recording and CspIndex are updated to use this new struct, and comprehensive unit tests are added. The reviewer suggested optimizing compute_file_sizes by collecting unique file paths into a HashSet first to avoid redundant checks/reads across multiple chunks.
- bound lazy reads by MAX_FILE_BYTES and decode lossily like the indexer - memoize misses so an unreadable path is attempted once per index - dedupe result paths with a HashSet in save_search_stats - tighten telemetry tests to assert real file_chars values
Greptile SummaryThis PR replaces eager file-size collection with lazy, memoized reads for local indexes while retaining captured sizes for temporary Git checkouts.
Confidence Score: 4/5The PR should not merge until the breaking public Rust API change is either preserved or explicitly handled; the lazy-read containment and telemetry-consistency issues should also be addressed. Existing users of the published crate can fail to compile because two public contracts change types, while lazy source reads can escape the intended root and calculate savings from content different from the indexed results. Files Needing Attention: crates/csp/src/indexing/index.rs, crates/csp/src/indexing/file_sizes.rs, crates/csp/src/stats.rs
|
| Filename | Overview |
|---|---|
| crates/csp/src/indexing/file_sizes.rs | Introduces lazy and captured size resolution, but live pathname reads can escape through intermediate symlinks and diverge from indexed content. |
| crates/csp/src/indexing/index.rs | Integrates lazy local sizing and captured Git sizing while changing the public file_sizes field contract. |
| crates/csp/src/stats.rs | Deduplicates result paths efficiently and resolves sizes through FileSizes, but changes a public function signature. |
| crates/csp/src/indexing/index/tests.rs | Adds focused coverage for lazy local sizing and unavailable sizes when a source root disappears. |
| crates/csp/src/bin/csp/main.rs | Strengthens the CLI telemetry test to verify nonzero serialized character counts. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
LP[Local source] --> LI[Build or load index]
LI --> LZ[FileSizes lazy root]
GS[Git source] --> CL[Temporary clone]
CL --> CP[Capture file sizes]
CP --> GI[Git-backed index]
SR[Search results] --> UP[Unique result paths]
UP --> GET[FileSizes get]
LZ --> GET
GI --> GET
GET --> MEM[Memoized UTF-16 counts]
MEM --> ST[Token-savings telemetry]
Prompt To Fix All With AI
### Issue 1
crates/csp/src/indexing/index.rs:89
**Public API Breakage**
Changing the public `CspIndex.file_sizes` field from `HashMap<String, u64>` to `FileSizes`, together with changing `save_search_stats` to accept `&FileSizes`, breaks existing consumers of the published Rust crate. Callers that iterate, clone, inspect, or pass the former map will now fail to compile. Preserve the existing contract or handle and document this as a deliberate breaking API change.
### Issue 2
crates/csp/src/indexing/file_sizes.rs:104-110
**Path Checks Are Bypassable**
The containment and file-type checks do not cover the later pathname read. `symlink_metadata` follows symlinks in intermediate directories, and the path can also change before `std::fs::read` reopens it. A mutable indexed tree can therefore redirect telemetry reads outside the source root or bypass the regular-file and size checks. Use a no-follow, descriptor-relative open and enforce the byte limit while reading.
**How this was verified:** An indexed intermediate directory can be replaced with a symlink, after which metadata and the read both resolve a regular target outside the configured source root.
### Issue 3
crates/csp/src/indexing/file_sizes.rs:63-68
**Telemetry Uses Mismatched Content**
Lazily reading the live source can make `file_chars` describe different content from the returned indexed chunks. During the MCP freshness cooldown, or when an explicitly loaded index remains open while its source changes, the first lookup reads the modified file. Files grown beyond `MAX_FILE_BYTES` also contribute zero. As a result, `saved_chars = file_chars - snippet_chars` becomes inaccurate. Preserve the size associated with indexed content or invalidate lazy values whenever the index becomes stale.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "chore(stats): apply code review fixes to..." | Re-trigger Greptile
Collect unique chunk paths first so an unreadable file is attempted once instead of once per chunk (Gemini review on #92).
|
/gemini review |
Reject a symlink leaf, require the canonicalized path to stay under the canonicalized root (a symlinked intermediate directory no longer escapes), fstat the opened handle instead of the path, and cap the read at MAX_FILE_BYTES while reading (Greptile review on #92).
There was a problem hiding this comment.
Code Review
This pull request implements lazy loading and memoization of file sizes for token-savings telemetry via a new FileSizes utility, replacing the previous eager computation. It includes path traversal guards, file size limits, and robust test coverage. The review feedback suggests a performance optimization in read_file_chars to count UTF-16 code units using char::len_utf16 instead of performing a full encoding.
|
/gemini review |
There was a problem hiding this comment.
Review completed against the latest diff
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
Code Review
This pull request introduces lazy loading and memoization of file character counts via a new FileSizes utility to optimize token-savings telemetry, replacing the previous eager computation. The feedback focuses on performance improvements to avoid redundant filesystem I/O: specifically, the root path should be canonicalized once during initialization in FileSizes::lazy and at the start of compute_file_sizes, rather than repeatedly canonicalizing it inside read_file_chars for every file lookup.
FileSizes::lazy and compute_file_sizes canonicalize the root at construction; read_file_chars now requires a canonical root and does a plain prefix check (Gemini review on #92).
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces lazy loading of file sizes (character counts) for token-savings telemetry, diverging from the upstream eager calculation. It adds a new FileSizes struct in crates/csp/src/indexing/file_sizes.rs that supports both eager (captured) and lazy (on-demand from a local root) resolution with memoization, path traversal guards, and file size limits. Corresponding updates and tests have been added across the indexing, stats, and main binary modules. I have no feedback to provide as there are no review comments to assess.
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
… reads A FIFO at an indexed path made File::open block until a writer appeared, stalling the search that triggered the size lookup. Check the canonical path with symlink_metadata first; the fstat on the opened handle stays as the race guard. Raised by cubic on #92.
|



Summary
Closes #90.
CspIndex::load_from_diskandfrom_pathused to callcompute_file_sizeseagerly, re-reading and UTF-16 counting every indexed file on every cached load, only to feed thefile_charsside of token-savings telemetry.save_search_statsonly ever looks up the unique file paths of the returned results.This replaces the eager
HashMapwith aFileSizesvalue (crates/csp/src/indexing/file_sizes.rs):from_path,load_from_diskwith a still-present root) read sizes lazily per result and memoize them behind aMutex, so theArc<CspIndex>shared across MCP calls staysSync.from_git) still capture sizes at clone time, because the temp checkout is gone by search time.read_file_chars, shared by both paths.savings.jsonlrecord shape and UTF-16 accounting are unchanged.This is a deliberate divergence from upstream semble, which computes sizes eagerly in
SembleIndex.__init__. Recorded in.please/docs/references/semble.md§4.15.Tests
file_sizes.rs: lazy read + memo (survives file deletion),Nonefor.., absolute, symlink, and missing paths; captured map serves only known paths;empty()is not available.index/tests.rs:from_pathyields lazy sizes;load_from_diskwith a removed source root reports no sizes.compute_file_sizes_*andsave_search_statstests unchanged in intent.Verification
Breaking change (library field, pre-1.0)
CspIndex::file_sizesis now aFileSizesvalue instead of a publicHashMap<String, u64>, andstats::save_search_statstakes&FileSizes. Replace map lookups withindex.file_sizes.get(path); there is no iteration orlen()because local-path sizes are read on demand. The field is derived telemetry metadata and is not part of the README-documented API surface, so this ships without a major bump.FileSizesis notClone. Nothing in the workspace clones aCspIndex.Follow-up
file_charsmatch the indexed content, and fixes git-from-cache and moved-source reportingfile_chars: 0.Review hardening
MAX_FILE_BYTES, decode lossily like the indexer, memoize misses, and are contained to the canonicalized root withfstaton the opened handle.