Skip to content

Commit f991643

Browse files
akoclaude
andcommitted
fix(describe): only trust a fresh catalog when auto-detecting type
resolveViaCatalog read the cached catalog unconditionally, so after an edit (the .mpr mod-time changed) auto-detect could resolve a renamed/deleted name to a stale type. Gate the catalog lookup on catalogMatchesProject (same MPR path + unchanged mod-time, mirroring isCacheValid); on a mismatch, fall back to the authoritative live scan. Verified: fresh catalog stays fast (~4s); a touched .mpr falls back to the live scan (~8.5s) and still resolves correctly. Note: mod-time can't detect a project's unsaved in-memory changes (MCP/PED backend) — that broader catalog-staleness gap is tracked separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4c0ccc6 commit f991643

1 file changed

Lines changed: 35 additions & 0 deletions

File tree

cmd/mxcli/cmd_describe.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ func resolveViaCatalog(projectPath, name string) []string {
380380
}
381381
defer cat.Close()
382382

383+
// Only trust a cache that matches the current project file. A stale cache
384+
// (the .mpr changed since it was built) could resolve a renamed/deleted name
385+
// to the wrong type, so fall back to the authoritative live scan instead.
386+
if !catalogMatchesProject(cat, projectPath) {
387+
return nil
388+
}
389+
383390
q := "'" + strings.ReplaceAll(name, "'", "''") + "'"
384391
var out []string
385392
// Documents (the objects union view).
@@ -397,6 +404,34 @@ func resolveViaCatalog(projectPath, name string) []string {
397404
return out
398405
}
399406

407+
// catalogMatchesProject reports whether the cached catalog was built from the
408+
// current project file and is still up to date — same MPR path and unchanged
409+
// modification time (Unix seconds, mirroring isCacheValid). Any read failure or
410+
// mismatch is treated as "not fresh" so the caller live-scans (safe but slower).
411+
func catalogMatchesProject(cat *catalog.Catalog, projectPath string) bool {
412+
info, err := cat.GetCacheInfo()
413+
if err != nil {
414+
return false
415+
}
416+
fi, err := os.Stat(projectPath)
417+
if err != nil {
418+
return false
419+
}
420+
if info.MprModTime.Unix() != fi.ModTime().Unix() {
421+
return false
422+
}
423+
// Compare absolute paths when both resolve; a path mismatch means the cache
424+
// is for a different .mpr in the same directory.
425+
if info.MprPath != "" {
426+
a, err1 := filepath.Abs(info.MprPath)
427+
b, err2 := filepath.Abs(projectPath)
428+
if err1 == nil && err2 == nil && a != b {
429+
return false
430+
}
431+
}
432+
return true
433+
}
434+
400435
// resolveViaReader scans the live project for the name. Authoritative but slower
401436
// than the catalog (it enumerates documents). Covers top-level documents plus
402437
// entities and associations (including cross-module associations).

0 commit comments

Comments
 (0)