feat: follow symlinked files and directories in the scan and the watcher (#935, #770) - #288
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d7a8cde018
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - **Tracked release:** [`UPSTREAM.md`](UPSTREAM.md) keeps `v1.6.0` until the | ||
| ported rows ship in a release and the downloaded binaries are verified. |
There was a problem hiding this comment.
Restore the verified v1.6.1 audit record
These lines rewrite the dated audit's already-verified conclusion from “advanced to v1.6.1 after v0.51.0 shipped” back to the obsolete pre-release state “keeps v1.6.0,” contradicting the current ledger and its 2026-10-01 closeout. Restore the historical text and place any later correction in a new ledger entry instead of altering append-only evidence.
AGENTS.md reference: docs/AGENTS.md:L19-L21
Useful? React with 👍 / 👎.
| known_dirs.extend(next_links.watched_dirs.iter().cloned()); | ||
| link_state = next_links; | ||
| supplemental = next_supplemental; | ||
| full_sync_pending = true; |
There was a problem hiding this comment.
Arm the debounce when topology alone requires a full sync
When a newly moved-in real directory already contains a symlink, collect_watch_tree sets link_topology_changed, but the directory event adds no source path to pending. This line sets full_sync_pending, while the subsequent deadline is armed only when pending is nonempty, so the promised full reconcile never runs and the linked subtree remains absent until an unrelated event or manual sync.
AGENTS.md reference: AGENTS.md:L39-L41
Useful? React with 👍 / 👎.
| for dir in unwatch { | ||
| // Best effort: a removed link's watch is often already gone. | ||
| let _ = watcher.unwatch(&project_root.join(dir)); |
There was a problem hiding this comment.
Preserve watches when a link becomes a real directory
On per-directory backends, if a followed directory symlink is atomically replaced by a real directory at the same logical path, the event handler first registers watches for the new real tree, but this loop then unwatches every path from the old link state. Because the new link state contains no corresponding link watches, those paths are not re-registered, and later edits below the replacement directory are silently missed.
AGENTS.md reference: AGENTS.md:L39-L41
Useful? React with 👍 / 👎.
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (94.51%) is below the target coverage (95.00%). You can increase the patch coverage or adjust the target coverage. @@ Coverage Diff @@
## main #288 +/- ##
==========================================
- Coverage 95.29% 95.28% -0.02%
==========================================
Files 158 160 +2
Lines 97628 98419 +791
==========================================
+ Hits 93037 93779 +742
- Misses 4591 4640 +49
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
…eject ambiguous link records
Summary
This PR follows symlinked files and directories in the scan (upstream #935) and in the watcher (#770). Following them by default, including targets outside the project, was the owner's decision on 2026-10-01. The design passed the kirocodex plan-convergence review in round 3 (blocking items 6 → 1 → 0).
It also corrects the ledger. The 2026-06-24 sync recorded #935 as ALREADY-HAVE, but the official v0.51.0 binary indexed only
src/real/a.tsfrom{src/real/a.ts, src/afile.ts -> real/a.ts, src/linkdir -> real, src/extlink -> ../../outside/lib}. The scan never followed a symlink.Scan (
crates/codegraph-extract/src/links.rs,engine.rs)read_dirorder..gitor a reserved index root;ScanProjectResultgainslinks,linked_dirsandfile_aliases.Sync stays equal to
index --force(crates/codegraph-watch/src/link_state.rs)project_metadatakeyfollowed_links; no DDL change.Watcher (#770)
CI
macOS Watcherjob runs the symlink scan and watcher tests onmacos-15, the only backend no other job runs. It is a required need ofCI Success, andci-gate.test.shfollows the newneedsline.Docs
docs/cli.md: the scope rules above.UPSTREAM.md: the1.6.0 → 1.6.1callout keeps itsv0.51.0boundary and marks #770 as landed afterwards, and a new dated entry supersedes the #935 row without rewriting it.docs/AGENTS.md's append-only rule.Verification
crates/codegraph-extract/tests/scan_symlinks.rs(8): precedence, the hop-level counterexample from the plan review, skipped targets, ignore rules at the logical path, creation-order independence, aliases, unreadable targets. Against the pre-change scan, 6 failed; the two skip-rule tests are negative controls that hold either way.crates/codegraph-cli/tests/symlink_sync.rs(4, through the real binary): indexing through links, a removed link, same-stat directory and file retargets, and an index without the record. With the re-read rule forced off, the three retarget and record tests fail becausestatusreports nothing modified.symlink_*watcher tests: supplemental selection and cap, canonical-to-logical mapping, the re-registration plan, the removal hint, scan/watch parity (.cache,.codegraph-sources), and, on a real watcher, an edit behind a link, alias fan-out, and link creation, retarget and removal.src/afile.ts,src/extlink/out.tsandsrc/real/a.tswhere v0.51.0 indexed onlysrc/real/a.ts.followed_linksis recorded, andsyncthenstatusreport nothing pending.make pre-ci): 3,927 tests passed, 0 failed; clippy-D warnings, fmt, guardrail, workflow lint,ci-gateharness (11/11) and archive smoke clean.BEGIN_COMMIT_OVERRIDE
feat(scan): follow symlinked files and directories (#935)
fix(sync): re-read files behind new, retargeted or unrecorded symlinks
feat(watch): follow the scan's symlinks with live watches (#770)
END_COMMIT_OVERRIDE
🤖 Generated with Claude Code