Skip to content

Commit b9a3942

Browse files
committed
Remove legacy cache migration and require current index formats
1 parent d68b190 commit b9a3942

13 files changed

Lines changed: 55 additions & 135 deletions

‎README.md‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,15 @@ Successful LLM responses are saved even if another batch fails. The command
122122
still exits with an error; rerun without `--force` to request only missing,
123123
short, or stale entries. `--batch-size` explicitly controls batch size when set.
124124

125-
The cache automatically migrates to version 2 on the next write. Unique legacy
126-
entries are retained; ambiguous parent/file keys are dropped rather than shared
127-
between unrelated files. Legacy entries have no context fingerprint, so their
128-
first online `init` or `sync` refreshes them. Use `--offline` with `init` to migrate
129-
without LLM requests. Keep the CLI upgraded when sharing these cache files.
125+
Only version 2 caches and indexes are supported. Older files are rejected;
126+
there is no migration or synonym-key fallback. For the default paths, rebuild:
127+
128+
```bash
129+
rm .ctxt/ctx_cache.json
130+
ctxt init .
131+
```
132+
133+
Use `ctxt init . --offline` to rebuild without LLM requests.
130134

131135
Use `init` or restart `watch` after adding files, then run `sync` to fill gaps.
132136

‎cache.go‎

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"os"
77
)
88

9+
const currentCacheVersion = 2
10+
911
type synonymCacheState struct {
1012
Version int `json:"cache_version"`
1113
Entries SynonymResponse `json:"entries"`
@@ -18,7 +20,7 @@ func LoadSynonymCache(path string) (SynonymResponse, error) {
1820
}
1921

2022
func loadSynonymCacheState(path string) (synonymCacheState, error) {
21-
state := synonymCacheState{Entries: make(SynonymResponse), Fingerprints: make(map[string]string)}
23+
state := synonymCacheState{Version: currentCacheVersion, Entries: make(SynonymResponse), Fingerprints: make(map[string]string)}
2224
if path == "" {
2325
return state, nil
2426
}
@@ -29,20 +31,16 @@ func loadSynonymCacheState(path string) (synonymCacheState, error) {
2931
if err != nil {
3032
return state, fmt.Errorf("read synonym cache: %w", err)
3133
}
32-
var fields map[string]json.RawMessage
33-
if err := json.Unmarshal(data, &fields); err != nil {
34-
return state, fmt.Errorf("parse synonym cache: %w", err)
34+
// Decode into a fresh value so a missing version is rejected.
35+
var decoded synonymCacheState
36+
if err := json.Unmarshal(data, &decoded); err != nil {
37+
return state, fmt.Errorf("parse synonym cache: %w; remove the cache file and run ctxt init", err)
3538
}
36-
if version, ok := fields["cache_version"]; ok && len(version) > 0 && version[0] != '[' {
37-
if err := json.Unmarshal(data, &state); err != nil {
38-
return state, fmt.Errorf("parse synonym cache: %w", err)
39-
}
40-
if state.Version != 2 {
41-
return state, fmt.Errorf("unsupported synonym cache version %d", state.Version)
42-
}
43-
} else if err := json.Unmarshal(data, &state.Entries); err != nil {
44-
return state, fmt.Errorf("parse synonym cache: %w", err)
39+
if decoded.Version != currentCacheVersion {
40+
return state, fmt.Errorf("unsupported synonym cache version %d; remove the cache file and run ctxt init", decoded.Version)
4541
}
42+
state = decoded
43+
4644
if state.Entries == nil {
4745
state.Entries = make(SynonymResponse)
4846
}
@@ -59,7 +57,7 @@ func saveSynonymCacheState(path string, state synonymCacheState) error {
5957
if path == "" {
6058
return nil
6159
}
62-
state.Version = 2
60+
state.Version = currentCacheVersion
6361
data, err := json.MarshalIndent(state, "", " ")
6462
if err != nil {
6563
return fmt.Errorf("marshal synonym cache: %w", err)
@@ -68,18 +66,8 @@ func saveSynonymCacheState(path string, state synonymCacheState) error {
6866
}
6967

7068
func SaveSynonymCache(path string, cache SynonymResponse) error {
71-
if path == "" {
72-
return nil
73-
}
7469
if cache == nil {
7570
cache = make(SynonymResponse)
7671
}
77-
bytes, err := json.MarshalIndent(cache, "", " ")
78-
if err != nil {
79-
return fmt.Errorf("marshal synonym cache: %w", err)
80-
}
81-
if err := writeFileAtomic(path, append(bytes, '\n'), 0o600); err != nil {
82-
return fmt.Errorf("write synonym cache: %w", err)
83-
}
84-
return nil
72+
return saveSynonymCacheState(path, synonymCacheState{Entries: cache})
8573
}

‎cache_migration.go‎

Lines changed: 0 additions & 57 deletions
This file was deleted.

‎command_init.go‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ Examples:
128128
SynonymsMin: flags.SynonymsMin,
129129
SynonymsMax: flags.SynonymsMax,
130130
SynonymCache: cacheState.Entries,
131-
CacheVersion: cacheState.Version,
132131
SynonymFingerprints: cacheState.Fingerprints,
133132
MaxBatchSize: cfg.Watch.MaxBatchSize,
134133
Endpoint: llmEndpoint,
@@ -145,7 +144,7 @@ Examples:
145144
}
146145

147146
emitSynonymWarning(result.SynonymError)
148-
if err := saveSynonymCacheState(cachePath, synonymCacheState{Version: 2, Entries: result.SynonymCache, Fingerprints: result.SynonymFingerprints}); err != nil {
147+
if err := saveSynonymCacheState(cachePath, synonymCacheState{Version: currentCacheVersion, Entries: result.SynonymCache, Fingerprints: result.SynonymFingerprints}); err != nil {
149148
return err
150149
}
151150
if err := SaveContextIndex(outputPath, result.Index); err != nil {

‎command_search.go‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package contexting
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
67
"path/filepath"
@@ -42,7 +43,7 @@ Examples:
4243
ctxt search-hints "database" --explain Show score breakdown
4344
ctxt search-hints "upload" --type files Files only (no directories)
4445
ctxt search-hints "config" --limit 20 More results`,
45-
Args: cobra.ExactArgs(1),
46+
Args: cobra.ExactArgs(1),
4647
RunE: func(cmd *cobra.Command, args []string) error {
4748
var absConfigPath string
4849
if configPath != "" {
@@ -121,6 +122,9 @@ Examples:
121122
if !usedMemory {
122123
index, err := LoadContextIndex(indexPath)
123124
if err != nil {
125+
if !errors.Is(err, os.ErrNotExist) {
126+
return err
127+
}
124128
if useMemory {
125129
source = "none"
126130
f := true

‎command_sync.go‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ Examples:
9090
return fmt.Errorf("load synonym cache: %w", err)
9191
}
9292

93-
cacheState = migrateSynonymCache(index.Tree, cacheState)
9493
allNames := CollectNamesForLLM(index.Tree)
9594
if targetPath != "" {
9695
resolved := resolveProjectPath(absRoot, targetPath)

‎index_manager.go‎

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ type IndexManager struct {
6767

6868
index *ContextIndex
6969
cache SynonymResponse
70-
cacheVersion int
7170
fingerprints map[string]string
7271
dirty bool
7372
loaded bool
@@ -207,19 +206,14 @@ func (m *IndexManager) Bootstrap(ctx context.Context) (IndexStats, error) {
207206
return IndexStats{}, err
208207
}
209208
m.cache = state.Entries
210-
m.cacheVersion = state.Version
211209
m.fingerprints = state.Fingerprints
212210

213211
loadedIndex, err := LoadContextIndex(m.outputPath)
214212
if err == nil && loadedIndex != nil && loadedIndex.Tree != nil {
215213
if absRoot, rootErr := filepath.Abs(m.rootPath); rootErr == nil && loadedIndex.RootPath == absRoot {
216-
migrated := state.Version < 2
217-
state = migrateSynonymCache(loadedIndex.Tree, state)
218-
m.cache = state.Entries
219-
m.cacheVersion = state.Version
220214
m.index = loadedIndex
221215
m.loaded = true
222-
m.dirty = migrated
216+
m.dirty = false
223217

224218
// Diff snapshot against filesystem to catch changes made while not running
225219
fsState, fsErr := collectFilesystemState(m.rootPath, m.ignored)
@@ -311,7 +305,6 @@ func (m *IndexManager) Bootstrap(ctx context.Context) (IndexStats, error) {
311305
SynonymsMin: m.synonymsMin,
312306
SynonymsMax: m.synonymsMax,
313307
SynonymCache: m.cache,
314-
CacheVersion: m.cacheVersion,
315308
SynonymFingerprints: m.fingerprints,
316309
MaxBatchSize: m.maxBatchSize,
317310
Endpoint: m.endpoint,
@@ -323,7 +316,6 @@ func (m *IndexManager) Bootstrap(ctx context.Context) (IndexStats, error) {
323316
}
324317
m.index = result.Index
325318
m.cache = result.SynonymCache
326-
m.cacheVersion = 2
327319
m.fingerprints = result.SynonymFingerprints
328320
m.dirty = true
329321
m.loaded = true
@@ -472,7 +464,7 @@ func (m *IndexManager) FlushIfDirty() (bool, error) {
472464
if !m.dirty || m.index == nil {
473465
return false, nil
474466
}
475-
if err := saveSynonymCacheState(m.cachePath, synonymCacheState{Version: 2, Entries: m.cache, Fingerprints: m.fingerprints}); err != nil {
467+
if err := saveSynonymCacheState(m.cachePath, synonymCacheState{Version: currentCacheVersion, Entries: m.cache, Fingerprints: m.fingerprints}); err != nil {
476468
return false, err
477469
}
478470
if err := SaveContextIndex(m.outputPath, m.index); err != nil {

‎indexer.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ type BuildOptions struct {
1818
SynonymsMin int
1919
SynonymsMax int
2020
SynonymCache SynonymResponse
21-
CacheVersion int
2221
SynonymFingerprints map[string]string
2322
MaxBatchSize int
2423
Endpoint string
@@ -87,7 +86,10 @@ func BuildIndex(opts BuildOptions) (*BuildResult, error) {
8786
LogInfof("✓ Built tree: %d files, %d directories%s", stats.TotalFiles, stats.TotalDirs, extraMsg)
8887

8988
names := CollectNamesForLLM(tree)
90-
state := migrateSynonymCache(tree, synonymCacheState{Version: opts.CacheVersion, Entries: cloneSynonymMap(opts.SynonymCache), Fingerprints: opts.SynonymFingerprints})
89+
state := synonymCacheState{Entries: cloneSynonymMap(opts.SynonymCache), Fingerprints: opts.SynonymFingerprints}
90+
if state.Fingerprints == nil {
91+
state.Fingerprints = make(map[string]string)
92+
}
9193
combined := state.Entries
9294
// Sequential execution: extract symbols FIRST, then generate synonyms with symbols context
9395
var symbolCount int

‎node.go‎

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -117,24 +117,10 @@ func BuildTree(rootPath string, ignored map[string]bool) (*Node, error) {
117117
return root, nil
118118
}
119119

120-
func pathSuffix(fullPath string) string {
121-
dir := filepath.Dir(fullPath)
122-
base := filepath.Base(fullPath)
123-
parent := filepath.Base(dir)
124-
// For root-level files, parent is the project directory name (still useful context).
125-
// For deeply nested files, parentDir/basename is usually unique enough.
126-
return parent + "/" + base
127-
}
128-
129-
// llmSynonymKey returns the key used for LLM synonym generation and lookup.
130-
// New indexes use project-relative paths. Legacy nodes retain suffix lookup
131-
// until their cache is migrated.
132-
func llmSynonymKey(node *Node) string {
133-
if node.SynonymKey != "" {
134-
return node.SynonymKey
135-
}
120+
// nodeLexicalName keeps lexical hints focused on the immediate location.
121+
func nodeLexicalName(node *Node) string {
136122
if node.Type == "file" {
137-
return pathSuffix(node.FullPath)
123+
return filepath.Base(filepath.Dir(node.FullPath)) + "/" + filepath.Base(node.FullPath)
138124
}
139125
return filepath.Base(node.FullPath)
140126
}
@@ -150,7 +136,10 @@ func CollectNamesForLLM(tree *Node) []string {
150136
if node == tree {
151137
return
152138
}
153-
name := llmSynonymKey(node)
139+
name := node.SynonymKey
140+
if name == "" {
141+
return
142+
}
154143
if _, ok := seen[name]; ok {
155144
return
156145
}
@@ -168,7 +157,7 @@ func AssignSynonymsToTree(tree *Node, synonyms SynonymResponse, maxPerNode int)
168157
if node == tree {
169158
return
170159
}
171-
node.Synonyms = sanitizeSynonyms(lexicalSynonyms(legacySynonymKey(node)), maxPerNode)
160+
node.Synonyms = sanitizeSynonyms(lexicalSynonyms(nodeLexicalName(node)), maxPerNode)
172161
})
173162
return
174163
}
@@ -177,12 +166,12 @@ func AssignSynonymsToTree(tree *Node, synonyms SynonymResponse, maxPerNode int)
177166
if node == tree {
178167
return
179168
}
180-
name := llmSynonymKey(node)
169+
name := node.SynonymKey
181170
combined := make([]string, 0, maxPerNode+4)
182171
if syns, ok := synonyms[name]; ok {
183172
combined = append(combined, syns...)
184173
}
185-
combined = append(combined, lexicalSynonyms(legacySynonymKey(node))...)
174+
combined = append(combined, lexicalSynonyms(nodeLexicalName(node))...)
186175
node.Synonyms = sanitizeSynonyms(combined, maxPerNode)
187176
})
188177
}

‎node_mutation.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func upsertNodeByRelPath(root *Node, absRoot string, relPath string, isDir bool,
8888
}
8989
size = info.Size()
9090
}
91-
nodeSynonyms := buildNodeSynonyms(filepath.ToSlash(relPath), legacySynonymKey(&Node{FullPath: fullPath, Type: nodeType}), cache, maxSynonyms)
91+
nodeSynonyms := buildNodeSynonyms(filepath.ToSlash(relPath), nodeLexicalName(&Node{FullPath: fullPath, Type: nodeType}), cache, maxSynonyms)
9292

9393
node, ok := parent.Children[name]
9494
if !ok {

0 commit comments

Comments
 (0)