Skip to content

Windows: profile domain detection never matches — cwd_to_project_id lowercases the id but profile projects keep original casing (hollow query_methodology, remember stores domain "") #95

Description

@mbe14

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_domaindetect_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions