feat(web): return memory_dir category from server (closes #299)#301
Merged
feat(web): return memory_dir category from server (closes #299)#301
Conversation
The memory_dirs widget previously classified each entry client-side with a regex mirror of ``_detect_provider_dirs`` — every new provider needed two matching edits with no tooling guard. Move classification to the server so the widget just reads ``entry.category`` from ``GET /api/memory-dirs/status``. - ``config.categorize_memory_dir`` is the stateless per-path classifier. Pattern rows live in ``_PROVIDER_CATEGORY_PATTERNS`` and ``PROVIDER_DIR_CATEGORIES`` is derived from the same table, so adding a category can't silently diverge. - ``memory_dir_stats`` emits ``category`` on every entry alongside the existing ``path`` / ``chunk_count`` / ``source_file_count`` / ``exists`` fields. No endpoint/route change — the handler is a passthrough. - ``sources-memory-dirs.js`` drops ``_categorizeMemoryDir``. Presentation constants (order, i18n label keys, default-collapsed set) stay on the client by design — new providers still need the client-side ordering + i18n edits plus the server pattern row. - Drift guard: ``TestDetectProviderDirsRoundtrip`` asserts every path ``_detect_provider_dirs`` returns classifies back to the same category via ``categorize_memory_dir``. Turns the "single source of truth" claim from a code-review invariant into a CI-enforced one. Classification timing changes from synchronous to async-on-status-fetch. Before the first ``/api/memory-dirs/status`` response, every entry renders under ``user`` and then settles into its group on the next render. Locally the fetch is sub-100ms so the flicker is not visible, but call it out so the behaviour shift isn't invisible in review. Follow-up to #300 / #297. Co-Authored-By: Claude <[email protected]>
``handleReindexGroup`` filtered candidate paths via ``_categorizeMemoryDir(d)`` — the previous commit removed that function when consolidating classification server-side but missed this caller, so clicking the per-group ↻ button threw ``ReferenceError: _categorizeMemoryDir is not defined`` and aborted silently. Mirror the render-loop fallback pattern: read ``category`` from ``statusByPath[d]`` with a ``'user'`` default. Audited with ``grep -r _categorizeMemoryDir`` post-fix: zero references. Cache-bust bumped (``v=4`` → ``v=5``) so returning users pick up the fix without a manual hard refresh. Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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 #299. Follow-up to PR #300 / #297.
Scope
Consolidates the
memory_dirclassification (regex mirror) into asingle server-side source. Presentation dicts (group order, i18n label
keys, default-collapsed set) stay on the client by design — new
providers still need the client-side ordering + i18n edits plus the
server pattern row. This PR only eliminates the duplicated regex.
Changes
config.categorize_memory_dir(path)— stateless per-path classifier.Returns one of
PROVIDER_DIR_CATEGORIESor"user"._PROVIDER_CATEGORY_PATTERNS— single source of truth for thecategory-to-regex mapping.
PROVIDER_DIR_CATEGORIESis now derived from the same patterntable instead of a parallel hard-coded tuple, so adding a category
can't open a new drift seam.
_detect_provider_dirsdelegates per-path classification to thehelper (buckets by
categorize_memory_dir(p)), so discovery andclassification lock onto the same pattern table.
memory_dir_stats(indexing/engine.py) emitscategoryon everyentry. The
/api/memory-dirs/statushandler is an untouchedpassthrough.
sources-memory-dirs.jsdrops_categorizeMemoryDirand readsstatusByPath[path].category. Drift-mirror warnings removed fromboth
config.pydocstring and the JS file header.Timing note (intentional behaviour change)
Classification was synchronous in the client; it is now async. Before
the first
/api/memory-dirs/statusresponse — first paint, or atransient fetch error — every entry falls back to
'user'and thensettles into its group on the next render. Locally the fetch is
sub-100ms so the flicker is not user-visible, but flagging it here so
the async shift isn't invisible in review.
Tests
TestCategorizeMemoryDir(8 cases): all four categories, trailing-slash normalisation, user fallback, look-alike paths that must NOT
match (
/archive/.codex/memories-backup,/work/claude/plans/foo),Pathinput, and an assertion thatPROVIDER_DIR_CATEGORIESequalsthe derived tuple.
TestDetectProviderDirsRoundtrip— drift guard: every path_detect_provider_dirsreturns must classify back to the samecategory via
categorize_memory_dir. Without this the "single sourceof truth" claim is code-review-only; this makes it CI-enforced
(framing per
feedback_silent_policy_enforcement_gap.md).TestMemoryDirStats::test_category_reflects_provider_layout— mix ofprovider-shaped and user paths produces the right
categoryon eachentry.
Full suite: 1855 passed, 0 regressions. Ruff + mypy clean on touched files.
Out of scope
~/.gemini/GEMINI.md) — still excluded: single-file surface,secrets in parent directory.
/api/memory-dirs/status— staysuntyped dict-through; typing is its own sweep.
the helper docstring so a future Windows pass knows where to look.
Test plan
uv run pytest -m "not ollama"greenuv run ruff check/ruff format --checkcleanuv run mypyadvisory clean on touched filesuv run mm web→ Sources tab → confirm groupsrender correctly (Claude projects / Claude plans / Codex / User),
DevTools Network shows
categoryon each entry of/api/memory-dirs/status, adding a user path lands it in theusergroup, default-collapsed state unchanged
🤖 Generated with Claude Code