-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommand_init.go
More file actions
148 lines (136 loc) · 5.3 KB
/
Copy pathcommand_init.go
File metadata and controls
148 lines (136 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package contexting
import (
"bufio"
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
)
func newInitCommand() *cobra.Command {
flags := CommonFlags{}
cmd := &cobra.Command{
Use: "init [path]",
Short: "Build context index and write context JSON",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var absConfigPath string
if configPath != "" {
var err error
absConfigPath, err = filepath.Abs(configPath)
if err != nil {
return fmt.Errorf("resolve config path: %w", err)
}
}
cfg, err := LoadContextingConfig(absConfigPath)
if err != nil {
return err
}
applyCommonConfig(cmd, &flags, cfg.Common)
flags.normalize()
// If config was just created, pause so user can edit settings before indexing
if configJustCreated {
fmt.Println()
fmt.Printf("Config created at %s. You can customize settings before indexing begins:\n", configPath)
fmt.Println(" - synonyms: number of synonyms per name (default: 10)")
fmt.Println(" - batch_size: names per LLM request (default: auto)")
fmt.Println(" - llm_model: LLM model for synonym generation")
fmt.Println(" - ignore: paths to exclude from indexing")
fmt.Println()
if isInteractiveTerminal() {
fmt.Print("Edit .ctxt/ctx_config.toml now, then press Enter to continue... ")
reader := bufio.NewReader(os.Stdin)
_, _ = reader.ReadString('\n')
}
// Re-read config so any edits are picked up
cfg, err = LoadContextingConfig(absConfigPath)
if err != nil {
return err
}
applyCommonConfig(cmd, &flags, cfg.Common)
flags.normalize()
}
rootPath := "."
if len(args) == 1 {
rootPath = args[0]
} else if cfg.Init.RootPath != "" {
rootPath = cfg.Init.RootPath
}
absRoot, err := filepath.Abs(rootPath)
if err != nil {
return err
}
outputPath := resolveProjectPath(absRoot, flags.OutputPath)
cachePath := resolveProjectPath(absRoot, flags.SynonymCache)
ignored, err := BuildIgnoreMapForRoot(absRoot, flags.ExtraIgnores)
if err != nil {
return err
}
EmbedDotWhitelist(ignored, BuildDotWhitelist(cfg.Common.DotWhitelist))
// Only skip internal files by basename when the resolved paths are inside the project.
if isInsideProject(absConfigPath, absRoot) {
ignored[filepath.Base(absConfigPath)] = true
ignored[filepath.Base(absConfigPath)+".example"] = true
}
if isInsideProject(outputPath, absRoot) {
ignored[filepath.Base(outputPath)] = true
}
llmEndpoint, llmModel, llmKey, llmTemp, llmMaxTokens, llmProvider := resolveLLMConfig(flags, cfg.LLM)
LogInfof("LLM: provider=%s model=%s endpoint=%s api_key=%s", llmProvider, llmModel, llmEndpoint, maskAPIKey(llmKey))
cache, err := LoadSynonymCache(cachePath)
if err != nil {
return err
}
if llmKey == "" {
LogWarnf("LLM API key not configured; continuing without synonyms")
}
ctx, stop := signalAwareContext()
defer stop()
result, err := BuildIndex(BuildOptions{
Ctx: ctx,
RootPath: rootPath,
IgnoredPaths: ignored,
APIKey: llmKey,
Model: llmModel,
BatchSize: flags.BatchSize,
SynonymsPerName: flags.SynonymsPerName,
SynonymsMin: flags.SynonymsMin,
SynonymsMax: flags.SynonymsMax,
SynonymCache: cache,
MaxBatchSize: cfg.Watch.MaxBatchSize,
Endpoint: llmEndpoint,
Temperature: llmTemp,
MaxTokens: llmMaxTokens,
ParallelRequests: cfg.LLM.ParallelRequests,
Verbose: flags.Verbose,
})
if err != nil {
return err
}
if ctx.Err() != nil {
return ctx.Err()
}
emitSynonymWarning(result.SynonymError)
if err := SaveSynonymCache(cachePath, result.SynonymCache); err != nil {
return err
}
if err := SaveContextIndex(outputPath, result.Index); err != nil {
return err
}
LogInfof("Indexed %d nodes (%d files, %d directories). Synonyms on %d nodes.", result.Stats.TotalNodes, result.Stats.TotalFiles, result.Stats.TotalDirs, result.Stats.SynonymNodes)
LogInfof("Collected %d unique names. Wrote %s", result.Stats.CollectedNames, outputPath)
return nil
},
}
cmd.Flags().StringVarP(&flags.OutputPath, "output", "o", ".ctxt/ctx_index.json", "Output JSON path")
cmd.Flags().StringVar(&flags.Model, "llm-model", "", "LLM model used for synonym generation")
cmd.Flags().StringVar(&flags.APIKey, "api-key", "", "LLM API key (falls back to config api_key_env, LLM_API_KEY, OPENROUTER_API_KEY)")
cmd.Flags().StringVar(&flags.Endpoint, "llm-endpoint", "", "LLM API endpoint URL")
cmd.Flags().IntVar(&flags.BatchSize, "batch-size", 8, "Names per LLM request")
cmd.Flags().IntVar(&flags.SynonymsPerName, "synonyms", defaultSynonyms, "Synonyms per name (fallback for min/max)")
cmd.Flags().IntVar(&flags.SynonymsMin, "synonyms-min", 0, "Min synonyms per name (0 = use synonyms value)")
cmd.Flags().IntVar(&flags.SynonymsMax, "synonyms-max", 0, "Max synonyms per name (0 = use synonyms value)")
cmd.Flags().StringVar(&flags.SynonymCache, "synonym-cache", ".ctxt/ctx_cache.json", "Path to persistent synonym cache JSON")
cmd.Flags().StringSliceVar(&flags.ExtraIgnores, "ignore", nil, "Additional ignore entries (name or relative path)")
cmd.Flags().BoolVarP(&flags.Verbose, "verbose", "v", false, "Enable verbose logging")
return cmd
}