Environment
- Cortex 4.5.0 (marketplace install), Windows 11 Pro (26200), Python 3.13.13
- Profile built by
rebuild_profiles: domain c--work-myrepo, 44 sessions, projects: ["C--Work-MyRepo"] (original filesystem casing)
- Project lives at
C:\Work\MyRepo, which is outside every _discover_repos root — so resolve_cwd correctly returns '' and profile-based detection is the deciding path (the common Windows layout whenever code isn't under ~/dev-style roots)
Symptom — same live server, same cwd, A/B on the project argument
query_methodology(cwd="C:\Work\MyRepo")
-> domain: null, confidence: 0, sessionCount: 0,
"Domain \"None\" detected but no profile built yet. Run rebuild_profiles..."
query_methodology(cwd="C:\Work\MyRepo", project="C--Work-MyRepo") # exact casing
-> domain: "c--work-myrepo", confidence: 0.88, sessionCount: 44 # full profile
It also poisons writes: remember(directory="C:\Work\MyRepo") goes through _resolve_domain → detect_domain({"cwd": ...}) and stores the memory with domain: "" (verified live — the stored row's domain is empty), so new memories silently detach from their domain.
Root cause
cwd_to_project_id produces a lowercased id (c--work-myrepo), while profiles.json keeps the project id in original casing (C--Work-MyRepo). Both comparison sites in mcp_server/core/domain_detector.py compare raw:
def _score_project_match(project_id, domain):
...
projects = domain.get("projects") or []
return 1.0 if project_id in projects else 0.0 # never true on Windows
def map_project_to_domain(project_id, profiles):
...
if project_id in projects: # same mismatch
Windows paths are case-insensitive, so on Windows this mismatch is the norm, not an edge case: any capital letter anywhere in the project path kills profile detection for every derived-id caller.
Fix (validated locally on 4.5.0)
Lowercase both sides at the two comparison sites:
def _score_project_match(project_id: str | None, domain: dict) -> float:
if not project_id:
return 0.0
projects = {str(p).lower() for p in (domain.get("projects") or [])}
return 1.0 if project_id.lower() in projects else 0.0
(and the same two lines inside map_project_to_domain). With just this patch on the live 4.5.0 server: query_methodology(cwd=...) with no project argument returns the full profile (0.88 / 44 sessions), and remember stores domain: "c--work-myrepo" again.
An alternative that fixes the class rather than the sites: normalize casing once at profile-write time (store projects pre-lowercased) — but that needs a migration for existing profiles.json; the two-site compare fix is backward-compatible with existing profiles.
Related: #18 was the slug-normalization variant of derived-id drift; this one is pure casing and survives its fix.
Environment
rebuild_profiles: domainc--work-myrepo, 44 sessions,projects: ["C--Work-MyRepo"](original filesystem casing)C:\Work\MyRepo, which is outside every_discover_reposroot — soresolve_cwdcorrectly returns''and profile-based detection is the deciding path (the common Windows layout whenever code isn't under~/dev-style roots)Symptom — same live server, same cwd, A/B on the
projectargumentIt also poisons writes:
remember(directory="C:\Work\MyRepo")goes through_resolve_domain→detect_domain({"cwd": ...})and stores the memory withdomain: ""(verified live — the stored row's domain is empty), so new memories silently detach from their domain.Root cause
cwd_to_project_idproduces a lowercased id (c--work-myrepo), whileprofiles.jsonkeeps the project id in original casing (C--Work-MyRepo). Both comparison sites inmcp_server/core/domain_detector.pycompare raw:Windows paths are case-insensitive, so on Windows this mismatch is the norm, not an edge case: any capital letter anywhere in the project path kills profile detection for every derived-id caller.
Fix (validated locally on 4.5.0)
Lowercase both sides at the two comparison sites:
(and the same two lines inside
map_project_to_domain). With just this patch on the live 4.5.0 server:query_methodology(cwd=...)with noprojectargument returns the full profile (0.88 / 44 sessions), andrememberstoresdomain: "c--work-myrepo"again.An alternative that fixes the class rather than the sites: normalize casing once at profile-write time (store
projectspre-lowercased) — but that needs a migration for existingprofiles.json; the two-site compare fix is backward-compatible with existing profiles.Related: #18 was the slug-normalization variant of derived-id drift; this one is pure casing and survives its fix.