| 1 | // The provider-visible memory index: scope-qualified references for every |
| 2 | // active fact, override annotations for shadowed global guidance. |
| 3 | package memory |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "sort" |
| 8 | "strings" |
| 9 | ) |
| 10 | |
| 11 | // Index returns the provider-visible index that loads into the cached |
| 12 | // prefix: every active fact from both scopes with a scope-qualified |
| 13 | // reference, shadowed global facts annotated rather than hidden — the index |
| 14 | // agrees with the project-over-global rule recall enforces (#7995). The |
| 15 | // per-directory MEMORY.md files keep their unqualified format. |
| 16 | func (s Store) Index() string { |
| 17 | memories := s.ListAll() |
| 18 | if len(memories) == 0 { |
| 19 | return "" |
| 20 | } |
| 21 | shadowed := map[string]string{} // global fact ID -> winning project reference |
| 22 | for _, o := range FindOverrides(memories) { |
| 23 | shadowed[o.Global.ID] = providerMemoryReference(o.Project) |
| 24 | } |
| 25 | sort.SliceStable(memories, func(i, j int) bool { |
| 26 | if memories[i].Name != memories[j].Name { |
| 27 | return memories[i].Name < memories[j].Name |
| 28 | } |
| 29 | return NormalizeFactScope(string(memories[i].Scope)) == FactScopeProject |
| 30 | }) |
| 31 | var b strings.Builder |
| 32 | seen := map[string]bool{} // collapse legacy migration duplicates (same qualified ref) |
| 33 | for _, memory := range memories { |
| 34 | if ref := providerMemoryReference(memory); seen[ref] { |
| 35 | continue |
| 36 | } else { |
| 37 | seen[ref] = true |
| 38 | } |
| 39 | b.WriteString(renderQualifiedIndexLine(memory)) |
| 40 | if winner, ok := shadowed[memory.ID]; ok && |
| 41 | NormalizeFactScope(string(memory.Scope)) == FactScopeGlobal { |
| 42 | b.WriteString(" (overridden by " + winner + ")") |
| 43 | } |
| 44 | b.WriteString("\n") |
| 45 | } |
| 46 | return b.String() |
| 47 | } |
| 48 | |
| 49 | // renderQualifiedIndexLine is the provider-index variant of renderIndexLine: |
| 50 | // the link is the scope-qualified reference every memory tool accepts, so a |
| 51 | // name collision across scopes can never be misread. |
| 52 | func renderQualifiedIndexLine(m Memory) string { |
| 53 | marker := "" |
| 54 | if ResolveActivation(m) == ActivationPinned { |
| 55 | marker = " pinned" |
| 56 | } |
| 57 | ref := providerMemoryReference(m) |
| 58 | return fmt.Sprintf("- [%s](%s) — [%s/%s%s] %s", |
| 59 | displayTitle(m.Title, m.Name), ref, |
| 60 | NormalizeFactScope(string(m.Scope)), NormalizeType(string(m.Type)), marker, oneLine(m.Description)) |
| 61 | } |
| 62 |