feat(sync): per-file staleness banner — MCP + CLI (closes #66 phase-1)#166
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This was referenced Jul 2, 2026
0b41bc0 to
ef4271e
Compare
Wolfvin
added a commit
that referenced
this pull request
Jul 3, 2026
Issue #173: PR #166 was written when main had 70 commands. After rebasing on latest main (which merged orient #165, affected #142, and others), the runtime COMMAND_REGISTRY now reports 77 commands. Ran sync_command_count.py --apply to regenerate all count references: - README.md: 5 substitutions - SKILL-QUICK.md: 5 substitutions - SKILL.md: 2 substitutions - pyproject.toml: 1 substitution - scripts/graph_model.py: 1 substitution - skill.json: 1 substitution Also resolved rebase conflicts in: - scripts/mcp_server.py: merged Phase 1 (staleness) + Phase 4 (worktree) banner methods — both coexist on the MCP server - scripts/sync/__init__.py: merged worktree (Phase 4) + pending (Phase 1) exports into unified __all__ Pre-existing finding (NOT caused by this PR): the 'affected' command (PR #142, already on main) uses -f for --filter, which conflicts with the global --format/-f flag added by codelens.py. This breaks ALL CLI invocations that trigger argparse registration (e.g. 'codelens affected --help'). Flagged for BOS — separate issue needed.
d865095 to
df47ffb
Compare
Issue #66 Phase 1 — Per-file staleness banner. Detects when indexed files have been edited since the last scan and surfaces a warning to the agent before the tool's actual output. New files: - scripts/sync/__init__.py — re-exports public API - scripts/sync/pending.py — StaleFileDetector (thread-safe, in-memory Dict[str, float] cache, 5s TTL per workspace) + detect_stale_files() + format_staleness_banner() - scripts/commands/staleness.py — 'codelens staleness' CLI command (manual check + full list when MCP banner truncates to 10) - tests/test_staleness.py — 41 tests (detection, cache, thread safety, banner formatting, CLI, MCP integration) - docs/sync/staleness-banner.md — architecture + design decisions Modified: - scripts/mcp_server.py — MCPServer._staleness_detector (lazy) + _attach_staleness_banner() prepends banner to read-tool responses (suppressed on scan/init) + _invalidate_staleness_cache() after successful scan - README/SKILL/SKILL-QUICK/pyproject/skill.json/graph_model.py — sync'd via sync_command_count.py --apply (command count 70 -> 71) Detection algorithm: 1. Load stored mtimes from .codelens/mtimes.json 2. For each indexed file: os.stat() compare (st_size, st_mtime_ns) 3. If mtime differs, re-compute SHA-256 (only when needed) to confirm content actually changed — skips false positives from / M README.md M SKILL-QUICK.md M SKILL.md A docs/sync/staleness-banner.md M pyproject.toml A scripts/commands/staleness.py M scripts/graph_model.py M scripts/mcp_server.py A scripts/sync/__init__.py A scripts/sync/pending.py M skill.json A tests/test_staleness.py of identical content 4. Sort by edit_age ascending (most recent edit first) 5. Return tuple of StaleFile records Why mtimes.json (not SQLite files table) as source of truth? mtimes.json is written by every scan, including workspaces that use the legacy JSON registry (pre-v8.2). The SQLite files table is only populated when the persistent registry is active. Cache: 5s TTL per workspace, thread-safe via threading.Lock. Invalidated after successful scan so next read tool re-probes against fresh index. Banner: plain text (not markdown), prepended to first content block's text + structured response['_staleness'] field. Both paths ensure the warning surfaces regardless of how agents consume tool output. Verified: - tests/test_staleness.py: 41 passed - tests/test_staleness.py + test_command_count.py + test_doctor.py + test_cli.py + test_codelens.py + test_mcp_hooks.py: 220 passed - sync_command_count.py --check: clean - 'codelens staleness' smoke-tested end-to-end (text + json output) Phase 2 (connect-time catch-up), Phase 3 (native file watcher), Phase 5 (anonymous telemetry) deferred ke follow-up issues per issue spec. StaleFileDetector cache + StaleFile data structure designed to accommodate Phase 2 without API change. Note: PR #154 (issue #66 Phase 4 — worktree mismatch) also creates scripts/sync/__init__.py and modifies mcp_server.py. If that PR merges first, this PR will need a small rebase to resolve the __init__.py overlap (both add the same package marker) and the mcp_server.py overlap (both add methods to MCPServer — different method names, no conflict). BOS will resolve at merge time.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #66 (phase 1)
Ringkasan
Phase 1 dari issue #66 — per-file staleness banner. Detects when indexed files have been edited since the last scan dan surfaces a warning to the agent before the tool's actual output. Banner prepended ke semua read-tool MCP responses (suppressed on
scan/init— those are the fix path).Perubahan
New files
scripts/sync/__init__.pyscripts/sync/pending.pyStaleFileDetector(thread-safe, in-memoryDict[str, float]cache, 5s TTL per workspace) +detect_stale_files()+format_staleness_banner()scripts/commands/staleness.pycodelens stalenessCLI command — manual check + full list when MCP banner truncates to 10tests/test_staleness.pydocs/sync/staleness-banner.mdModified
scripts/mcp_server.py—MCPServer._staleness_detector(lazy) +_attach_staleness_banner()prepends banner to read-tool responses (suppressed on scan/init) +_invalidate_staleness_cache()after successful scansync_command_count.py --apply(command count 70 → 71)Detection algorithm
.codelens/mtimes.json(source of truth — written byincremental.save_mtimes()on every scan)os.stat()compare(st_size, st_mtime_ns)touch/git checkoutof identical contentedit_ageascending (most recent edit first)StaleFilerecordsCache
5s TTL per workspace, thread-safe via
threading.Lock. Invalidated after successful scan so next read tool re-probes against fresh index. Exists karena walking 10k+ files on every tool call would add ~50ms latency.Banner shape
Plain text (not markdown) — renders correctly di terminal + MCP content blocks.⚠️ marker untuk agent pattern-matching. "content differs" vs "size/mtime differ" distinguishes hash-confirmed staleness dari mtime-only staleness. Truncates to 10 files dengan "and N more" — full list via
codelens staleness.MCP integration
_attach_staleness_banner()dipanggil di 3 response paths:scan/initscan/init(staleness context lebih useful dari error itself)Banner prepended to first content block's
textAND attached as structuredresponse['_staleness']field. Both paths ensure warning surfaces regardless of how agents consume tool output.After successful
scan,_invalidate_staleness_cache(workspace)called so next read tool re-probes against fresh index.Definition of Done (Phase 1, dari issue)
Dict[str, float](path → edit_timestamp), thread-safe viathreading.Lockos.stat(path)untuk compare(st_size, st_mtime_ns)⚠️ Some files referenced below were edited since the last index sync…banner ke MCP responsescodelens stalenessfor full list)scripts/sync/pending.pyPhase 2 (connect-time catch-up), Phase 3 (native file watcher), Phase 5 (anonymous telemetry) deferred ke follow-up issues per issue spec.
Verifikasi
python3 -m pytest tests/test_staleness.py -v→ 41 passedpython3 -m pytest tests/test_staleness.py tests/test_command_count.py tests/test_doctor.py tests/test_cli.py tests/test_codelens.py tests/test_mcp_hooks.py→ 220 passed, 0 failedpython3 scripts/sync_command_count.py --check→ cleancodelens stalenesssmoke-tested end-to-end (text + json output, --no-confirm-hash flag)Design decisions
scripts/sync/subpackage — staleness dan worktree mismatch (Phase 4, PR feat(sync): worktree index mismatch detection — doctor check + MCP banner (closes #66 phase-4) #154) are independent concerns sharing only the "index vs working tree" theme. Package keeps them discoverable without forcing them into one file._stalenessfield AND prepended text — different agents consume tool output differently. Both paths ensure warning surfaces.content_hash_changedtri-state (True/False/None) — True = hash confirmed stale, False =touch(skip), None = no stored hash available ("size/mtime differ").Findings (di luar scope)
test_codelensignore.py::TestBackwardCompat::test_actual_target_dir_is_ignoredfails diorigin/main(pre-existing, bukan regresi dari PR ini).scripts/sync/__init__.pydan modifiesmcp_server.py. If PR feat(sync): worktree index mismatch detection — doctor check + MCP banner (closes #66 phase-4) #154 merges first, this PR needs a small rebase untuk resolve__init__.pyoverlap (both add same package marker) danmcp_server.pyoverlap (both add methods to MCPServer — different method names, no conflict). BOS will resolve at merge time.