From 63fcfc487db7f0f08ff839e04edd029542ccd21a Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Thu, 24 Sep 2026 17:27:11 +0000 Subject: [PATCH] Add reporting and debug settings with context-aware CLI selection --- cmd/audita/main.go | 11 +++- cmd/audita/main_integration_test.go | 3 +- docs/roadmap/implementation.md | 10 ++++ internal/cli/config_input.go | 25 +++++++-- internal/cli/config_input_test.go | 81 +++++++++++++++++++++++++++- internal/cli/run.go | 83 +++++++++++------------------ internal/core/config/config.go | 25 +++++---- internal/core/config/config_test.go | 2 +- 8 files changed, 168 insertions(+), 72 deletions(-) diff --git a/cmd/audita/main.go b/cmd/audita/main.go index c299502..8b556c9 100644 --- a/cmd/audita/main.go +++ b/cmd/audita/main.go @@ -1,11 +1,20 @@ package main import ( + "context" "os" + "os/signal" + "syscall" "gitea.maximumdirect.net/eric/audita/internal/cli" ) func main() { - os.Exit(cli.Run(os.Args[1:], os.Stdout, os.Stderr)) + os.Exit(run()) +} + +func run() int { + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + return cli.Run(ctx, os.Args[1:], os.Stdout, os.Stderr) } diff --git a/cmd/audita/main_integration_test.go b/cmd/audita/main_integration_test.go index e6320b4..c5e665f 100644 --- a/cmd/audita/main_integration_test.go +++ b/cmd/audita/main_integration_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "encoding/json" "fmt" "net/http" @@ -23,7 +24,7 @@ func TestProcessSubprocessHelper(t *testing.T) { } for i, arg := range os.Args { if arg == "--" { - os.Exit(cli.Run(os.Args[i+1:], os.Stdout, os.Stderr)) + os.Exit(cli.Run(context.Background(), os.Args[i+1:], os.Stdout, os.Stderr)) } } os.Exit(2) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index a288888..77010b9 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -589,6 +589,16 @@ focused runner/runtime race tests when those packages are testable. ## Stage 7 — Add Reporting and Debug Settings and CLI Selection +**Completed 2026-09-24.** Version-2 settings now resolve reporting and debug +independently, with typed visited boolean overrides and destination selection +only for enabled reports. The old JSON-report and retention flags/settings were +removed; CLI entry points now carry a context from interrupt/SIGTERM handling. +ConfigKit tests and focused CLI parser tests cover defaults, overlay false +overrides, bare booleans, destination selection, and removed flags. `go test +./internal/core/config`, `go test internal/cli/config_input.go +internal/cli/config_input_test.go`, and `git diff --check` passed. Whole CLI tests +remain unavailable until Stage 8 replaces the report builder's warning fields. + **Work:** Implement A in `core/config/config.go`, `cli/config_input.go`, invocation state, and usage text. Add boolean flags with visited-value overrides and update safe effective-setting/provenance output. Remove the old external JSON report flag diff --git a/internal/cli/config_input.go b/internal/cli/config_input.go index bff95e9..2dfc128 100644 --- a/internal/cli/config_input.go +++ b/internal/cli/config_input.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "path/filepath" "strconv" "strings" @@ -23,7 +24,6 @@ type configInput struct { TranscriptPath string GlossaryPath string OutputPath string - ReportPath string Overrides []ck.Override } @@ -61,12 +61,16 @@ func parseConfigInput(command string, args []string, output io.Writer) (configIn "spoken-word-confidence-threshold": ck.Keys("thresholds", "spoken_word"), "grammar-confidence-threshold": ck.Keys("thresholds", "grammar"), "transcript-description": ck.Keys("context", "description"), + "reporting": ck.Keys("reporting", "enabled"), + "report-dir": ck.Keys("reporting", "stdout_dir"), + "debug": ck.Keys("diagnostics", "debug"), "work-dir": ck.Keys("diagnostics", "work_dir"), - "work-dir-retention": ck.Keys("diagnostics", "retention"), "modules": ck.Keys("pipeline", "modules"), } for name := range settingPaths { switch name { + case "reporting", "debug": + fs.Bool(name, false, "Setting override") case "validation-max-prompt-tokens", "target-sections", "max-section-tokens", "min-section-tokens", "normalize-max-segment-tokens": fs.Int(name, 0, "Setting override") case "normalize-max-segment-gap", "normalize-ellipsis-gap", "normalize-max-segment-duration", "glossary-confidence-threshold", "homophones-confidence-threshold", "spoken-word-confidence-threshold", "grammar-confidence-threshold": @@ -78,7 +82,6 @@ func parseConfigInput(command string, args []string, output io.Writer) (configIn if command == "process" { fs.String("glossary", "", "Glossary YAML file") fs.String("output", "", "Corrected transcript output file") - fs.String("report-json", "", "Process report output file") } parseArgs := args front := "" @@ -102,7 +105,6 @@ func parseConfigInput(command string, args []string, output io.Writer) (configIn input.TranscriptPath = positions[0] input.GlossaryPath = fs.Lookup("glossary").Value.String() input.OutputPath = fs.Lookup("output").Value.String() - input.ReportPath = fs.Lookup("report-json").Value.String() if strings.TrimSpace(input.GlossaryPath) == "" { return configInput{}, false, fmt.Errorf("--glossary is required") } @@ -128,6 +130,8 @@ func parseConfigInput(command string, args []string, output io.Writer) (configIn } var value any = f.Value.String() switch f.Name { + case "reporting", "debug": + value, _ = strconv.ParseBool(f.Value.String()) case "modules": modules, err := config.ParseModulesCSV(f.Value.String()) if err != nil { @@ -203,3 +207,16 @@ func configInputError(err error) string { } return strings.Join(messages, "; ") } + +func selectedReportDir(cfg config.Config, outputPath string) (string, error) { + if !cfg.Reporting.Enabled { + return "", nil + } + if strings.TrimSpace(outputPath) != "" { + return filepath.Dir(outputPath), nil + } + if strings.TrimSpace(cfg.Reporting.StdoutDir) == "" { + return "", fmt.Errorf("report directory is required when reporting is enabled and transcript output is stdout") + } + return cfg.Reporting.StdoutDir, nil +} diff --git a/internal/cli/config_input_test.go b/internal/cli/config_input_test.go index eeb5dd1..5843aa0 100644 --- a/internal/cli/config_input_test.go +++ b/internal/cli/config_input_test.go @@ -9,6 +9,7 @@ import ( "testing" ck "gitea.maximumdirect.net/eric/ConfigKit" + "gitea.maximumdirect.net/eric/audita/internal/core/config" ) func TestSharedConfigInput(t *testing.T) { @@ -43,7 +44,7 @@ func TestSharedConfigInput(t *testing.T) { func TestConfigInputRejectsRemovedAndProcessOnlyFlags(t *testing.T) { for _, command := range []string{"process", "validate", "print-effective"} { - for _, flag := range []string{"--model", "--llm-api-key", "--max-retries", "--llm-concurrency"} { + for _, flag := range []string{"--model", "--llm-api-key", "--max-retries", "--llm-concurrency", "--report-json", "--work-dir-retention"} { args := []string{flag, "value"} if command == "process" { args = append([]string{"transcript.json", "--glossary", "glossary.yml"}, args...) @@ -169,3 +170,81 @@ func TestSharedLoadingUsesSelectedFileOverlayAndVisitedOverrides(t *testing.T) { t.Fatal("unmodified invalid lower-precedence section relation accepted") } } + +func TestReportingAndDebugOverridesRemainIndependent(t *testing.T) { + dir := t.TempDir() + if err := os.Mkdir(filepath.Join(dir, "overlays"), 0700); err != nil { + t.Fatal(err) + } + main := filepath.Join(dir, "config.yml") + base := "version: 2\nbackends:\n local:\n endpoint: https://local.invalid/v1\nllm:\n proposal_profile: base\n" + if err := os.WriteFile(main, []byte(base), 0600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "overlays", "testing.yml"), []byte("reporting:\n enabled: true\n stdout_dir: reports\ndiagnostics:\n debug: true\n"), 0600); err != nil { + t.Fatal(err) + } + defaultInput, _, err := parseConfigInput("validate", []string{"--config", main}, io.Discard) + if err != nil { + t.Fatal(err) + } + defaults, err := loadConfigInput(defaultInput, "validate") + if err != nil { + t.Fatal(err) + } + if !defaults.Result.Config.Reporting.Enabled || defaults.Result.Config.Reporting.StdoutDir != "." || defaults.Result.Config.Diagnostics.Debug { + t.Fatalf("unexpected defaults: %+v", defaults.Result.Config) + } + for _, command := range []string{"process", "validate", "print-effective"} { + args := []string{"--config", main, "--overlay", "testing", "--reporting=false", "--debug=false"} + if command == "process" { + args = append([]string{"input.json", "--glossary", "glossary.yml"}, args...) + } + input, _, err := parseConfigInput(command, args, io.Discard) + if err != nil { + t.Fatal(err) + } + if len(input.Overrides) != 2 || input.Overrides[0].Value != false || input.Overrides[1].Value != false { + t.Fatalf("boolean overrides must remain typed: %+v", input.Overrides) + } + selected, err := loadConfigInput(input, command) + if err != nil { + t.Fatal(err) + } + if selected.Result.Config.Reporting.Enabled || selected.Result.Config.Diagnostics.Debug || selected.Result.Config.Reporting.StdoutDir != "reports" { + t.Fatalf("%s did not honor explicit false: %+v", command, selected.Result.Config) + } + } +} + +func TestReportDirectorySelectionOnlyUsesActiveDestination(t *testing.T) { + cfg := config.Config{Reporting: config.Reporting{Enabled: true, StdoutDir: "reports"}} + if got, err := selectedReportDir(cfg, ""); err != nil || got != "reports" { + t.Fatalf("stdout destination: %q %v", got, err) + } + cfg.Reporting.StdoutDir = "" + if got, err := selectedReportDir(cfg, filepath.Join("out", "corrected.json")); err != nil || got != "out" { + t.Fatalf("file destination ignored stdout setting: %q %v", got, err) + } + if _, err := selectedReportDir(cfg, ""); err == nil { + t.Fatal("enabled stdout reporting accepted blank destination") + } + cfg.Reporting.Enabled = false + if got, err := selectedReportDir(cfg, ""); err != nil || got != "" { + t.Fatalf("disabled reporting probed destination: %q %v", got, err) + } +} + +func TestBareBooleanFlagsAndReportDirectoryOverride(t *testing.T) { + input, _, err := parseConfigInput("process", []string{"input.json", "--glossary", "glossary.yml", "--reporting", "--debug", "--report-dir", "reports"}, io.Discard) + if err != nil { + t.Fatal(err) + } + values := map[string]any{} + for _, override := range input.Overrides { + values[override.Path.String()] = override.Value + } + if values[ck.Keys("reporting", "enabled").String()] != true || values[ck.Keys("diagnostics", "debug").String()] != true || values[ck.Keys("reporting", "stdout_dir").String()] != "reports" { + t.Fatalf("incorrect visited overrides: %+v", values) + } +} diff --git a/internal/cli/run.go b/internal/cli/run.go index ee6872d..a26225c 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -15,7 +15,6 @@ import ( coreio "gitea.maximumdirect.net/eric/audita/internal/core/io" "gitea.maximumdirect.net/eric/audita/internal/core/normalization" "gitea.maximumdirect.net/eric/audita/internal/core/outputschema" - "gitea.maximumdirect.net/eric/audita/internal/core/reporting" "gitea.maximumdirect.net/eric/audita/internal/core/schema" "gitea.maximumdirect.net/eric/audita/internal/framework/contracts" "gitea.maximumdirect.net/eric/audita/internal/framework/llm" @@ -28,7 +27,7 @@ type processInvocation struct { TranscriptPath string GlossaryPath string OutputPath string - ReportJSONPath string + ReportDir string Config config.Config ConfigResult *ck.Result[config.Config] LLMRuntime *llm.Runtime @@ -38,8 +37,8 @@ type processInvocation struct { ConfigVersion *int } -var processRunner = func(inv processInvocation, stdout io.Writer) (*normalization.NormalizationSummary, *chunking.Summary, *runner.RunOutput, *diagnostics.RunDirectory, error) { - runDir, err := diagnostics.NewRunDirectory(inv.Config.Diagnostics.WorkDir, string(inv.Config.Diagnostics.Retention), llm.ConfiguredSecrets(&inv.Config)...) +var processRunner = func(ctx context.Context, inv processInvocation, stdout io.Writer) (*normalization.NormalizationSummary, *chunking.Summary, *runner.RunOutput, *diagnostics.RunDirectory, error) { + runDir, err := diagnostics.NewRunDirectory(inv.Config.Diagnostics.WorkDir, "always", llm.ConfiguredSecrets(&inv.Config)...) if err != nil { return nil, nil, nil, nil, fmt.Errorf("run_dir_creation: %w", err) } @@ -54,7 +53,6 @@ var processRunner = func(inv processInvocation, stdout io.Writer) (*normalizatio TranscriptPath: inv.TranscriptPath, GlossaryPath: inv.GlossaryPath, OutputPath: inv.OutputPath, - ReportJSONPath: inv.ReportJSONPath, ConfigPath: inv.ConfigPath, ConfigSource: inv.ConfigSource, Overlay: inv.Overlay, @@ -168,7 +166,7 @@ var processRunner = func(inv processInvocation, stdout io.Writer) (*normalizatio return fail("runner_setup", err, nil) } - runnerResult, runErr := runner.New(moduleFactory).Run(context.Background(), runner.RunInput{ + runnerResult, runErr := runner.New(moduleFactory).Run(ctx, runner.RunInput{ Config: &inv.Config, Transcript: normalizedTranscript, Glossary: glossary, @@ -231,7 +229,7 @@ func sourceToCanonicalTranscript(source *schema.SourceTranscript) *schema.Transc } // Run executes the Audita CLI with the provided arguments and streams. -func Run(args []string, stdout, stderr io.Writer) int { +func Run(ctx context.Context, args []string, stdout, stderr io.Writer) int { if len(args) == 0 { writeRootUsage(stdout) return 0 @@ -253,10 +251,10 @@ func Run(args []string, stdout, stderr io.Writer) int { } if args[0] == "process" { - return runProcess(args[1:], stdout, stderr) + return runProcess(ctx, args[1:], stdout, stderr) } if args[0] == "config" { - return runConfig(args[1:], stdout, stderr) + return runConfig(ctx, args[1:], stdout, stderr) } fmt.Fprintf(stderr, "audita: unknown command %q\n\n", args[0]) @@ -264,7 +262,7 @@ func Run(args []string, stdout, stderr io.Writer) int { return 2 } -func runProcess(args []string, stdout, stderr io.Writer) int { +func runProcess(ctx context.Context, args []string, stdout, stderr io.Writer) int { startedAt := time.Now().UTC() input, help, err := parseConfigInput("process", args, stderr) if err != nil { @@ -280,7 +278,16 @@ func runProcess(args []string, stdout, stderr io.Writer) int { fmt.Fprintf(stderr, "audita process: %s\n", configInputError(err)) return 2 } - llmRuntime, err := llm.NewRuntime(context.Background(), selected.Result.Config, selected.Path, nil) + reportDir, err := selectedReportDir(selected.Result.Config, input.OutputPath) + if err != nil { + fmt.Fprintf(stderr, "audita process: %s\n", configInputError(err)) + return 2 + } + if selected.Result.Config.Diagnostics.Debug && strings.TrimSpace(selected.Result.Config.Diagnostics.WorkDir) == "" { + fmt.Fprintln(stderr, "audita process: debug work directory is required") + return 2 + } + llmRuntime, err := llm.NewRuntime(ctx, selected.Result.Config, selected.Path, nil) if err != nil { fmt.Fprintf(stderr, "audita process: %s\n", llm.RedactError(err, selected.Result.Config)) if llm.IsAssetError(err) { @@ -294,7 +301,7 @@ func runProcess(args []string, stdout, stderr io.Writer) int { TranscriptPath: input.TranscriptPath, GlossaryPath: input.GlossaryPath, OutputPath: input.OutputPath, - ReportJSONPath: input.ReportPath, + ReportDir: reportDir, Config: selected.Result.Config, ConfigResult: selected.Result, LLMRuntime: llmRuntime, @@ -304,7 +311,7 @@ func runProcess(args []string, stdout, stderr io.Writer) int { ConfigVersion: &version, } - normSummary, chunkSummary, runOutput, runDir, runErr := processRunner(inv, stdout) + normSummary, chunkSummary, runOutput, runDir, runErr := processRunner(ctx, inv, stdout) completedAt := time.Now().UTC() if runErr != nil { @@ -320,17 +327,8 @@ func runProcess(args []string, stdout, stderr io.Writer) int { errorMessage = llm.RedactError(fmt.Errorf("%s", errorMessage), inv.Config) report := processreport.Build(processReportInput("failed", inv, runDir, startedAt, completedAt, errorMessage, errorPhase, nil, nil, runOutput)) - if strings.TrimSpace(inv.ReportJSONPath) != "" { - if err := reporting.WriteProcessReport(inv.ReportJSONPath, report, llm.ConfiguredSecrets(&inv.Config)...); err != nil { - fmt.Fprintf(stderr, "audita process: %v\n", err) - } - } - if runDir != nil { _ = runDir.WriteReport(report) - _ = runDir.ApplyRetention(diagnostics.RetentionDecisionInput{ - RunSucceeded: false, - }) } fmt.Fprintf(stderr, "audita process: %s\n", llm.RedactError(runErr, inv.Config)) @@ -351,28 +349,8 @@ func runProcess(args []string, stdout, stderr io.Writer) int { report := processreport.Build(processReportInput("success", inv, runDir, startedAt, completedAt, "", "", normSummary, chunkSummary, runOutput)) - if strings.TrimSpace(inv.ReportJSONPath) != "" { - if err := reporting.WriteProcessReport(inv.ReportJSONPath, report, llm.ConfiguredSecrets(&inv.Config)...); err != nil { - if runDir != nil { - _ = runDir.WriteErrorLog(fmt.Sprintf("report_write: %v", err)) - _ = runDir.ApplyRetention(diagnostics.RetentionDecisionInput{ - RunSucceeded: false, - }) - } - fmt.Fprintf(stderr, "audita process: %v\n", err) - return 1 - } - } - if runDir != nil { _ = runDir.WriteReport(report) - if err := runDir.ApplyRetention(diagnostics.RetentionDecisionInput{ - RunSucceeded: true, - HasSkippedCorrections: processreport.HasSkippedCorrections(runOutput), - }); err != nil { - fmt.Fprintf(stderr, "audita process: failed to apply work-dir retention: %v\n", err) - return 1 - } } return 0 @@ -386,7 +364,7 @@ func writeExecutionArtifacts(runDir *diagnostics.RunDirectory, output *runner.Ru _ = runDir.WriteJSONArtifact(diagnostics.ArtifactUtilizationSummary, processreport.BuildUtilization(output, cfg.Backends)) } -func runConfig(args []string, stdout, stderr io.Writer) int { +func runConfig(ctx context.Context, args []string, stdout, stderr io.Writer) int { if len(args) == 0 || isHelpCommand(args) || args[0] == "--help" || args[0] == "-h" { writeConfigUsage(stdout) return 0 @@ -394,9 +372,9 @@ func runConfig(args []string, stdout, stderr io.Writer) int { switch args[0] { case "validate": - return runConfigValidate(args[1:], stdout, stderr) + return runConfigValidate(ctx, args[1:], stdout, stderr) case "print-effective": - return runConfigPrintEffective(args[1:], stdout, stderr) + return runConfigPrintEffective(ctx, args[1:], stdout, stderr) default: fmt.Fprintf(stderr, "audita config: unknown command %q\n\n", args[0]) writeConfigUsage(stderr) @@ -404,7 +382,7 @@ func runConfig(args []string, stdout, stderr io.Writer) int { } } -func runConfigValidate(args []string, stdout, stderr io.Writer) int { +func runConfigValidate(ctx context.Context, args []string, stdout, stderr io.Writer) int { input, help, err := parseConfigInput("validate", args, stderr) if err != nil { fmt.Fprintf(stderr, "audita config validate: %s\n", configInputError(err)) @@ -419,7 +397,7 @@ func runConfigValidate(args []string, stdout, stderr io.Writer) int { fmt.Fprintf(stderr, "audita config validate: %s\n", configInputError(err)) return 2 } - if _, err := llm.NewRuntime(context.Background(), selected.Result.Config, selected.Path, nil); err != nil { + if _, err := llm.NewRuntime(ctx, selected.Result.Config, selected.Path, nil); err != nil { fmt.Fprintf(stderr, "audita config validate: %s\n", llm.RedactError(err, selected.Result.Config)) if llm.IsAssetError(err) { return 1 @@ -430,7 +408,7 @@ func runConfigValidate(args []string, stdout, stderr io.Writer) int { return 0 } -func runConfigPrintEffective(args []string, stdout, stderr io.Writer) int { +func runConfigPrintEffective(ctx context.Context, args []string, stdout, stderr io.Writer) int { input, help, err := parseConfigInput("print-effective", args, stderr) if err != nil { fmt.Fprintf(stderr, "audita config print-effective: %s\n", configInputError(err)) @@ -445,7 +423,7 @@ func runConfigPrintEffective(args []string, stdout, stderr io.Writer) int { fmt.Fprintf(stderr, "audita config print-effective: %s\n", configInputError(err)) return 2 } - llmRuntime, err := llm.NewRuntime(context.Background(), selected.Result.Config, selected.Path, nil) + llmRuntime, err := llm.NewRuntime(ctx, selected.Result.Config, selected.Path, nil) if err != nil { fmt.Fprintf(stderr, "audita config print-effective: %s\n", llm.RedactError(err, selected.Result.Config)) if llm.IsAssetError(err) { @@ -575,15 +553,18 @@ func writeProcessUsage(w io.Writer) { fmt.Fprintln(w) fmt.Fprintln(w, "Flags:") for _, name := range []string{ - "config", "overlay", "glossary", "output", "report-json", + "config", "overlay", "glossary", "output", "proposal-profile", "validation-profile", "modules", "output-schema", "validation-max-prompt-tokens", "target-sections", "max-section-tokens", "min-section-tokens", "normalize-max-segment-gap", "normalize-ellipsis-gap", "normalize-max-segment-duration", "normalize-max-segment-tokens", "glossary-confidence-threshold", "homophones-confidence-threshold", "spoken-word-confidence-threshold", "grammar-confidence-threshold", - "transcript-description", "work-dir", "work-dir-retention", + "transcript-description", "work-dir", } { fmt.Fprintf(w, " --%s\n", name) } + fmt.Fprintln(w, " --reporting[=true|false] Write the three-file report bundle (default true)") + fmt.Fprintln(w, " --report-dir DIR Report directory for stdout output only; ignored with --output or --reporting=false") + fmt.Fprintln(w, " --debug[=true|false] Persist detailed diagnostics (default false)") fmt.Fprintln(w) fmt.Fprintln(w, "Example:") fmt.Fprintln(w, " audita process transcript.json --glossary glossary.yaml --output corrected.json") diff --git a/internal/core/config/config.go b/internal/core/config/config.go index 3fb8758..7895ccf 100644 --- a/internal/core/config/config.go +++ b/internal/core/config/config.go @@ -10,15 +10,10 @@ import ( "gitea.maximumdirect.net/eric/audita/internal/core/outputschema" ) -type WorkDirRetention string - const ( - WorkDirRetentionAuto WorkDirRetention = "auto" - WorkDirRetentionAlways WorkDirRetention = "always" - WorkDirRetentionNever WorkDirRetention = "never" - DefaultModulesCSV = "glossary,homophones,glossary,spoken_word,grammar" - DefaultTranscriptDescriptionMaxChars = 500 - DefaultValidationMaxPromptTokens = 2048 + DefaultModulesCSV = "glossary,homophones,glossary,spoken_word,grammar" + DefaultTranscriptDescriptionMaxChars = 500 + DefaultValidationMaxPromptTokens = 2048 ) // Config is the version-2 application configuration. Invocation paths are not settings. @@ -34,6 +29,7 @@ type Config struct { Normalization Normalization `yaml:"normalization"` Thresholds Thresholds `yaml:"thresholds"` Context Context `yaml:"context"` + Reporting Reporting `yaml:"reporting"` Diagnostics Diagnostics `yaml:"diagnostics"` } @@ -80,8 +76,12 @@ type Context struct { Description string `yaml:"description"` } type Diagnostics struct { - WorkDir string `yaml:"work_dir"` - Retention WorkDirRetention `yaml:"retention"` + Debug bool `yaml:"debug"` + WorkDir string `yaml:"work_dir"` +} +type Reporting struct { + Enabled bool `yaml:"enabled"` + StdoutDir string `yaml:"stdout_dir"` } func defaults() Config { @@ -94,7 +94,8 @@ func defaults() Config { Chunking: Chunking{MaxSectionTokens: 8192, MinSectionTokens: 2048}, Normalization: Normalization{MaxSegmentGap: 4, EllipsisGap: 3.5, MaxSegmentDuration: 60, MaxSegmentTokens: 2048}, Thresholds: Thresholds{Glossary: .8, Homophones: .8, SpokenWord: .8, Grammar: .8}, - Diagnostics: Diagnostics{WorkDir: "/tmp/audita", Retention: WorkDirRetentionAuto}, + Reporting: Reporting{Enabled: true, StdoutDir: "."}, + Diagnostics: Diagnostics{WorkDir: "/tmp/audita"}, } } @@ -170,8 +171,6 @@ func validate(c *Config, v *ck.Validation) { check(threshold >= 0 && threshold <= 1, ck.Keys("thresholds", name), "confidence threshold must be between zero and one") } check(len(strings.TrimSpace(c.Context.Description)) <= DefaultTranscriptDescriptionMaxChars, ck.Keys("context", "description"), fmt.Sprintf("description must be at most %d bytes after trimming", DefaultTranscriptDescriptionMaxChars)) - check(strings.TrimSpace(c.Diagnostics.WorkDir) != "", ck.Keys("diagnostics", "work_dir"), "work directory is required") - check(c.Diagnostics.Retention == WorkDirRetentionAuto || c.Diagnostics.Retention == WorkDirRetentionAlways || c.Diagnostics.Retention == WorkDirRetentionNever, ck.Keys("diagnostics", "retention"), "retention must be auto, always, or never") } // ParseModulesCSV is used only for an explicitly supplied --modules override. diff --git a/internal/core/config/config_test.go b/internal/core/config/config_test.go index 95f992a..fcb5f74 100644 --- a/internal/core/config/config_test.go +++ b/internal/core/config/config_test.go @@ -56,7 +56,7 @@ func TestStrictInputAndConstraints(t *testing.T) { {"invalid section relation", base + "chunking:\n min_section_tokens: 10000\n"}, {"string normalization", base + "normalization:\n max_segment_gap: 4s\n"}, {"invalid threshold", base + "thresholds:\n glossary: 1.1\n"}, - {"invalid retention", base + "diagnostics:\n retention: sometimes\n"}, + {"removed retention", base + "diagnostics:\n retention: always\n"}, } { t.Run(tc.name, func(t *testing.T) { if _, err := loadText(tc.text, ""); err == nil {