Skip to content

feat(cli): add csp clear orphans to remove cached indexes whose source is gone - #102

Merged
amondnet merged 4 commits into
mainfrom
amondnet/parity-add-csp-clear-orphans-to-remove-cached-in
Sep 4, 2026
Merged

feat(cli): add csp clear orphans to remove cached indexes whose source is gone#102
amondnet merged 4 commits into
mainfrom
amondnet/parity-add-csp-clear-orphans-to-remove-cached-in

Conversation

@amondnet

@amondnet amondnet commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Port of upstream semble#243 _clear_orphans: a new csp clear orphans subcommand that removes cached indexes whose local source directory no longer exists.

clear_orphan_indexes in crates/csp/src/indexing/cache.rs walks ~/.csp/index/* and removes an entry only when all of the following hold:

  • the directory name is cache-key shaped (32 lowercase hex characters),
  • the manifest sourceId is an absolute, non-git local path,
  • std::fs::metadata on that path returns NotFound.

The sweep uses std::fs::metadata rather than Path::exists on purpose: Path::exists collapses every error into false, so an unreachable volume or a permission error would look like a deleted source and get swept. Matching on NotFound keeps those entries.

The index-root guard is shared with clear index via guarded_index_root. orphans is its own choice and is deliberately not part of clear all, matching upstream.

Design note: no cache-key recomputation

The issue's task list asked to recompute the cache key from the manifest and compare it against the directory name, as upstream does. That was implemented first and then dropped in the second commit: csp keys the cache on the raw CLI source string (.), while the manifest records the absolute path, so the recomputed key never matched a real entry and the sweep skipped everything. csp instead relies on is_git_url to exclude git entries, which is sound because from_git re-roots the manifest sourceId to the URL. Follow-up #100 tracks canonicalizing the source before keying, which would let the stricter upstream check come back.

Changes

  • crates/csp/src/indexing/cache.rs: add clear_orphan_indexes, reuse guarded_index_root with clear index.
  • CLI: add orphans as a clear choice (not included in clear all).
  • README.md / README.ko.md: document the new subcommand (bilingual, kept in sync).
  • .please/docs/references/semble.md: record the upstream parity mapping and the deviation above.

Test plan

  • cargo fmt --all
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --workspace — 343 pass
  • End-to-end against the built binary: index a repo via ., delete the repo directory, run csp clear orphans — it reports the entry cleared and leaves a live source untouched

Related issue

Closes #87

Related follow-up: #100 (canonicalize the source before keying the cache)

Checklist

  • PR title follows Conventional Commits
  • Tests added or updated, and the suite passes
  • Lint/format pass
  • Documentation updated if behavior changed
  • No breaking change

Summary by cubic

Adds csp clear orphans to remove cached indexes whose recorded local source directory no longer exists. Live sources, git-URL entries, and sources that merely fail to stat (e.g., permission denied) are left untouched; sources on an unmounted volume are swept because their path is simply absent.

New Features

  • Walks ~/.csp/index/ and removes only entries with a cache-key-shaped directory name, an absolute local sourceId that is a genuine NotFound, and no git URL; traversal errors fail the sweep instead of reporting "no orphans found".
  • Reuses the same index-root guard as clear index; orphans is a separate choice and is not part of clear all.
  • Deviates from upstream by not recomputing the cache key from the manifest, since csp keys on the raw CLI source while the manifest records the absolute path.

Written for commit aeb128c. Summary will update on new commits.

…rce is gone (#87)

Port of upstream semble#243 (`_clear_orphans`). `clear_orphan_indexes` walks
`<home>/index/*`, reads each manifest, and removes entries whose `sourceId`
is a local path that no longer exists. An entry is trusted only when
`resolve_cache_dir(sourceId, content)` reproduces its directory name, so
git-URL leaves (URL + ref keyed, built from a temp clone), malformed
manifests, and key mismatches are left untouched. The root guard is shared
with `clear_index_cache` via `guarded_index_root`. `orphans` is its own
choice — `clear all` does not include it, matching upstream.

Closes #87
…source

The key-reproduction guard re-derived the cache key from the manifest and
required it to match the entry directory name. It never matched for the
documented invocation (`csp search "q" ."`): `load_or_build_index` keys on the
raw CLI argument while `from_path` records `std::path::absolute` of it, so the
sweep was a no-op for local sources. Upstream can afford that guard because its
`cache_key` resolves the path; csp normalizes only lexically.

Replace it with the checks that actually hold:
- the leaf directory name has the cache-key shape (32 lowercase hex, mirroring
  upstream `_SHA_256_REGEX`), so stray directories are never swept;
- `is_git_url(sourceId)` excludes remote indexes, which `from_git` re-roots to
  the URL;
- the `sourceId` is absolute, since a relative one would be judged against the
  caller's cwd;
- the source is a genuine `NotFound`, not merely unreachable — `Path::exists`
  collapses the two and would delete live caches for an unmounted volume or an
  unreadable parent.

Read only `sourceId` out of the manifest instead of a full `read_manifest`
parse, which also deserializes the per-file entry map; `read_manifest` goes
back to private.

Refs #87
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 36 complexity · 4 duplication

Metric Results
Complexity 36
Duplication 4

View in Codacy

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the clear orphans command, which identifies and removes cached indexes whose local source paths no longer exist. The changes span the CLI interface, cache management logic, documentation, and unit tests. The feedback suggests a performance optimization in read_manifest_source_id to deserialize only the sourceId field using a dedicated lightweight struct, avoiding the overhead of parsing the entire manifest into a generic JSON value.

Comment thread crates/csp/src/indexing/cache.rs
@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.12230% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/csp/src/bin/csp/main.rs 92.15% 4 Missing ⚠️
crates/csp/src/indexing/cache.rs 98.23% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 5 untouched benchmarks


Comparing amondnet/parity-add-csp-clear-orphans-to-remove-cached-in (aeb128c) with main (b2de30d)

Open in CodSpeed

@amondnet
amondnet marked this pull request as ready for review September 4, 2026 14:02
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds the csp clear orphans maintenance command and its cache-sweeping implementation, tests, CLI output, and bilingual documentation.

  • Restricts candidates to key-shaped directories with absolute, non-Git manifest sources.
  • Reuses the guarded index-root boundary and deletes only sources classified as NotFound.
  • Keeps orphan cleanup separate from clear all.
  • The current NotFound test cannot distinguish a deleted repository from a repository below an unmounted filesystem.
  • Per-entry traversal errors are currently hidden from the CLI.

Confidence Score: 4/5

The PR is not safe to merge until orphan cleanup stops deleting caches for sources that are temporarily absent because their filesystem is unmounted.

The sweep treats every metadata NotFound as proof of deletion, although paths beneath an unmounted volume can produce the same result; it also reports success after silently skipping individual traversal errors.

Files Needing Attention: crates/csp/src/indexing/cache.rs, README.md, README.ko.md, .please/docs/references/semble.md

Important Files Changed

Filename Overview
crates/csp/src/indexing/cache.rs Adds guarded orphan discovery and deletion, but conflates unmounted subpaths with deleted sources and suppresses per-entry traversal errors.
crates/csp/src/bin/csp/main.rs Adds the orphans clear choice, output, exit handling, and an end-to-end-style command test.
README.md Documents the new command but promises retention of unmounted sources that the implementation cannot guarantee.
README.ko.md Adds synchronized Korean documentation, including the same unsupported unmounted-source guarantee.
.please/docs/references/semble.md Records upstream parity and key-recomputation drift, but also overstates the protection provided by checking only NotFound.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[csp clear orphans] --> B[Resolve and guard index root]
    B --> C[Walk cache-key-shaped leaves]
    C --> D[Read manifest sourceId]
    D --> E{Absolute local path?}
    E -->|No| F[Keep cache entry]
    E -->|Yes| G[Call filesystem metadata]
    G -->|Exists or non-NotFound error| F
    G -->|NotFound| H[Remove cache entry]
    I[Unmounted source below mount point] -->|May also return NotFound| H
Loading

Fix all with Greploop Fix All in Claude Code

Prompt To Fix All With AI
### Issue 1
crates/csp/src/indexing/cache.rs:358
**Unmounted Sources Lose Caches**

If a repository is below a mount point and that volume or network share is unmounted, metadata on the recorded subpath can return `NotFound`. This code treats that result as proof that the source was deleted and removes its cache, despite the documented promise that unmounted sources are retained. The existing test only produces `PermissionDenied` by changing Unix permissions, so it does not cover this case.

### Issue 2
crates/csp/src/indexing/cache.rs:377-379
**Traversal Errors Are Hidden**

`flatten()` silently discards directory-entry errors, while `is_ok_and` silently drops entries whose file type cannot be read. A transient filesystem error can therefore leave an orphan untouched while the command still reports success and may print `No orphaned indexes found`, making an incomplete sweep indistinguishable from a complete one.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(cli): make `csp clear orphans` sweep..." | Re-trigger Greptile

Comment thread crates/csp/src/indexing/cache.rs
Comment thread crates/csp/src/indexing/cache.rs Outdated
- Deserialize only `sourceId` from the manifest via a typed struct instead
  of building a full `serde_json::Value` tree (gemini-code-assist).
- Propagate directory-traversal and file-type errors from the index root
  instead of silently skipping entries, so an incomplete sweep cannot
  report "No orphaned indexes found" (greptile P2).
- Stop claiming that sources on an unmounted volume are retained: their
  path is simply absent and is swept like upstream. Docs, READMEs, and
  the drift reference now describe the actual guard, which is
  `PermissionDenied`/other I/O errors, not unmounts (greptile P1).
@amondnet

amondnet commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new clear orphans command to remove cached indexes whose local source paths no longer exist. It implements the clear_orphan_indexes function in cache.rs, integrates it into the CLI command dispatcher, updates the documentation, and adds comprehensive unit tests. The feedback suggests improving the error context when traversing the index root fails by including the path of the index root in the error message.

Comment thread crates/csp/src/indexing/cache.rs Outdated
Wrap the `read_dir` failure on the index root with its path, matching the
per-entry errors introduced in the previous commit (gemini-code-assist).
@amondnet

amondnet commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the csp clear orphans command, which removes cached indexes whose recorded local source paths no longer exist. It includes comprehensive unit tests and documentation updates. The review feedback suggests optimizing the manifest parsing in read_manifest_source_id by streaming the JSON directly from a file reader instead of reading the entire file into memory, which prevents potential memory spikes on large manifest files.

Comment thread crates/csp/src/indexing/cache.rs
@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

@amondnet
amondnet merged commit f9b5440 into main Sep 4, 2026
13 checks passed
@amondnet
amondnet deleted the amondnet/parity-add-csp-clear-orphans-to-remove-cached-in branch September 4, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parity(#243): add csp clear orphans to remove cached indexes whose source path no longer exists

1 participant