Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .code-reducer.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
model_id: gemma4:26b-a4b-it-qat
model_id: ornith:9b
ollama_base_url: http://localhost:11434
ollama_num_ctx: 20000
docs_dir: wiki
Expand Down Expand Up @@ -47,6 +47,9 @@ ignore:
- go.mod
- screenshots
- examples
- docs
- gh-pages
- LICENSE
- AGENTS.md
- mkdocs.yml
- sonar-project.properties

19 changes: 8 additions & 11 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,27 +97,21 @@ func TestPromptStringList(t *testing.T) {
input := "a, b, c\n"
reader := bufio.NewReader(bytes.NewBufferString(input))

result, modified, err := promptStringList(reader, "Prompt", []string{"default"})
result, err := promptStringList(reader, "Prompt", []string{"default"})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !modified {
t.Errorf("Expected modified to be true")
}
if len(result) != 3 || result[0] != "a" || result[1] != "b" || result[2] != "c" {
t.Errorf("Unexpected result: %v", result)
}

// Test empty input falls back
input = "\n"
reader = bufio.NewReader(bytes.NewBufferString(input))
result, modified, err = promptStringList(reader, "Prompt", []string{"default"})
result, err = promptStringList(reader, "Prompt", []string{"default"})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if modified {
t.Errorf("Expected modified to be false")
}
if len(result) != 1 || result[0] != "default" {
t.Errorf("Unexpected result: %v", result)
}
Expand Down Expand Up @@ -166,7 +160,7 @@ func TestPromptIgnores(t *testing.T) {
func TestLoadInitialSetupConfig(t *testing.T) {
tmp := t.TempDir()
cfg := loadInitialSetupConfig(tmp)
if cfg.ModelID == "" || cfg.OllamaNumCtx <= 0 {
if cfg.ModelID == "" || cfg.OllamaNumCtx <= 0 || cfg.QuickstartPrompt == "" {
t.Fatalf("expected non-empty defaults, got %+v", cfg)
}

Expand All @@ -178,8 +172,8 @@ func TestLoadInitialSetupConfig(t *testing.T) {
t.Fatalf("failed to save config: %v", err)
}
loaded := loadInitialSetupConfig(tmp)
if loaded.ModelID != "custom-model" || loaded.OllamaNumCtx != 8192 {
t.Fatalf("expected custom values, got %+v", loaded)
if loaded.ModelID != "custom-model" || loaded.OllamaNumCtx != 8192 || loaded.QuickstartPrompt != config.DefaultQuickstartPrompt {
t.Fatalf("expected custom values and default prompts, got %+v", loaded)
}
}

Expand All @@ -199,4 +193,7 @@ func TestRunSetupFlow(t *testing.T) {
if cfg.ModelID != config.OllamaDefaultModelID {
t.Errorf("expected default model ID, got %s", cfg.ModelID)
}
if cfg.QuickstartPrompt != config.DefaultQuickstartPrompt {
t.Errorf("expected default quickstart prompt, got %s", cfg.QuickstartPrompt)
}
}
5 changes: 1 addition & 4 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ var initCmd = &cobra.Command{
Short: "Initialize project documentation",
Long: `Scan the repository and build the initial set of wiki markdown pages.`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := executeCommand(engine.ModeInit); err != nil {
return err
}
return nil
return executeCommand(engine.ModeInit)
},
}

Expand Down
49 changes: 15 additions & 34 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func init() {
RootCmd.PersistentFlags().StringVar(&numCtxFlag, "num-ctx", "", "Specify Ollama context window size")
}

func executeCommand(mode engine.Mode) error {
func executeCommand(mode engine.RunMode) error {
repoRoot, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current working directory: %w", err)
Expand All @@ -61,53 +61,34 @@ func executeCommand(mode engine.Mode) error {
}

func checkAndRunSetup(repoRoot string) error {
needsSetup := !config.ConfigExists(repoRoot)
if config.ConfigExists(repoRoot) {
return nil
}
isTTY := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())

if needsSetup {
if !isTTY {
return fmt.Errorf("configuration file %s does not exist in the current directory. Please run 'code-reducer setup' to configure the application", config.ConfigFileName)
}

if err := RunSetupFlow(repoRoot); err != nil {
return err
}
if !isTTY {
return fmt.Errorf("configuration file %s does not exist in the current directory. Please run 'code-reducer setup' to configure the application", config.ConfigFileName)
}
return nil
return RunSetupFlow(repoRoot)
}

func checkInitStatus(repoRoot, docsDir string, mode engine.Mode) error {
func checkInitStatus(repoRoot, docsDir string, mode engine.RunMode) error {
hasInit := engine.IsInitialized(repoRoot, docsDir)

if mode == engine.ModeInit {
if hasInit {
return fmt.Errorf("the project has already been initialized. Please use 'code-reducer update' to refresh documentation")
}
} else if mode == engine.ModeUpdate {
if !hasInit {
return fmt.Errorf("the project has not been initialized yet. Please run 'code-reducer init' first")
}
if mode == engine.ModeInit && hasInit {
return fmt.Errorf("the project has already been initialized. Please use 'code-reducer update' to refresh documentation")
}
if mode == engine.ModeUpdate && !hasInit {
return fmt.Errorf("the project has not been initialized yet. Please run 'code-reducer init' first")
}

return nil
}

func runEngine(repoRoot string, cfg *config.Config, mode engine.Mode) error {
func runEngine(repoRoot string, cfg *config.Config, mode engine.RunMode) error {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

runner := engine.NewRunner(cfg)
err := runner.Run(ctx, repoRoot, mode, func(ev engine.Event) {
if ev.Type == engine.EventStatus {
fmt.Println(ev.Message)
} else if ev.Type == engine.EventError {
fmt.Fprintf(os.Stderr, "Error: %s\n", ev.Message)
} else {
fmt.Println(ev.Message)
}
})

if err != nil {
if err := engine.Run(ctx, repoRoot, cfg, mode, func(msg string) { fmt.Println(msg) }); err != nil {
return fmt.Errorf("documentation run failed: %w", err)
}

Expand Down
113 changes: 20 additions & 93 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,47 +32,10 @@ func init() {

func loadInitialSetupConfig(repoRoot string) *config.Config {
cfg, err := config.LoadConfig(repoRoot)
if err != nil || cfg == nil {
return &config.Config{
ModelID: config.OllamaDefaultModelID,
OllamaBaseURL: config.OllamaDefaultBaseURL,
OllamaNumCtx: config.OllamaDefaultNumCtx,
DocsDir: config.DefaultDocsDir,
ExtractionSteps: config.DefaultExtractionSteps,
SystemPrompt: config.DefaultSystemPrompt,
ModuleSynthesisPrompt: config.DefaultModuleSynthesisPrompt,
ArchitecturePrompt: config.DefaultArchitecturePrompt,
FileFactConsolidationPrompt: config.DefaultFileFactConsolidationPrompt,
}
}

if cfg.ModelID == "" {
cfg.ModelID = config.OllamaDefaultModelID
}
if cfg.OllamaBaseURL == "" {
cfg.OllamaBaseURL = config.OllamaDefaultBaseURL
}
if cfg.OllamaNumCtx <= 0 {
cfg.OllamaNumCtx = config.OllamaDefaultNumCtx
}
if cfg.DocsDir == "" {
cfg.DocsDir = config.DefaultDocsDir
}
if len(cfg.ExtractionSteps) == 0 {
cfg.ExtractionSteps = config.DefaultExtractionSteps
}
if cfg.SystemPrompt == "" {
cfg.SystemPrompt = config.DefaultSystemPrompt
}
if cfg.ModuleSynthesisPrompt == "" {
cfg.ModuleSynthesisPrompt = config.DefaultModuleSynthesisPrompt
}
if cfg.ArchitecturePrompt == "" {
cfg.ArchitecturePrompt = config.DefaultArchitecturePrompt
}
if cfg.FileFactConsolidationPrompt == "" {
cfg.FileFactConsolidationPrompt = config.DefaultFileFactConsolidationPrompt
if err != nil {
cfg = &config.Config{}
}
config.ApplyDefaults(cfg)
return cfg
}

Expand All @@ -88,20 +51,11 @@ func promptContextSize(reader *bufio.Reader, defaultVal int) (int, error) {
}

func promptIgnores(reader *bufio.Reader, defaultIgnores []string) ([]string, error) {
userInputIgnores, ignoresModified, err := promptStringList(reader, "Enter directories, files, or patterns to ignore (comma-separated)", defaultIgnores)
ignores, err := promptStringList(reader, "Enter directories, files, or patterns to ignore (comma-separated)", defaultIgnores)
if err != nil {
return nil, fmt.Errorf("error reading ignores: %w", err)
}
if !ignoresModified {
if defaultIgnores != nil {
return defaultIgnores, nil
}
return []string{}, nil
}
if len(userInputIgnores) > 0 {
return userInputIgnores, nil
}
return []string{}, nil
return ignores, nil
}

// RunSetupFlow guides the user through setting up the configuration file.
Expand Down Expand Up @@ -139,64 +93,37 @@ func runSetupFlowWithReader(reader *bufio.Reader, repoRoot string) error {
return fmt.Errorf("error reading docs dir: %w", err)
}

newCfg := &config.Config{
ModelID: modelInput,
OllamaBaseURL: urlInput,
OllamaNumCtx: numCtx,
DocsDir: docsDirInput,
ExtractionSteps: current.ExtractionSteps,
Ignore: ignores,
SystemPrompt: current.SystemPrompt,
ModuleSynthesisPrompt: current.ModuleSynthesisPrompt,
ArchitecturePrompt: current.ArchitecturePrompt,
FileFactConsolidationPrompt: current.FileFactConsolidationPrompt,
}
current.ModelID = modelInput
current.OllamaBaseURL = urlInput
current.OllamaNumCtx = numCtx
current.DocsDir = docsDirInput
current.Ignore = ignores

err = config.SaveConfig(repoRoot, newCfg)
if err != nil {
if err := config.SaveConfig(repoRoot, current); err != nil {
return fmt.Errorf("error saving configuration: %w", err)
}
fmt.Printf("Configuration successfully saved to local %s file.\n", config.ConfigFileName)
return nil
}

func promptStringList(reader *bufio.Reader, promptMsg string, existingList []string) ([]string, bool, error) {
var result []string
existingStr := ""
if len(existingList) > 0 {
existingStr = strings.Join(existingList, ", ")
}

if existingStr == "" {
fmt.Printf("%s: ", promptMsg)
} else {
fmt.Printf("%s [%s]: ", promptMsg, existingStr)
}

input, err := reader.ReadString('\n')
func promptStringList(reader *bufio.Reader, promptMsg string, existingList []string) ([]string, error) {
input, err := promptString(reader, promptMsg, strings.Join(existingList, ", "))
if err != nil {
return nil, false, err
}

input = strings.TrimSpace(input)
if input == "" {
return existingList, false, nil
return nil, err
}

lowerInput := strings.ToLower(input)
if lowerInput == "clear" || lowerInput == "none" {
return []string{}, true, nil
if input == "" || lowerInput == "clear" || lowerInput == "none" {
return []string{}, nil
}

parts := strings.Split(input, ",")
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
var result []string
for _, part := range strings.Split(input, ",") {
if trimmed := strings.TrimSpace(part); trimmed != "" {
result = append(result, trimmed)
}
}

return result, true, nil
return result, nil
}

func promptString(reader *bufio.Reader, promptMsg, existingVal string) (string, error) {
Expand Down
7 changes: 2 additions & 5 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@ import (

var updateCmd = &cobra.Command{
Use: "update",
Short: "Update project documentation incrementally",
Short: "Incrementally update documentation for modified files using cached SHA-256 hashes.",
Long: `Scan changed files since the last documented commit and update the wiki pages.`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := executeCommand(engine.ModeUpdate); err != nil {
return err
}
return nil
return executeCommand(engine.ModeUpdate)
},
}

Expand Down
Loading
Loading