AddFactWithMeta is AddFactWithSource with explicit confidence and provenance. It deduplicates by content hash (reinforcing on an exact repeat) and, before inserting, reconciles against same-category facts: a near-duplicate rephrasing reinforces the existing fact, and a same-subject update of equal-o
(content, category string, tags []string, sourceProject string, confidence float64, provenance string)
| 126 | // reinforces the existing fact, and a same-subject update of equal-or-higher |
| 127 | // confidence supersedes the stale one instead of piling up a contradiction. |
| 128 | func (fi *FactIndex) AddFactWithMeta(content, category string, tags []string, sourceProject string, confidence float64, provenance string) bool { |
| 129 | content = strings.TrimSpace(content) |
| 130 | if content == "" { |
| 131 | return false |
| 132 | } |
| 133 | if confidence <= 0 { |
| 134 | confidence = defaultConfidence |
| 135 | } |
| 136 | |
| 137 | id := fi.hashContent(content) |
| 138 | |
| 139 | fi.mu.Lock() |
| 140 | defer fi.mu.Unlock() |
| 141 | |
| 142 | // Exact duplicate (same content hash) — reinforce. |
| 143 | if existing, ok := fi.facts[id]; ok { |
| 144 | fi.reinforceLocked(existing, sourceProject, confidence, provenance) |
| 145 | fi.persistLocked() |
| 146 | return false |
| 147 | } |
| 148 | |
| 149 | // Reconcile against near-duplicates / stale same-subject facts. |
| 150 | switch outcome, target := fi.reconcileLocked(content, category, confidence); outcome { |
| 151 | case reconcileReinforce: |
| 152 | fi.reinforceLocked(target, sourceProject, confidence, provenance) |
| 153 | fi.persistLocked() |
| 154 | return false |
| 155 | case reconcileSupersede: |
| 156 | fi.logger.Debug("memory: superseding stale fact", |
| 157 | zap.String("old_id", target.ID), |
| 158 | zap.String("old", target.Content), |
| 159 | zap.String("new", content), |
| 160 | ) |
| 161 | delete(fi.facts, target.ID) |
| 162 | // Tombstone the superseded fact or the shared-file merge re-adopts it |
| 163 | // from disk and the update never sticks. |
| 164 | fi.recordTombstonesLocked(target.ID) |
| 165 | if provenance == "" { |
| 166 | provenance = ProvenanceExtraction |
| 167 | } |
| 168 | provenance += " (supersedes " + target.ID + ")" |
| 169 | } |
| 170 | |
| 171 | fact := &Fact{ |
| 172 | ID: id, |
| 173 | Content: content, |
| 174 | Category: category, |
| 175 | Tags: tags, |
| 176 | CreatedAt: time.Now(), |
| 177 | LastAccessed: time.Now(), |
| 178 | AccessCount: 1, |
| 179 | Score: 1.0, |
| 180 | SourceProject: sourceProject, |
| 181 | Confidence: confidence, |
| 182 | Provenance: provenance, |
| 183 | } |
| 184 | |
| 185 | fi.facts[id] = fact |