@@ -93,7 +93,7 @@ pub struct Config {
9393 #[ serde( default = "default_timeout_secs" ) ]
9494 pub timeout_secs : u64 ,
9595
96- /// LLM temperature (0.0-2 .0, default 0.3)
96+ /// LLM temperature (0.0-1 .0, default 0.3)
9797 #[ serde( default = "default_temperature" ) ]
9898 pub temperature : f32 ,
9999
@@ -430,7 +430,7 @@ impl Config {
430430
431431 if !( 0.0 ..=2.0 ) . contains ( & self . temperature ) {
432432 return Err ( Error :: Config ( format ! (
433- "temperature must be 0.0–2 .0, got {}" ,
433+ "temperature must be 0.0–1 .0, got {}" ,
434434 self . temperature
435435 ) ) ) ;
436436 }
@@ -485,81 +485,9 @@ impl Config {
485485 fs:: create_dir_all ( & dir) ?;
486486
487487 let path = dir. join ( "config.toml" ) ;
488- let content = r#"# CommitBee Configuration
488+ let content = Self :: generate_default_config ( ) ;
489489
490- # LLM provider: ollama, openai, anthropic
491- provider = "ollama"
492-
493- # Model name (for Ollama, use `ollama list` to see available)
494- model = "qwen3.5:4b"
495-
496- # Ollama server URL
497- ollama_host = "http://localhost:11434"
498-
499- # Maximum lines of diff to include in prompt
500- max_diff_lines = 500
501-
502- # Maximum lines per file in diff
503- max_file_lines = 100
504-
505- # Maximum tokens to generate (default 256)
506- # Increase to 8192+ if using thinking models with think = true
507- # num_predict = 256
508-
509- # Enable thinking/reasoning for Ollama models (default: false)
510- # When enabled, models like qwen3 will reason before responding.
511- # Requires higher num_predict (8192+) to accommodate thinking tokens.
512- # think = false
513-
514- # Maximum context characters for LLM prompt (~4 chars per token)
515- # Increase for larger models (e.g., 48000 for 16K context)
516- # max_context_chars = 24000
517-
518- # Rename detection similarity threshold (0-100, default 70)
519- # Set to 0 to disable rename detection
520- # rename_threshold = 70
521-
522- # Language for commit message generation (ISO 639-1 code)
523- # Type and scope remain in English per Conventional Commits spec.
524- # locale = "de"
525-
526- # Custom secret patterns (additional regex patterns for secret scanning)
527- # custom_secret_patterns = ["CUSTOM_KEY_[a-zA-Z0-9]{32}"]
528-
529- # Disable built-in secret patterns by name
530- # disabled_secret_patterns = ["Generic Secret (unquoted)"]
531-
532- # Experimental: learn commit style from repository history (default: false)
533- # Analyzes recent commits to learn scope naming, type patterns, and
534- # subject phrasing conventions for the repository.
535- # learn_from_history = false
536-
537- # Number of recent commits to sample for style learning (default: 50)
538- # history_sample_size = 50
539-
540- # Exclude files matching glob patterns from analysis and diff context
541- # Excluded files are listed in output but not sent to the LLM.
542- # exclude_patterns = ["*.lock", "**/*.generated.*"]
543-
544- # Custom system prompt file (overrides built-in prompt)
545- # system_prompt_path = "/path/to/system_prompt.txt"
546-
547- # Custom user prompt template file (supports {{diff}}, {{symbols}}, {{files}} variables)
548- # template_path = "/path/to/template.txt"
549-
550- # Commit message format options
551- [format]
552- # Include body/description in commit message
553- include_body = true
554-
555- # Include scope in commit type, e.g., feat(scope): subject
556- include_scope = true
557-
558- # Enforce lowercase first character of subject (conventional commits best practice)
559- lowercase_subject = true
560- "# ;
561-
562- fs:: write ( & path, content) ?;
490+ fs:: write ( & path, & content) ?;
563491
564492 // Set secure permissions (0600)
565493 #[ cfg( unix) ]
@@ -572,4 +500,228 @@ lowercase_subject = true
572500
573501 Ok ( path)
574502 }
503+
504+ /// Generate the default config TOML string with descriptive comments.
505+ ///
506+ /// Values are pulled from `Config::default()` so the template never
507+ /// drifts from the struct defaults. Comments are maintained alongside
508+ /// field descriptors in a single list.
509+ pub fn generate_default_config ( ) -> String {
510+ let default = Config :: default ( ) ;
511+ let table: toml:: Table = {
512+ let s = toml:: to_string ( & default) . expect ( "Config serializes to TOML" ) ;
513+ toml:: from_str ( & s) . expect ( "round-trips as TOML table" )
514+ } ;
515+
516+ /// Whether to show the field commented-out (advanced/optional settings)
517+ /// or active (core settings the user should see immediately).
518+ #[ derive( Clone , Copy ) ]
519+ enum Show {
520+ Active ,
521+ CommentedOut ,
522+ }
523+
524+ struct Field {
525+ key : & ' static str ,
526+ comment : & ' static str ,
527+ show : Show ,
528+ /// Override value for Option/Vec fields that serialize to nothing
529+ example : Option < & ' static str > ,
530+ }
531+
532+ let fields: & [ Field ] = & [
533+ Field {
534+ key : "provider" ,
535+ comment : "LLM provider: ollama, openai, anthropic" ,
536+ show : Show :: Active ,
537+ example : None ,
538+ } ,
539+ Field {
540+ key : "model" ,
541+ comment : "Model name (for Ollama, use `ollama list` to see available)" ,
542+ show : Show :: Active ,
543+ example : None ,
544+ } ,
545+ Field {
546+ key : "ollama_host" ,
547+ comment : "Ollama server URL" ,
548+ show : Show :: Active ,
549+ example : None ,
550+ } ,
551+ Field {
552+ key : "max_diff_lines" ,
553+ comment : "Maximum lines of diff to include in prompt" ,
554+ show : Show :: Active ,
555+ example : None ,
556+ } ,
557+ Field {
558+ key : "max_file_lines" ,
559+ comment : "Maximum lines per file in diff" ,
560+ show : Show :: Active ,
561+ example : None ,
562+ } ,
563+ Field {
564+ key : "num_predict" ,
565+ comment : "Maximum tokens to generate\n \
566+ Increase to 8192+ if using thinking models with think = true",
567+ show : Show :: CommentedOut ,
568+ example : None ,
569+ } ,
570+ Field {
571+ key : "think" ,
572+ comment : "Enable thinking/reasoning for Ollama models\n \
573+ When enabled, models like qwen3 will reason before responding.\n \
574+ Requires higher num_predict (8192+) to accommodate thinking tokens.",
575+ show : Show :: CommentedOut ,
576+ example : None ,
577+ } ,
578+ Field {
579+ key : "max_context_chars" ,
580+ comment : "Maximum context characters for LLM prompt (~4 chars per token)\n \
581+ Increase for larger models (e.g., 48000 for 16K context)",
582+ show : Show :: CommentedOut ,
583+ example : None ,
584+ } ,
585+ Field {
586+ key : "timeout_secs" ,
587+ comment : "Request timeout in seconds" ,
588+ show : Show :: CommentedOut ,
589+ example : None ,
590+ } ,
591+ Field {
592+ key : "temperature" ,
593+ comment : "LLM temperature (0.0-1.0, default 0.3)" ,
594+ show : Show :: CommentedOut ,
595+ example : None ,
596+ } ,
597+ Field {
598+ key : "rename_threshold" ,
599+ comment : "Rename detection similarity threshold (0-100)\n \
600+ Set to 0 to disable rename detection",
601+ show : Show :: CommentedOut ,
602+ example : None ,
603+ } ,
604+ Field {
605+ key : "locale" ,
606+ comment : "Language for commit message generation (ISO 639-1 code)\n \
607+ Type and scope remain in English per Conventional Commits spec.",
608+ show : Show :: CommentedOut ,
609+ example : Some ( "\" de\" " ) ,
610+ } ,
611+ Field {
612+ key : "custom_secret_patterns" ,
613+ comment : "Custom secret patterns (additional regex patterns for secret scanning)" ,
614+ show : Show :: CommentedOut ,
615+ example : Some ( "[\" CUSTOM_KEY_[a-zA-Z0-9]{32}\" ]" ) ,
616+ } ,
617+ Field {
618+ key : "disabled_secret_patterns" ,
619+ comment : "Disable built-in secret patterns by name" ,
620+ show : Show :: CommentedOut ,
621+ example : Some ( "[\" Generic Secret (unquoted)\" ]" ) ,
622+ } ,
623+ Field {
624+ key : "learn_from_history" ,
625+ comment : "Experimental: learn commit style from repository history\n \
626+ Analyzes recent commits to learn scope naming, type patterns, and\n \
627+ subject phrasing conventions for the repository.",
628+ show : Show :: CommentedOut ,
629+ example : None ,
630+ } ,
631+ Field {
632+ key : "history_sample_size" ,
633+ comment : "Number of recent commits to sample for style learning" ,
634+ show : Show :: CommentedOut ,
635+ example : None ,
636+ } ,
637+ Field {
638+ key : "exclude_patterns" ,
639+ comment : "Exclude files matching glob patterns from analysis and diff context\n \
640+ Excluded files are listed in output but not sent to the LLM.",
641+ show : Show :: CommentedOut ,
642+ example : Some ( "[\" *.lock\" , \" **/*.generated.*\" ]" ) ,
643+ } ,
644+ Field {
645+ key : "openai_base_url" ,
646+ comment : "Base URL for OpenAI-compatible APIs" ,
647+ show : Show :: CommentedOut ,
648+ example : Some ( "\" https://api.openai.com/v1\" " ) ,
649+ } ,
650+ Field {
651+ key : "anthropic_base_url" ,
652+ comment : "Base URL for Anthropic API" ,
653+ show : Show :: CommentedOut ,
654+ example : Some ( "\" https://api.anthropic.com/v1\" " ) ,
655+ } ,
656+ Field {
657+ key : "system_prompt_path" ,
658+ comment : "Custom system prompt file (overrides built-in prompt)" ,
659+ show : Show :: CommentedOut ,
660+ example : Some ( "\" /path/to/system_prompt.txt\" " ) ,
661+ } ,
662+ Field {
663+ key : "template_path" ,
664+ comment : "Custom user prompt template file\n \
665+ Supports {{diff}}, {{symbols}}, {{files}} variables",
666+ show : Show :: CommentedOut ,
667+ example : Some ( "\" /path/to/template.txt\" " ) ,
668+ } ,
669+ ] ;
670+
671+ let format_fields: & [ ( & str , & str ) ] = & [
672+ ( "include_body" , "Include body/description in commit message" ) ,
673+ (
674+ "include_scope" ,
675+ "Include scope in commit type, e.g., feat(scope): subject" ,
676+ ) ,
677+ (
678+ "lowercase_subject" ,
679+ "Enforce lowercase first character of subject (conventional commits best practice)" ,
680+ ) ,
681+ ] ;
682+
683+ let mut out = String :: from ( "# CommitBee Configuration\n " ) ;
684+
685+ for field in fields {
686+ out. push ( '\n' ) ;
687+ for line in field. comment . lines ( ) {
688+ out. push_str ( "# " ) ;
689+ out. push_str ( line) ;
690+ out. push ( '\n' ) ;
691+ }
692+
693+ // Resolve the display value: serialized default, or example for Option/Vec fields
694+ let val_str = if let Some ( v) = table. get ( field. key ) {
695+ // f32 round-trips through f64 in TOML and gains precision noise;
696+ // format from the struct directly for float fields
697+ if v. is_float ( ) {
698+ format ! ( "{} = {}" , field. key, default . temperature)
699+ } else {
700+ format ! ( "{} = {v}" , field. key)
701+ }
702+ } else if let Some ( ex) = field. example {
703+ format ! ( "{} = {ex}" , field. key)
704+ } else {
705+ continue ;
706+ } ;
707+
708+ if matches ! ( field. show, Show :: CommentedOut ) {
709+ out. push_str ( "# " ) ;
710+ }
711+ out. push_str ( & val_str) ;
712+ out. push ( '\n' ) ;
713+ }
714+
715+ // [format] section
716+ out. push_str ( "\n # Commit message format options\n [format]\n " ) ;
717+ if let Some ( toml:: Value :: Table ( fmt) ) = table. get ( "format" ) {
718+ for ( key, comment) in format_fields {
719+ if let Some ( v) = fmt. get ( * key) {
720+ out. push_str ( & format ! ( "# {comment}\n {key} = {v}\n " ) ) ;
721+ }
722+ }
723+ }
724+
725+ out
726+ }
575727}
0 commit comments