Skip to content

Add --config CLI flag to specify custom configuration file path#3

Merged
tomohiro-owada merged 4 commits intotomohiro-owada:mainfrom
badri:claude/config-file-loading-011CUq1TFcSG7qjg8hsoSsij
Dec 15, 2025
Merged

Add --config CLI flag to specify custom configuration file path#3
tomohiro-owada merged 4 commits intotomohiro-owada:mainfrom
badri:claude/config-file-loading-011CUq1TFcSG7qjg8hsoSsij

Conversation

@badri
Copy link
Copy Markdown
Contributor

@badri badri commented Nov 5, 2025

User description

Changes:

  • Add flag parsing in cmd/main.go to accept --config parameter
  • Modify config.Load() to accept configPath parameter (defaults to "config.json")
  • Add comprehensive tests for custom config path functionality
  • Update README.md with documentation for --config flag (English and Japanese)

Benefits:

  • Support running multiple instances with different configurations
  • Enable testing different models or chunk sizes easily
  • Allow separate dev/test/prod configurations
  • Follow industry standard CLI patterns (docker, kubectl, git, etc.)

Tests added:

  • TestLoadConfig_CustomPath: Verify custom config loading
  • TestLoadConfig_CustomPath_NotFound: Test fallback to defaults
  • TestLoadConfig_CustomPath_InvalidJSON: Test graceful degradation
  • TestLoadConfig_RelativeAndAbsolutePaths: Test both path types

PR Type

Enhancement, Tests


Description

  • Add --config CLI flag to specify custom configuration file path

  • Modify config.Load() to accept configPath parameter with defaults

  • Add four comprehensive tests for custom config path functionality

  • Update README.md with CLI flag documentation (English and Japanese)


Diagram Walkthrough

flowchart LR
  CLI["CLI Arguments"] -- "--config flag" --> Main["cmd/main.go"]
  Main -- "configPath parameter" --> ConfigLoad["config.Load()"]
  ConfigLoad -- "custom or default path" --> ConfigFile["Configuration File"]
  ConfigFile -- "loaded config" --> App["Application"]
Loading

File Walkthrough

Relevant files
Enhancement
main.go
Add CLI flag parsing for config path                                         

cmd/main.go

  • Import flag package for CLI argument parsing
  • Add flag parsing to accept --config parameter with default value
    config.json
  • Pass parsed configPath to config.Load() function
+6/-1     
config.go
Support dynamic config file path parameter                             

internal/config/config.go

  • Modify Load() function signature to accept configPath string parameter
  • Add empty string check to default to config.json if not provided
  • Update all file operations to use dynamic configPath instead of
    hardcoded constant
  • Only generate template file when using default config.json path
  • Update all log messages to reference the actual config file path
+18/-13 
Tests
config_test.go
Add comprehensive custom config path tests                             

internal/config/config_test.go

  • Update existing tests to pass empty string "" to Load() for default
    behavior
  • Add TestLoadConfig_CustomPath() to verify custom config file loading
    with various settings
  • Add TestLoadConfig_CustomPath_NotFound() to test fallback to defaults
    when file missing
  • Add TestLoadConfig_CustomPath_InvalidJSON() to test graceful
    degradation with invalid JSON
  • Add TestLoadConfig_RelativeAndAbsolutePaths() to verify both relative
    and absolute path support
+155/-2 
Documentation
README.md
Document --config flag in English and Japanese                     

README.md

  • Add English documentation section for --config command-line option
    with usage example
  • Explain use cases for custom config paths (multiple instances,
    testing, dev/test/prod separation)
  • Add MCP server configuration example showing --config flag usage
  • Add Japanese translation of command-line options section with
    equivalent examples and use cases
  • Add Japanese MCP server configuration example with --config flag
+54/-0   

This enhancement allows users to specify multiple document directories
and glob patterns in config.json, providing more flexibility in
organizing and indexing markdown files.

Features:
- Config: Changed from single 'documents_dir' to 'document_patterns' array
- Supports both directory paths and glob patterns (e.g., "./docs/**/*.md")
- Backwards compatible: old 'documents_dir' automatically migrated
- Sync: Uses pattern matching to find files instead of single directory walk
- MCP tools: Updated path validation to work with multiple base directories
- Tests: Added comprehensive tests for pattern expansion and migration
- Documentation: Updated README with examples and pattern syntax

Example config.json:
{
  "document_patterns": [
    "./documents",
    "./notes/**/*.md",
    "./projects/backend/**/*.md"
  ],
  ...
}

Pattern examples:
- "./documents" - All .md files in directory
- "./docs/**/*.md" - Recursive search with **
- "./projects/*/docs/*.md" - Wildcard patterns
- "/path/to/external/docs" - Absolute paths
Changes:
- Add flag parsing in cmd/main.go to accept --config parameter
- Modify config.Load() to accept configPath parameter (defaults to "config.json")
- Add comprehensive tests for custom config path functionality
- Update README.md with documentation for --config flag (English and Japanese)

Benefits:
- Support running multiple instances with different configurations
- Enable testing different models or chunk sizes easily
- Allow separate dev/test/prod configurations
- Follow industry standard CLI patterns (docker, kubectl, git, etc.)

Tests added:
- TestLoadConfig_CustomPath: Verify custom config loading
- TestLoadConfig_CustomPath_NotFound: Test fallback to defaults
- TestLoadConfig_CustomPath_InvalidJSON: Test graceful degradation
- TestLoadConfig_RelativeAndAbsolutePaths: Test both path types
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Nov 5, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Action logging: New config loading paths and outcomes are printed to stderr but there is no structured
audit log of critical actions like config file access success/failure or fallback
decisions with user/context.

Referred Code
// Check if config file exists
if _, err := os.Stat(configPath); os.IsNotExist(err) {
	fmt.Fprintf(os.Stderr, "[INFO] %s not found, using defaults\n", configPath)
	cfg := DefaultConfig()

	// Only generate template if using default path
	if configPath == "config.json" {
		if err := cfg.Save(configPath); err != nil {
			fmt.Fprintf(os.Stderr, "[WARN] Failed to generate config template: %v\n", err)
		} else {
			fmt.Fprintf(os.Stderr, "[INFO] Generated config template: %s\n", configPath)
		}
	}

	return cfg, nil
}

// Read existing config
data, err := os.ReadFile(configPath)
if err != nil {
	return nil, fmt.Errorf("failed to read config: %w", err)


 ... (clipped 11 lines)
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Fallback handling: The loader falls back to defaults on missing/invalid config and logs warnings, but it
returns success without surfacing context to callers and does not validate external input
(path) beyond existence, which may obscure configuration issues.

Referred Code
if _, err := os.Stat(configPath); os.IsNotExist(err) {
	fmt.Fprintf(os.Stderr, "[INFO] %s not found, using defaults\n", configPath)
	cfg := DefaultConfig()

	// Only generate template if using default path
	if configPath == "config.json" {
		if err := cfg.Save(configPath); err != nil {
			fmt.Fprintf(os.Stderr, "[WARN] Failed to generate config template: %v\n", err)
		} else {
			fmt.Fprintf(os.Stderr, "[INFO] Generated config template: %s\n", configPath)
		}
	}

	return cfg, nil
}

// Read existing config
data, err := os.ReadFile(configPath)
if err != nil {
	return nil, fmt.Errorf("failed to read config: %w", err)
}


 ... (clipped 7 lines)
Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured logs: New informational and warning messages are plain text to stderr and include file paths,
lacking structured logging and controls to avoid sensitive data exposure in logs.

Referred Code
	fmt.Fprintf(os.Stderr, "[INFO] %s not found, using defaults\n", configPath)
	cfg := DefaultConfig()

	// Only generate template if using default path
	if configPath == "config.json" {
		if err := cfg.Save(configPath); err != nil {
			fmt.Fprintf(os.Stderr, "[WARN] Failed to generate config template: %v\n", err)
		} else {
			fmt.Fprintf(os.Stderr, "[INFO] Generated config template: %s\n", configPath)
		}
	}

	return cfg, nil
}

// Read existing config
data, err := os.ReadFile(configPath)
if err != nil {
	return nil, fmt.Errorf("failed to read config: %w", err)
}



 ... (clipped 9 lines)
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Path validation: The new configPath input from CLI is used directly without validation or normalization
(e.g., path traversal checks, symlink handling, permissions), and errors may reveal
filesystem paths in logs.

Referred Code
func Load(configPath string) (*Config, error) {
	if configPath == "" {
		configPath = "config.json"
	}

	// Check if config file exists
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		fmt.Fprintf(os.Stderr, "[INFO] %s not found, using defaults\n", configPath)
		cfg := DefaultConfig()

		// Only generate template if using default path
		if configPath == "config.json" {
			if err := cfg.Save(configPath); err != nil {
				fmt.Fprintf(os.Stderr, "[WARN] Failed to generate config template: %v\n", err)
			} else {
				fmt.Fprintf(os.Stderr, "[INFO] Generated config template: %s\n", configPath)
			}
		}

		return cfg, nil
	}


 ... (clipped 5 lines)
  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

…1BK5KtdbNwCE1

Add support for multiple document paths with glob patterns
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Nov 5, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Fail startup on invalid custom config

The application should error and exit if a user-specified configuration file is
not found or contains invalid JSON, rather than silently falling back to default
settings. This "fail-fast" approach prevents unexpected behavior from running
with an unintended configuration.

Examples:

internal/config/config.go [47-61]
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		fmt.Fprintf(os.Stderr, "[INFO] %s not found, using defaults\n", configPath)
		cfg := DefaultConfig()

		// Only generate template if using default path
		if configPath == "config.json" {
			if err := cfg.Save(configPath); err != nil {
				fmt.Fprintf(os.Stderr, "[WARN] Failed to generate config template: %v\n", err)
			} else {
				fmt.Fprintf(os.Stderr, "[INFO] Generated config template: %s\n", configPath)

 ... (clipped 5 lines)
internal/config/config.go [70-74]
	if err := json.Unmarshal(data, cfg); err != nil {
		fmt.Fprintf(os.Stderr, "[WARN] Invalid JSON in %s: %v\n", configPath, err)
		fmt.Fprintf(os.Stderr, "[WARN] Using default configuration\n")
		return cfg, nil
	}

Solution Walkthrough:

Before:

// in internal/config/config.go
func Load(configPath string) (*Config, error) {
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		// If a custom config path is provided and not found...
		if configPath != "config.json" {
			// ...it logs and returns default config without an error.
			return DefaultConfig(), nil
		}
		// ...
	}

	data, err := os.ReadFile(configPath)
	// ...
	cfg := DefaultConfig()
	if err := json.Unmarshal(data, cfg); err != nil {
		// If config is invalid JSON, it logs and returns default config.
		return DefaultConfig(), nil
	}

	return cfg, nil
}

After:

// in internal/config/config.go
func Load(configPath string) (*Config, error) {
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		// If a custom config path is provided and not found...
		if configPath != "config.json" {
			// ...it should return an error.
			return nil, fmt.Errorf("config file not found: %s", configPath)
		}
		// ...
	}

	data, err := os.ReadFile(configPath)
	// ...
	cfg := DefaultConfig()
	if err := json.Unmarshal(data, cfg); err != nil {
		// If config is invalid JSON, it should return an error.
		return nil, fmt.Errorf("invalid JSON in %s: %w", configPath, err)
	}

	return cfg, nil
}
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical design flaw where providing an invalid custom config path fails silently, which could lead to major operational issues like using the wrong database or model.

High
Possible issue
Fail on invalid JSON configuration

When parsing a user-provided configuration file fails due to invalid JSON, the
program should exit with an error instead of silently falling back to default
settings.

internal/config/config.go [70-74]

 	if err := json.Unmarshal(data, cfg); err != nil {
-		fmt.Fprintf(os.Stderr, "[WARN] Invalid JSON in %s: %v\n", configPath, err)
-		fmt.Fprintf(os.Stderr, "[WARN] Using default configuration\n")
-		return cfg, nil
+		return nil, fmt.Errorf("failed to parse config file %s: %w", configPath, err)
 	}
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that silently falling back to default settings on an invalid JSON configuration is poor user experience and proposes a "fail-fast" approach, which is more robust.

Medium
General
Generate config template at custom paths

Modify the logic to generate a template configuration file at the user-specified
path if the file does not exist, instead of only doing so for the default
config.json path.

internal/config/config.go [51-58]

-	// Only generate template if using default path
-	if configPath == "config.json" {
-		if err := cfg.Save(configPath); err != nil {
-			fmt.Fprintf(os.Stderr, "[WARN] Failed to generate config template: %v\n", err)
-		} else {
-			fmt.Fprintf(os.Stderr, "[INFO] Generated config template: %s\n", configPath)
-		}
+	// Attempt to generate a template config file if it doesn't exist.
+	if err := cfg.Save(configPath); err != nil {
+		// Log a warning if saving fails, but continue with defaults.
+		// This handles cases like non-existent directories or permission errors gracefully.
+		fmt.Fprintf(os.Stderr, "[WARN] Could not write config template to %s: %v\n", configPath, err)
+	} else {
+		fmt.Fprintf(os.Stderr, "[INFO] Generated config template: %s\n", configPath)
 	}
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion proposes a valid user experience improvement by creating a template configuration file even at custom paths, which makes the tool's behavior more intuitive and helpful.

Low
  • Update

@tomohiro-owada tomohiro-owada self-requested a review December 15, 2025 09:30
@tomohiro-owada
Copy link
Copy Markdown
Owner

Thanks for this contribution! The --config flag is a very useful feature that will benefit many users.

There are some merge conflicts with the current main branch, but I'll resolve them on my end after merging. No action needed from you.

Merging now - thank you! 🎉

@tomohiro-owada tomohiro-owada merged commit bc8ec12 into tomohiro-owada:main Dec 15, 2025
tomohiro-owada added a commit that referenced this pull request Dec 15, 2025
- Fix undefined variable: configFile -> configPath in config.go
- Fix truncated test: restore complete TestLoadConfig_CustomPath
- Remove unused import: path/filepath from sync.go
- Update integration tests: DocumentsDir -> DocumentPatterns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tomohiro-owada added a commit that referenced this pull request Dec 15, 2025
Fix merge issues from PR #3 (--config CLI flag)
@tomohiro-owada
Copy link
Copy Markdown
Owner

I've resolved the merge conflicts and merged the fix in PR #6. Everything is working now!

Thank you again for your contribution - this feature is a great addition to DevRag. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants