From 0630d3673491cc898eccb424a6caad950683e146 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 23 May 2026 18:33:12 -0500 Subject: [PATCH] Implemented the CLI override extraction cleanup identified in the code audit --- internal/cli/process_flags.go | 121 ++++++++ internal/cli/process_flags_test.go | 433 +++++++++++++++++++++++++++++ internal/cli/run.go | 70 +---- 3 files changed, 555 insertions(+), 69 deletions(-) create mode 100644 internal/cli/process_flags.go create mode 100644 internal/cli/process_flags_test.go diff --git a/internal/cli/process_flags.go b/internal/cli/process_flags.go new file mode 100644 index 0000000..e87bf85 --- /dev/null +++ b/internal/cli/process_flags.go @@ -0,0 +1,121 @@ +package cli + +import ( + "flag" + + "gitea.maximumdirect.net/eric/audita/internal/core/config" +) + +type processOverrideBinding func(*config.CLIOverrides, processFlags) + +var processOverrideBindings = map[string]processOverrideBinding{ + "modules": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ModulesCSV = flags.modules + }, + "output-schema": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.OutputSchema = flags.outputSchema + }, + "llm-api-key": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.PrimaryLLMAPIKey = flags.llmAPIKey + }, + "validation-llm-api-key": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ValidationLLMAPIKey = flags.validationLLMAPIKey + }, + "model": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.PrimaryModel = flags.model + }, + "validation-model": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ValidationModel = flags.validationModel + }, + "base-url": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.PrimaryBaseURL = flags.baseURL + }, + "validation-base-url": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ValidationBaseURL = flags.validationBaseURL + }, + "llm-timeout-seconds": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.PrimaryLLMTimeoutSeconds = flags.llmTimeoutSeconds + }, + "total-llm-concurrency": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.TotalLLMConcurrency = flags.totalLLMConcurrency + }, + "proposal-llm-concurrency": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ProposalLLMConcurrency = flags.proposalLLMConcurrency + }, + "llm-concurrency": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.PrimaryLLMConcurrency = flags.llmConcurrency + }, + "validation-llm-timeout-seconds": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ValidationLLMTimeoutSeconds = flags.validationLLMTimeoutSeconds + }, + "max-retries": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.MaxRetries = flags.maxRetries + }, + "validation-max-retries": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ValidationMaxRetries = flags.validationMaxRetries + }, + "validation-llm-concurrency": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ValidationLLMConcurrency = flags.validationLLMConcurrency + }, + "validation-max-prompt-tokens": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.ValidationMaxPromptTokens = flags.validationMaxPromptTokens + }, + "max-section-tokens": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.MaxSectionTokens = flags.maxSectionTokens + }, + "min-section-tokens": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.MinSectionTokens = flags.minSectionTokens + }, + "target-sections": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.TargetSections = flags.targetSections + }, + "glossary-confidence-threshold": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.GlossaryConfidenceThreshold = flags.glossaryConfidenceThreshold + }, + "grammar-confidence-threshold": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.GrammarConfidenceThreshold = flags.grammarConfidenceThreshold + }, + "homophones-confidence-threshold": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.HomophonesConfidenceThreshold = flags.homophonesConfidenceThreshold + }, + "spoken-word-confidence-threshold": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.SpokenWordConfidenceThreshold = flags.spokenWordConfidenceThreshold + }, + "normalize-max-segment-gap": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.NormalizeMaxSegmentGap = flags.normalizeMaxSegmentGap + }, + "normalize-ellipsis-gap": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.NormalizeEllipsisGap = flags.normalizeEllipsisGap + }, + "normalize-max-segment-duration": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.NormalizeMaxSegmentDuration = flags.normalizeMaxSegmentDuration + }, + "normalize-max-segment-tokens": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.NormalizeMaxSegmentTokens = flags.normalizeMaxSegmentTokens + }, + "transcript-description": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.TranscriptDescription = flags.transcriptDescription + }, + "work-dir": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.WorkDir = flags.workDir + }, + "work-dir-retention": func(overrides *config.CLIOverrides, flags processFlags) { + overrides.WorkDirRetention = flags.workDirRetention + }, +} + +func processCLIOverrides(fs *flag.FlagSet, flags processFlags) (config.CLIOverrides, bool) { + overrides := config.CLIOverrides{} + explicitModules := false + fs.Visit(func(f *flag.Flag) { + if f.Name == "modules" { + explicitModules = true + } + binding, ok := processOverrideBindings[f.Name] + if !ok { + return + } + binding(&overrides, flags) + }) + return overrides, explicitModules +} diff --git a/internal/cli/process_flags_test.go b/internal/cli/process_flags_test.go new file mode 100644 index 0000000..309ce78 --- /dev/null +++ b/internal/cli/process_flags_test.go @@ -0,0 +1,433 @@ +package cli + +import ( + "io" + "reflect" + "strings" + "testing" + + "gitea.maximumdirect.net/eric/audita/internal/core/config" +) + +func TestProcessCLIOverridesMapsEveryConfigMutatingFlag(t *testing.T) { + tests := []struct { + name string + flagName string + value string + wantExplicitModules bool + assertOverrideFields func(t *testing.T, overrides config.CLIOverrides) + }{ + { + name: "modules", + flagName: "modules", + value: "grammar,glossary", + wantExplicitModules: true, + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "ModulesCSV", overrides.ModulesCSV, "grammar,glossary") + }, + }, + { + name: "output schema", + flagName: "output-schema", + value: "audita-v1", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "OutputSchema", overrides.OutputSchema, "audita-v1") + }, + }, + { + name: "primary api key", + flagName: "llm-api-key", + value: "primary-key", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "PrimaryLLMAPIKey", overrides.PrimaryLLMAPIKey, "primary-key") + }, + }, + { + name: "validation api key", + flagName: "validation-llm-api-key", + value: "validation-key", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "ValidationLLMAPIKey", overrides.ValidationLLMAPIKey, "validation-key") + }, + }, + { + name: "primary model", + flagName: "model", + value: "primary-model", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "PrimaryModel", overrides.PrimaryModel, "primary-model") + }, + }, + { + name: "validation model", + flagName: "validation-model", + value: "validation-model", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "ValidationModel", overrides.ValidationModel, "validation-model") + }, + }, + { + name: "primary base url", + flagName: "base-url", + value: "https://primary.example.test", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "PrimaryBaseURL", overrides.PrimaryBaseURL, "https://primary.example.test") + }, + }, + { + name: "validation base url", + flagName: "validation-base-url", + value: "https://validation.example.test", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "ValidationBaseURL", overrides.ValidationBaseURL, "https://validation.example.test") + }, + }, + { + name: "primary timeout", + flagName: "llm-timeout-seconds", + value: "101", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "PrimaryLLMTimeoutSeconds", overrides.PrimaryLLMTimeoutSeconds, 101) + }, + }, + { + name: "total concurrency", + flagName: "total-llm-concurrency", + value: "5", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "TotalLLMConcurrency", overrides.TotalLLMConcurrency, 5) + }, + }, + { + name: "proposal concurrency", + flagName: "proposal-llm-concurrency", + value: "3", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "ProposalLLMConcurrency", overrides.ProposalLLMConcurrency, 3) + }, + }, + { + name: "legacy concurrency alias", + flagName: "llm-concurrency", + value: "4", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "PrimaryLLMConcurrency", overrides.PrimaryLLMConcurrency, 4) + }, + }, + { + name: "validation timeout", + flagName: "validation-llm-timeout-seconds", + value: "202", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "ValidationLLMTimeoutSeconds", overrides.ValidationLLMTimeoutSeconds, 202) + }, + }, + { + name: "max retries", + flagName: "max-retries", + value: "6", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "MaxRetries", overrides.MaxRetries, 6) + }, + }, + { + name: "validation max retries", + flagName: "validation-max-retries", + value: "7", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "ValidationMaxRetries", overrides.ValidationMaxRetries, 7) + }, + }, + { + name: "validation concurrency", + flagName: "validation-llm-concurrency", + value: "8", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "ValidationLLMConcurrency", overrides.ValidationLLMConcurrency, 8) + }, + }, + { + name: "validation max prompt tokens", + flagName: "validation-max-prompt-tokens", + value: "4096", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "ValidationMaxPromptTokens", overrides.ValidationMaxPromptTokens, 4096) + }, + }, + { + name: "max section tokens", + flagName: "max-section-tokens", + value: "9000", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "MaxSectionTokens", overrides.MaxSectionTokens, 9000) + }, + }, + { + name: "min section tokens", + flagName: "min-section-tokens", + value: "1000", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "MinSectionTokens", overrides.MinSectionTokens, 1000) + }, + }, + { + name: "target sections", + flagName: "target-sections", + value: "12", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "TargetSections", overrides.TargetSections, 12) + }, + }, + { + name: "glossary threshold", + flagName: "glossary-confidence-threshold", + value: "0.91", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertFloatOverride(t, "GlossaryConfidenceThreshold", overrides.GlossaryConfidenceThreshold, 0.91) + }, + }, + { + name: "grammar threshold", + flagName: "grammar-confidence-threshold", + value: "0.92", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertFloatOverride(t, "GrammarConfidenceThreshold", overrides.GrammarConfidenceThreshold, 0.92) + }, + }, + { + name: "homophones threshold", + flagName: "homophones-confidence-threshold", + value: "0.93", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertFloatOverride(t, "HomophonesConfidenceThreshold", overrides.HomophonesConfidenceThreshold, 0.93) + }, + }, + { + name: "spoken word threshold", + flagName: "spoken-word-confidence-threshold", + value: "0.94", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertFloatOverride(t, "SpokenWordConfidenceThreshold", overrides.SpokenWordConfidenceThreshold, 0.94) + }, + }, + { + name: "normalize max segment gap", + flagName: "normalize-max-segment-gap", + value: "1.2", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertFloatOverride(t, "NormalizeMaxSegmentGap", overrides.NormalizeMaxSegmentGap, 1.2) + }, + }, + { + name: "normalize ellipsis gap", + flagName: "normalize-ellipsis-gap", + value: "2.3", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertFloatOverride(t, "NormalizeEllipsisGap", overrides.NormalizeEllipsisGap, 2.3) + }, + }, + { + name: "normalize max segment duration", + flagName: "normalize-max-segment-duration", + value: "45.6", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertFloatOverride(t, "NormalizeMaxSegmentDuration", overrides.NormalizeMaxSegmentDuration, 45.6) + }, + }, + { + name: "normalize max segment tokens", + flagName: "normalize-max-segment-tokens", + value: "321", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertIntOverride(t, "NormalizeMaxSegmentTokens", overrides.NormalizeMaxSegmentTokens, 321) + }, + }, + { + name: "transcript description", + flagName: "transcript-description", + value: "podcast episode", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "TranscriptDescription", overrides.TranscriptDescription, "podcast episode") + }, + }, + { + name: "work dir", + flagName: "work-dir", + value: "/tmp/custom-audita", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "WorkDir", overrides.WorkDir, "/tmp/custom-audita") + }, + }, + { + name: "work dir retention", + flagName: "work-dir-retention", + value: "always", + assertOverrideFields: func(t *testing.T, overrides config.CLIOverrides) { + assertStringOverride(t, "WorkDirRetention", overrides.WorkDirRetention, "always") + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + fs, flags := newProcessFlagSet(config.Default(), io.Discard) + if err := fs.Parse([]string{"--" + tc.flagName, tc.value}); err != nil { + t.Fatalf("parse flag: %v", err) + } + + overrides, explicitModules := processCLIOverrides(fs, flags) + if explicitModules != tc.wantExplicitModules { + t.Fatalf("explicitModules=%v, want %v", explicitModules, tc.wantExplicitModules) + } + tc.assertOverrideFields(t, overrides) + }) + } +} + +func TestProcessCLIOverridesIgnoresNonConfigFlags(t *testing.T) { + fs, flags := newProcessFlagSet(config.Default(), io.Discard) + if err := fs.Parse([]string{ + "--config", "/tmp/config.yml", + "--glossary", "/tmp/glossary.yml", + "--output", "/tmp/output.json", + "--report-json", "/tmp/report.json", + }); err != nil { + t.Fatalf("parse flags: %v", err) + } + + overrides, explicitModules := processCLIOverrides(fs, flags) + if explicitModules { + t.Fatal("non-config flags should not mark modules explicit") + } + assertNoCLIOverrides(t, overrides) +} + +func TestNewProcessFlagSetDefaultsReflectEffectiveConfig(t *testing.T) { + cfg := config.Default() + cfg.Modules = []string{"grammar", "glossary"} + cfg.OutputSchema = "audita-v1" + cfg.PrimaryLLM.APIKey = "primary-key" + cfg.ValidationLLM.APIKey = "validation-key" + cfg.PrimaryLLM.Model = "primary-model" + cfg.ValidationLLM.Model = "validation-model" + cfg.PrimaryLLM.BaseURL = "https://primary.example.test" + cfg.ValidationLLM.BaseURL = "https://validation.example.test" + cfg.PrimaryLLM.TimeoutSeconds = 101 + cfg.TotalLLMConcurrency = 5 + cfg.ProposalLLMConcurrency = 3 + cfg.PrimaryLLM.MaxRetries = 6 + cfg.ValidationMaxPromptTokens = 4096 + cfg.MaxSectionTokens = 9000 + cfg.MinSectionTokens = 1000 + cfg.Thresholds.Glossary = 0.91 + cfg.Thresholds.Grammar = 0.92 + cfg.Thresholds.Homophones = 0.93 + cfg.Thresholds.SpokenWord = 0.94 + cfg.Normalization.MaxSegmentGap = 1.2 + cfg.Normalization.EllipsisGap = 2.3 + cfg.Normalization.MaxSegmentDuration = 45.6 + cfg.Normalization.MaxSegmentTokens = 321 + cfg.TranscriptDescription = "podcast episode" + cfg.WorkDir = "/tmp/custom-audita" + cfg.WorkDirRetention = config.WorkDirRetentionAlways + + validationTimeout := 202 + validationRetries := 7 + validationConcurrency := 8 + targetSections := 12 + cfg.ValidationLLM.TimeoutSeconds = &validationTimeout + cfg.ValidationLLM.MaxRetries = &validationRetries + cfg.ValidationLLMConcurrency = &validationConcurrency + cfg.TargetSections = &targetSections + + _, flags := newProcessFlagSet(cfg, io.Discard) + + assertStringOverride(t, "modules default", flags.modules, "grammar,glossary") + assertStringOverride(t, "output schema default", flags.outputSchema, "audita-v1") + assertStringOverride(t, "primary api key default", flags.llmAPIKey, "primary-key") + assertStringOverride(t, "validation api key default", flags.validationLLMAPIKey, "validation-key") + assertStringOverride(t, "primary model default", flags.model, "primary-model") + assertStringOverride(t, "validation model default", flags.validationModel, "validation-model") + assertStringOverride(t, "primary base url default", flags.baseURL, "https://primary.example.test") + assertStringOverride(t, "validation base url default", flags.validationBaseURL, "https://validation.example.test") + assertIntOverride(t, "primary timeout default", flags.llmTimeoutSeconds, 101) + assertIntOverride(t, "total concurrency default", flags.totalLLMConcurrency, 5) + assertIntOverride(t, "proposal concurrency default", flags.proposalLLMConcurrency, 3) + assertIntOverride(t, "legacy concurrency alias default", flags.llmConcurrency, 5) + assertIntOverride(t, "validation timeout default", flags.validationLLMTimeoutSeconds, validationTimeout) + assertIntOverride(t, "max retries default", flags.maxRetries, 6) + assertIntOverride(t, "validation max retries default", flags.validationMaxRetries, validationRetries) + assertIntOverride(t, "validation concurrency default", flags.validationLLMConcurrency, validationConcurrency) + assertIntOverride(t, "validation max prompt tokens default", flags.validationMaxPromptTokens, 4096) + assertIntOverride(t, "max section tokens default", flags.maxSectionTokens, 9000) + assertIntOverride(t, "min section tokens default", flags.minSectionTokens, 1000) + assertIntOverride(t, "target sections default", flags.targetSections, targetSections) + assertFloatOverride(t, "glossary threshold default", flags.glossaryConfidenceThreshold, 0.91) + assertFloatOverride(t, "grammar threshold default", flags.grammarConfidenceThreshold, 0.92) + assertFloatOverride(t, "homophones threshold default", flags.homophonesConfidenceThreshold, 0.93) + assertFloatOverride(t, "spoken word threshold default", flags.spokenWordConfidenceThreshold, 0.94) + assertFloatOverride(t, "normalize max segment gap default", flags.normalizeMaxSegmentGap, 1.2) + assertFloatOverride(t, "normalize ellipsis gap default", flags.normalizeEllipsisGap, 2.3) + assertFloatOverride(t, "normalize max segment duration default", flags.normalizeMaxSegmentDuration, 45.6) + assertIntOverride(t, "normalize max segment tokens default", flags.normalizeMaxSegmentTokens, 321) + assertStringOverride(t, "transcript description default", flags.transcriptDescription, "podcast episode") + assertStringOverride(t, "work dir default", flags.workDir, "/tmp/custom-audita") + assertStringOverride(t, "work dir retention default", flags.workDirRetention, "always") +} + +func TestNewProcessFlagSetUsesFallbackDefaultsForUnsetOptionalConfig(t *testing.T) { + cfg := config.Default() + + _, flags := newProcessFlagSet(cfg, io.Discard) + + assertIntOverride(t, "validation timeout fallback", flags.validationLLMTimeoutSeconds, cfg.PrimaryLLM.TimeoutSeconds) + assertIntOverride(t, "validation retries fallback", flags.validationMaxRetries, cfg.PrimaryLLM.MaxRetries) + assertIntOverride(t, "validation concurrency fallback", flags.validationLLMConcurrency, cfg.TotalLLMConcurrency) + assertIntOverride(t, "target sections fallback", flags.targetSections, 0) +} + +func assertStringOverride(t *testing.T, name string, got *string, want string) { + t.Helper() + if got == nil || *got != want { + t.Fatalf("%s=%v, want %q", name, pointerValue(got), want) + } +} + +func assertIntOverride(t *testing.T, name string, got *int, want int) { + t.Helper() + if got == nil || *got != want { + t.Fatalf("%s=%v, want %d", name, pointerValue(got), want) + } +} + +func assertFloatOverride(t *testing.T, name string, got *float64, want float64) { + t.Helper() + if got == nil || *got != want { + t.Fatalf("%s=%v, want %v", name, pointerValue(got), want) + } +} + +func assertNoCLIOverrides(t *testing.T, overrides config.CLIOverrides) { + t.Helper() + value := reflect.ValueOf(overrides) + typ := value.Type() + for i := 0; i < value.NumField(); i++ { + field := value.Field(i) + if field.Kind() != reflect.Ptr { + t.Fatalf("unexpected non-pointer CLIOverrides field %s", typ.Field(i).Name) + } + if !field.IsNil() { + t.Fatalf("expected no CLI overrides, field %s was set", typ.Field(i).Name) + } + } +} + +func pointerValue[T any](ptr *T) any { + if ptr == nil { + return "" + } + if stringer, ok := any(*ptr).(interface{ String() string }); ok { + return strings.TrimSpace(stringer.String()) + } + return *ptr +} diff --git a/internal/cli/run.go b/internal/cli/run.go index cdb0298..243f156 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -420,75 +420,7 @@ func runProcess(args []string, stdout, stderr io.Writer) int { return 2 } - overrides := config.CLIOverrides{} - explicitModules := false - fs.Visit(func(f *flag.Flag) { - switch f.Name { - case "modules": - explicitModules = true - overrides.ModulesCSV = pFlags.modules - case "output-schema": - overrides.OutputSchema = pFlags.outputSchema - case "llm-api-key": - overrides.PrimaryLLMAPIKey = pFlags.llmAPIKey - case "validation-llm-api-key": - overrides.ValidationLLMAPIKey = pFlags.validationLLMAPIKey - case "model": - overrides.PrimaryModel = pFlags.model - case "validation-model": - overrides.ValidationModel = pFlags.validationModel - case "base-url": - overrides.PrimaryBaseURL = pFlags.baseURL - case "validation-base-url": - overrides.ValidationBaseURL = pFlags.validationBaseURL - case "llm-timeout-seconds": - overrides.PrimaryLLMTimeoutSeconds = pFlags.llmTimeoutSeconds - case "total-llm-concurrency": - overrides.TotalLLMConcurrency = pFlags.totalLLMConcurrency - case "proposal-llm-concurrency": - overrides.ProposalLLMConcurrency = pFlags.proposalLLMConcurrency - case "llm-concurrency": - overrides.PrimaryLLMConcurrency = pFlags.llmConcurrency - case "validation-llm-timeout-seconds": - overrides.ValidationLLMTimeoutSeconds = pFlags.validationLLMTimeoutSeconds - case "max-retries": - overrides.MaxRetries = pFlags.maxRetries - case "validation-max-retries": - overrides.ValidationMaxRetries = pFlags.validationMaxRetries - case "validation-llm-concurrency": - overrides.ValidationLLMConcurrency = pFlags.validationLLMConcurrency - case "validation-max-prompt-tokens": - overrides.ValidationMaxPromptTokens = pFlags.validationMaxPromptTokens - case "max-section-tokens": - overrides.MaxSectionTokens = pFlags.maxSectionTokens - case "min-section-tokens": - overrides.MinSectionTokens = pFlags.minSectionTokens - case "target-sections": - overrides.TargetSections = pFlags.targetSections - case "glossary-confidence-threshold": - overrides.GlossaryConfidenceThreshold = pFlags.glossaryConfidenceThreshold - case "grammar-confidence-threshold": - overrides.GrammarConfidenceThreshold = pFlags.grammarConfidenceThreshold - case "homophones-confidence-threshold": - overrides.HomophonesConfidenceThreshold = pFlags.homophonesConfidenceThreshold - case "spoken-word-confidence-threshold": - overrides.SpokenWordConfidenceThreshold = pFlags.spokenWordConfidenceThreshold - case "normalize-max-segment-gap": - overrides.NormalizeMaxSegmentGap = pFlags.normalizeMaxSegmentGap - case "normalize-ellipsis-gap": - overrides.NormalizeEllipsisGap = pFlags.normalizeEllipsisGap - case "normalize-max-segment-duration": - overrides.NormalizeMaxSegmentDuration = pFlags.normalizeMaxSegmentDuration - case "normalize-max-segment-tokens": - overrides.NormalizeMaxSegmentTokens = pFlags.normalizeMaxSegmentTokens - case "transcript-description": - overrides.TranscriptDescription = pFlags.transcriptDescription - case "work-dir": - overrides.WorkDir = pFlags.workDir - case "work-dir-retention": - overrides.WorkDirRetention = pFlags.workDirRetention - } - }) + overrides, explicitModules := processCLIOverrides(fs, pFlags) if err := cfg.ApplyCLIOverrides(overrides); err != nil { fmt.Fprintf(stderr, "audita process: invalid CLI configuration: %v\n", err)