diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 3fde179..014d8fa 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -53,7 +53,11 @@ Plans: 3. User passes `--config /path/to/custom.toml` and the tool uses that file; if the file does not exist, the tool exits with a clear error before capture begins 4. User types `frequncy = 440` in their config file and the tool exits at startup with an error naming `frequncy` as an unrecognized key 5. User sets waveform for one class in TOML and leaves all other classes at their defaults — the unspecified classes are unchanged -**Plans**: TBD +**Plans:** 2 plans + +Plans: +- [ ] 06-01-PLAN.md — Config package: TOML load, validate, merge with TDD (config/config.go, config/config_test.go) +- [ ] 06-02-PLAN.md — CLI wiring: --config flag, RunSynthesis signature change, main.go integration ### Phase 7: Custom Rules and Print-Config **Goal**: Users can define their own traffic classification rules in TOML, assign custom sounds to them, and inspect the full effective config before capture begins @@ -75,5 +79,5 @@ Plans: | 3. Pipeline Integration and MVP | v1.0 | 2/2 | Complete | 2026-03-26 | | 4. Power User Features | v1.0 | 2/2 | Complete | 2026-03-26 | | 5. Waveform Types and Bank Decoupling | v1.1 | 2/2 | Complete | 2026-03-26 | -| 6. Config Package and Sound Overrides | v1.1 | 0/? | Not started | - | +| 6. Config Package and Sound Overrides | v1.1 | 0/2 | Planning complete | - | | 7. Custom Rules and Print-Config | v1.1 | 0/? | Not started | - | diff --git a/.planning/phases/06-config-package-and-sound-overrides/06-01-PLAN.md b/.planning/phases/06-config-package-and-sound-overrides/06-01-PLAN.md new file mode 100644 index 0000000..f6ac123 --- /dev/null +++ b/.planning/phases/06-config-package-and-sound-overrides/06-01-PLAN.md @@ -0,0 +1,323 @@ +--- +phase: 06-config-package-and-sound-overrides +plan: 01 +type: tdd +wave: 1 +depends_on: [] +files_modified: + - config/config.go + - config/config_test.go + - go.mod + - go.sum +autonomous: true +requirements: + - CFG-01 + - CFG-02 + - CFG-04 + - CFG-05 + +must_haves: + truths: + - "Load with explicit path to valid TOML returns merged config map with overrides applied" + - "Load with no config file found returns default ClassFreqConfigs unchanged" + - "Load with unknown TOML key returns error naming the bad key" + - "Load with partial override (only frequency set) leaves waveform unchanged" + - "Load with partial override (only waveform set) leaves frequency unchanged" + - "Load with unknown class name logs warning and does not error" + artifacts: + - path: "config/config.go" + provides: "Load function, parse, validate, merge, discover" + exports: ["Load"] + - path: "config/config_test.go" + provides: "Table-driven tests for CFG-01 through CFG-05" + min_lines: 100 + key_links: + - from: "config/config.go" + to: "synth/config.go" + via: "imports synth.FreqConfig, synth.WaveformType, synth.ClassFreqConfigs, synth.WaveformPresetHarmonics" + pattern: "synth\\.FreqConfig|synth\\.ClassFreqConfigs|synth\\.WaveformPresetHarmonics" + - from: "config/config.go" + to: "classify/types.go" + via: "imports classify.TrafficClass, classify.AllClasses" + pattern: "classify\\.TrafficClass|classify\\.AllClasses" + - from: "config/config.go" + to: "github.com/BurntSushi/toml" + via: "toml.DecodeFile, md.Undecoded()" + pattern: "toml\\.DecodeFile|Undecoded" +--- + + +Create the `config` package with TOML loading, unknown-key validation, partial-merge semantics, and auto-discovery logic. This is the core of Phase 6 — all config behavior except CLI flag wiring. + +Purpose: Implements CFG-01 (TOML override), CFG-02 (auto-discovery), CFG-04 (partial override), CFG-05 (unknown key error). The package exposes a single `Load(configPath string)` function that returns a ready-to-use `map[classify.TrafficClass]synth.FreqConfig`. + +Output: `config/config.go`, `config/config_test.go`, updated `go.mod`/`go.sum` + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/06-config-package-and-sound-overrides/06-CONTEXT.md +@.planning/phases/06-config-package-and-sound-overrides/06-RESEARCH.md + + + + +From synth/config.go: +```go +type WaveformType int +const ( + WaveformCustom WaveformType = iota + WaveformSine + WaveformSquare + WaveformSawtooth + WaveformTriangle +) + +type HarmonicDef struct { + Ratio int + Amplitude float64 +} + +type FreqConfig struct { + BaseHz float64 + Harmonics []HarmonicDef + Pan float64 + WaveformType WaveformType +} + +var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{ /* 14 entries */ } +func WaveformPresetHarmonics(wt WaveformType, baseHz float64, sampleRate int) []HarmonicDef +const SampleRate = 44100 +``` + +From classify/types.go: +```go +type TrafficClass string +const ( + ClassICMP TrafficClass = "ICMP" + ClassDNS TrafficClass = "DNS" + ClassHTTPS TrafficClass = "HTTPS" + ClassHTTP TrafficClass = "HTTP" + ClassSSH TrafficClass = "SSH" + ClassSMTP TrafficClass = "SMTP" + ClassNTP TrafficClass = "NTP" + ClassDHCP TrafficClass = "DHCP" + ClassOtherTCP TrafficClass = "other-TCP" + ClassOtherUDP TrafficClass = "other-UDP" + ClassUnknown1 TrafficClass = "unknown-1" + ClassUnknown2 TrafficClass = "unknown-2" + ClassUnknown3 TrafficClass = "unknown-3" + ClassUnknown4 TrafficClass = "unknown-4" +) +func AllClasses() []TrafficClass +``` + +Go module path: `github.com/netsynth/netsynth` + + + + + Config package: TOML load, validate, merge + config/config.go, config/config_test.go + + - Test: Load(explicitPath) with valid TOML `[sounds.ICMP]\nfrequency = 100.0` returns map where ICMP.BaseHz == 100.0 and all other classes unchanged (CFG-01, CFG-04) + - Test: Load(explicitPath) with `[sounds.ICMP]\nwaveform = "square"` returns map where ICMP.WaveformType == WaveformSquare and ICMP.BaseHz unchanged (CFG-04) + - Test: Load(explicitPath) with `[sounds.ICMP]\nfrequncy = 440` returns error containing "frequncy" (CFG-05) + - Test: Load("") in a directory with no netsynth.toml returns default ClassFreqConfigs map with no error (CFG-02) + - Test: Load(explicitPath) where file does not exist returns error containing "not found" (CFG-03 prep) + - Test: Load(explicitPath) with `[sounds.BOGUS]\nfrequency = 100.0` returns no error but stderr contains "unknown class" (D-09) + - Test: Load(explicitPath) with `[sounds.ICMP]\nfrequency = 100.0\nwaveform = "square"` returns ICMP with both overrides applied and harmonics regenerated + - Test: Load(explicitPath) with `[sounds.ICMP]\nwaveform = "invalid"` returns error containing "invalid waveform" + - Test: All 14 default classes present in result map regardless of override count + + + Create `config/config.go` with: + 1. `SoundOverride` struct with pointer fields `Frequency *float64` and `Waveform *string` (toml tags) + 2. `rawConfig` struct with `Sounds map[string]SoundOverride` (toml tag "sounds") + 3. `Load(configPath string) (map[classify.TrafficClass]synth.FreqConfig, error)` — public entry point + 4. `resolvePath(configPath string) (path string, explicit bool, err error)` — handles D-05 discovery order + 5. `discoverPath() string` — probes ./netsynth.toml then ~/.config/netsynth/config.toml + 6. `parseFile(path string) (rawConfig, error)` — uses toml.DecodeFile + md.Undecoded() for CFG-05 + 7. `validate(raw rawConfig) error` — validates waveform strings + 8. `merge(defaults map[classify.TrafficClass]synth.FreqConfig, overrides map[string]SoundOverride) map[classify.TrafficClass]synth.FreqConfig` — per-field overlay + 9. `copyDefaults() map[classify.TrafficClass]synth.FreqConfig` — shallow copy of ClassFreqConfigs + 10. `parseWaveform(s string) (synth.WaveformType, error)` — string-to-enum map + 11. `validWaveforms` map: "sine"->WaveformSine, "square"->WaveformSquare, "sawtooth"->WaveformSawtooth, "triangle"->WaveformTriangle + + Add `github.com/BurntSushi/toml@v1.6.0` to go.mod via `go get`. + + Per D-03: Per-field overlay merge — only non-nil pointer fields override defaults. + Per D-07: Unknown keys detected via md.Undecoded(), error names the key. + Per D-09: Unknown class names in [sounds.] produce warning to stderr, not error. + Per D-05: Discovery order: --config > ./netsynth.toml > ~/.config/netsynth/config.toml. + Per D-06: Only one config file loaded, first found wins. + Per D-08: Type mismatches produce clear error with field name. + Per D-11: All validation happens before returning — fail fast. + + When frequency is overridden and WaveformType != WaveformCustom, regenerate Harmonics via WaveformPresetHarmonics(cfg.WaveformType, newBaseHz, synth.SampleRate). + When waveform is overridden, regenerate Harmonics via WaveformPresetHarmonics(newWt, cfg.BaseHz, synth.SampleRate). + + + + + + + Task 1: Config package — TDD red-green-refactor + config/config.go, config/config_test.go, go.mod, go.sum + + synth/config.go (FreqConfig, WaveformType, ClassFreqConfigs, WaveformPresetHarmonics, SampleRate) + classify/types.go (TrafficClass, AllClasses, class constants) + go.mod (current dependencies) + .planning/phases/06-config-package-and-sound-overrides/06-RESEARCH.md (patterns 1-5, pitfalls 1-4) + + + - TestLoadPartialOverrideFrequency: Load TOML `[sounds.ICMP]\nfrequency = 100.0` -> ICMP.BaseHz == 100.0, ICMP.WaveformType == synth.WaveformCustom (unchanged), DNS.BaseHz == 110.0 (unchanged) + - TestLoadPartialOverrideWaveform: Load TOML `[sounds.ICMP]\nwaveform = "square"` -> ICMP.WaveformType == synth.WaveformSquare, ICMP.BaseHz == 65.0 (unchanged), len(ICMP.Harmonics) > 0 + - TestLoadBothOverrides: Load TOML `[sounds.ICMP]\nfrequency = 100.0\nwaveform = "square"` -> ICMP.BaseHz == 100.0, ICMP.WaveformType == synth.WaveformSquare + - TestLoadUnknownKey: Load TOML `[sounds.ICMP]\nfrequncy = 440` -> error != nil, error contains "frequncy" + - TestLoadNoConfig: Load("") in temp dir with no netsynth.toml -> err == nil, result has 14 entries, ICMP.BaseHz == 65.0 + - TestLoadExplicitMissing: Load("/nonexistent/file.toml") -> error != nil, error contains "not found" + - TestLoadUnknownClass: Load TOML `[sounds.BOGUS]\nfrequency = 100.0` -> err == nil, result has 14 entries (BOGUS not present) + - TestLoadInvalidWaveform: Load TOML `[sounds.ICMP]\nwaveform = "invalid"` -> error != nil, error contains "invalid waveform" + - TestLoadAllDefaultsPresent: Load with any valid override -> len(result) == 14 + + + **RED phase:** Create `config/config_test.go` with all 9 test functions listed in behavior above. Each test: + - Creates a temp TOML file with `os.CreateTemp(t.TempDir(), "*.toml")` + - Calls `config.Load(tmpFile.Name())` (or `config.Load("")` for no-config test) + - Asserts expected outcomes + + For TestLoadNoConfig: use `t.Chdir(t.TempDir())` (Go 1.24 testing.T.Chdir) to ensure no netsynth.toml exists in working directory. + + Create a minimal `config/config.go` with just `package config` and a stub `Load` function returning nil, nil so the test file compiles. Run `go test ./config/... -count=1` — all tests must FAIL (red). + + **GREEN phase:** Implement `config/config.go` fully: + + 1. Run `go get github.com/BurntSushi/toml@v1.6.0` to add the dependency. + + 2. Package declaration and imports: + ``` + package config + imports: errors, fmt, io/fs, os, path/filepath, strings + github.com/BurntSushi/toml + github.com/netsynth/netsynth/classify + github.com/netsynth/netsynth/synth + ``` + + 3. Types: + - `SoundOverride` struct: `Frequency *float64 \`toml:"frequency"\``, `Waveform *string \`toml:"waveform"\`` + - `rawConfig` struct: `Sounds map[string]SoundOverride \`toml:"sounds"\`` + + 4. `validWaveforms` var: map[string]synth.WaveformType with entries "sine"->WaveformSine, "square"->WaveformSquare, "sawtooth"->WaveformSawtooth, "triangle"->WaveformTriangle + + 5. `Load(configPath string) (map[classify.TrafficClass]synth.FreqConfig, error)`: + - Call resolvePath(configPath) -> path, explicit, err + - If err != nil, return nil, err + - If path == "", return copyDefaults(), nil (CFG-02: silent default) + - Call parseFile(path) -> raw, err + - If err != nil AND explicit AND errors.Is(err, fs.ErrNotExist): return nil, fmt.Errorf("config file not found: %s", configPath) + - If err != nil (other): return nil, err + - Call validate(raw) -> err; if err, return nil, err + - Return merge(copyDefaults(), raw.Sounds), nil + + 6. `resolvePath(configPath string) (string, bool, error)`: + - If configPath != "": return configPath, true, nil + - path := discoverPath() + - return path, false, nil + + 7. `discoverPath() string`: + - Check `os.Stat("netsynth.toml")` — if err == nil, return "netsynth.toml" + - dir, err := os.UserConfigDir(); if err != nil, return "" + - p := filepath.Join(dir, "netsynth", "config.toml") + - Check os.Stat(p) — if err == nil, return p + - return "" + + 8. `parseFile(path string) (rawConfig, error)`: + - var raw rawConfig + - md, err := toml.DecodeFile(path, &raw) + - If err != nil, return raw, err (this handles file-not-found and parse errors including D-08 type mismatches) + - undecoded := md.Undecoded() + - If len(undecoded) > 0: keyPath := strings.Join(undecoded[0].String() ... ) — actually undecoded is []toml.Key where Key is []string. Use `undecoded[0].String()` which returns dot-joined path. Return raw, fmt.Errorf("config: unknown key %q — check spelling", undecoded[0].String()) + - Return raw, nil + + 9. `validate(raw rawConfig) error`: + - For each className, override in raw.Sounds: + - If override.Waveform != nil: call parseWaveform(*override.Waveform); if err, return err + + 10. `parseWaveform(s string) (synth.WaveformType, error)`: + - If wt, ok := validWaveforms[s]; ok: return wt, nil + - valid := []string{"sine", "square", "sawtooth", "triangle"} + - Return 0, fmt.Errorf("config: invalid waveform %q — valid values: %s", s, strings.Join(valid, ", ")) + + 11. `copyDefaults() map[classify.TrafficClass]synth.FreqConfig`: + - result := make(map[...], len(synth.ClassFreqConfigs)) + - For k, v := range synth.ClassFreqConfigs: result[k] = v + - Return result + - Comment: "Shallow copy is safe because merge assigns fresh Harmonics slices from WaveformPresetHarmonics, never mutates the original." + + 12. `merge(defaults map[classify.TrafficClass]synth.FreqConfig, overrides map[string]SoundOverride) map[classify.TrafficClass]synth.FreqConfig`: + - For className, override := range overrides: + - class := classify.TrafficClass(className) + - cfg, known := defaults[class] + - If !known: fmt.Fprintf(os.Stderr, "Warning: config: unknown class %q (ignored)\n", className); continue (D-09) + - If override.Frequency != nil: + - cfg.BaseHz = *override.Frequency + - If cfg.WaveformType != synth.WaveformCustom: cfg.Harmonics = synth.WaveformPresetHarmonics(cfg.WaveformType, cfg.BaseHz, synth.SampleRate) + - If override.Waveform != nil: + - wt, _ := parseWaveform(*override.Waveform) (already validated) + - cfg.WaveformType = wt + - cfg.Harmonics = synth.WaveformPresetHarmonics(wt, cfg.BaseHz, synth.SampleRate) + - defaults[class] = cfg + - Return defaults + + Run `go test ./config/... -count=1` — all tests must PASS (green). + + **REFACTOR:** Review for clarity. Run `go vet ./config/...` clean. + + + cd /home/dev/workspace/yoloyolo && go test ./config/... -count=1 -v + + + - config/config.go contains `func Load(configPath string) (map[classify.TrafficClass]synth.FreqConfig, error)` + - config/config.go contains `type SoundOverride struct` with `Frequency *float64` and `Waveform *string` + - config/config.go contains `type rawConfig struct` with `Sounds map[string]SoundOverride` + - config/config.go contains `toml.DecodeFile` call + - config/config.go contains `md.Undecoded()` call + - config/config.go contains `validWaveforms` map with 4 entries + - config/config.go contains `os.UserConfigDir()` call in discoverPath + - config/config_test.go contains at least 9 test functions (TestLoad*) + - go.mod contains `github.com/BurntSushi/toml` + - `go test ./config/... -count=1` exits 0 + - `go vet ./config/...` exits 0 + + + config package exists with Load function that handles TOML parsing, unknown-key detection, partial-merge, auto-discovery, and waveform validation. All 9+ tests pass. BurntSushi/toml dependency in go.mod. + + + + + + +- `go test ./config/... -count=1 -v` — all tests pass +- `go vet ./config/...` — no issues +- `go build ./config/...` — compiles cleanly + + + +- config.Load("path/to/valid.toml") returns merged map with overrides applied (CFG-01) +- config.Load("") with no config file returns defaults silently (CFG-02) +- config.Load("") with partial TOML returns map where unset fields retain defaults (CFG-04) +- config.Load("path/to/typo.toml") with unknown key returns error naming the key (CFG-05) +- All 14 default classes always present in result map + + + +After completion, create `.planning/phases/06-config-package-and-sound-overrides/06-01-SUMMARY.md` + diff --git a/.planning/phases/06-config-package-and-sound-overrides/06-02-PLAN.md b/.planning/phases/06-config-package-and-sound-overrides/06-02-PLAN.md new file mode 100644 index 0000000..720b7f6 --- /dev/null +++ b/.planning/phases/06-config-package-and-sound-overrides/06-02-PLAN.md @@ -0,0 +1,262 @@ +--- +phase: 06-config-package-and-sound-overrides +plan: 02 +type: execute +wave: 2 +depends_on: ["06-01"] +files_modified: + - encode/mp3.go + - encode/mp3_test.go + - cmd/netsynth/main.go +autonomous: true +requirements: + - CFG-03 + +must_haves: + truths: + - "User passes --config /path/to/file.toml and the tool uses that file for sound overrides" + - "User passes --config /nonexistent.toml and the tool exits with a clear error before capture" + - "User runs without --config and auto-discovery kicks in (or defaults used silently)" + - "RunSynthesis uses the merged config map instead of hardcoded ClassFreqConfigs" + artifacts: + - path: "cmd/netsynth/main.go" + provides: "--config flag, config.Load call, passing merged map to RunSynthesis" + contains: "configPath" + - path: "encode/mp3.go" + provides: "RunSynthesis with freqCfgs parameter" + contains: "freqCfgs map[classify.TrafficClass]synth.FreqConfig" + - path: "encode/mp3_test.go" + provides: "Updated tests for new RunSynthesis signature" + key_links: + - from: "cmd/netsynth/main.go" + to: "config/config.go" + via: "config.Load(configPath)" + pattern: "config\\.Load" + - from: "cmd/netsynth/main.go" + to: "encode/mp3.go" + via: "encode.RunSynthesis(snapshots, outputPath, freqCfgs)" + pattern: "encode\\.RunSynthesis.*freqCfgs" + - from: "encode/mp3.go" + to: "synth/bank.go" + via: "synth.NewBank(1.0, freqCfgs) using passed-in config" + pattern: "synth\\.NewBank.*freqCfgs" +--- + + +Wire the config package into the CLI and synthesis pipeline. Add `--config` flag to Cobra, call `config.Load` at startup, change `RunSynthesis` signature to accept the merged config map, and update all call sites. + +Purpose: Completes CFG-03 (explicit --config flag) and D-10 (RunSynthesis signature change). After this plan, the end-to-end flow works: user creates TOML -> tool loads it -> synthesis uses overridden frequencies/waveforms. + +Output: Updated `cmd/netsynth/main.go`, `encode/mp3.go`, `encode/mp3_test.go` + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/06-config-package-and-sound-overrides/06-CONTEXT.md +@.planning/phases/06-config-package-and-sound-overrides/06-01-SUMMARY.md + + + + +From config/config.go (created in Plan 01): +```go +// Load finds, parses, validates, and merges a config file. +// configPath is the --config flag value; empty string triggers auto-discovery. +func Load(configPath string) (map[classify.TrafficClass]synth.FreqConfig, error) +``` + +From encode/mp3.go (current signature to change): +```go +// Current: +func RunSynthesis(snapshots []classify.WindowSnapshot, outputPath string) error +// New: +func RunSynthesis(snapshots []classify.WindowSnapshot, outputPath string, freqCfgs map[classify.TrafficClass]synth.FreqConfig) error +``` + +From cmd/netsynth/main.go (existing flags pattern): +```go +var ( + ifaceName string + listIfaces bool + verbose bool + outputPath string + bpfFilter string + readPath string +) +// Flag registration pattern: +rootCmd.Flags().StringVar(&bpfFilter, "filter", "", "BPF filter expression") +``` + +From synth/config.go: +```go +var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{ /* 14 entries */ } +``` + + + + + + + Task 1: Change RunSynthesis signature and update encode tests + encode/mp3.go, encode/mp3_test.go + + encode/mp3.go (current RunSynthesis signature and body) + encode/mp3_test.go (current test calls to RunSynthesis) + synth/config.go (ClassFreqConfigs, FreqConfig type) + classify/types.go (TrafficClass type) + + + **encode/mp3.go changes:** + + 1. Add `freqCfgs map[classify.TrafficClass]synth.FreqConfig` as third parameter to RunSynthesis: + ``` + func RunSynthesis(snapshots []classify.WindowSnapshot, outputPath string, freqCfgs map[classify.TrafficClass]synth.FreqConfig) error + ``` + + 2. Change line 57 from: + ``` + bank := synth.NewBank(1.0, synth.ClassFreqConfigs) + ``` + to: + ``` + bank := synth.NewBank(1.0, freqCfgs) + ``` + + 3. No other changes to encode/mp3.go. + + **encode/mp3_test.go changes:** + + 4. Update `TestMP3Valid` (line 56): change `RunSynthesis(snaps, tmpPath)` to `RunSynthesis(snaps, tmpPath, synth.ClassFreqConfigs)`. + + 5. Update `TestZeroPacketError` (line 101): change `RunSynthesis([]classify.WindowSnapshot{}, tmpPath)` to `RunSynthesis([]classify.WindowSnapshot{}, tmpPath, synth.ClassFreqConfigs)`. + + 6. Update `TestZeroPacketError` (line 121): change `RunSynthesis(zeroSnaps, tmpPath2)` to `RunSynthesis(zeroSnaps, tmpPath2, synth.ClassFreqConfigs)`. + + The `synth` import is already present in mp3_test.go. + + + cd /home/dev/workspace/yoloyolo && go build ./encode/... && go test ./encode/... -count=1 -run TestZeroPacketError + + + - encode/mp3.go contains `func RunSynthesis(snapshots []classify.WindowSnapshot, outputPath string, freqCfgs map[classify.TrafficClass]synth.FreqConfig) error` + - encode/mp3.go contains `synth.NewBank(1.0, freqCfgs)` (not synth.ClassFreqConfigs) + - encode/mp3_test.go contains `RunSynthesis(snaps, tmpPath, synth.ClassFreqConfigs)` + - encode/mp3_test.go contains `RunSynthesis([]classify.WindowSnapshot{}, tmpPath, synth.ClassFreqConfigs)` + - encode/mp3_test.go contains `RunSynthesis(zeroSnaps, tmpPath2, synth.ClassFreqConfigs)` + - `go build ./encode/...` exits 0 + - `go test ./encode/... -count=1 -run TestZeroPacketError` exits 0 + + + RunSynthesis accepts injected config map. All encode tests updated and passing. + + + + + Task 2: Add --config flag and wire config.Load into main.go + cmd/netsynth/main.go + + cmd/netsynth/main.go (full file — flag definitions, run function, runLiveMode, runPcapMode) + config/config.go (Load function signature) + encode/mp3.go (updated RunSynthesis signature from Task 1) + + + **cmd/netsynth/main.go changes:** + + 1. Add `configPath` to the var block (after `readPath`): + ```go + configPath string // NEW: --config flag (CFG-03) + ``` + + 2. Add import for config package in the import block: + ```go + "github.com/netsynth/netsynth/config" + ``` + Also add import for `synth` package (needed for ClassFreqConfigs fallback reference — though config.Load handles this internally): + No — `synth` is NOT needed in main.go. `config.Load` returns the full map. Only add `config` import. + + 3. Add flag registration in main() after the `readPath` flag line (line 44): + ```go + rootCmd.Flags().StringVar(&configPath, "config", "", "Path to TOML config file (default: auto-discover)") + ``` + + 4. In the `run` function, add config loading AFTER the BPF filter validation block (after line 80) and BEFORE output path resolution (before line 83). This is per D-11 (fail fast on config errors before capture): + ```go + // Load config (CFG-01 through CFG-05, D-11: fail fast) + freqCfgs, err := config.Load(configPath) + if err != nil { + return err + } + ``` + + 5. The `freqCfgs` variable must be accessible in both `runLiveMode` and `runPcapMode`. Two approaches: + - Option A: Pass freqCfgs to both functions (cleanest). + - Option B: Store in a package-level var (simpler change). + + Use Option A. Change signatures: + - `runLiveMode(cmd *cobra.Command) error` -> `runLiveMode(cmd *cobra.Command, freqCfgs map[classify.TrafficClass]synth.FreqConfig) error` + - `runPcapMode(cmd *cobra.Command) error` -> `runPcapMode(cmd *cobra.Command, freqCfgs map[classify.TrafficClass]synth.FreqConfig) error` + + This requires adding `synth` import after all: + ```go + "github.com/netsynth/netsynth/synth" + ``` + + Update call sites in `run()`: + - Line 92: `return runPcapMode(cmd)` -> `return runPcapMode(cmd, freqCfgs)` + - Line 94: `return runLiveMode(cmd)` -> `return runLiveMode(cmd, freqCfgs)` + + 6. In `runLiveMode`, change the RunSynthesis call (line 147): + From: `encode.RunSynthesis(collectedSnapshots, outputPath)` + To: `encode.RunSynthesis(collectedSnapshots, outputPath, freqCfgs)` + + 7. In `runPcapMode`, change the RunSynthesis call (line 215): + From: `encode.RunSynthesis(collectedSnapshots, outputPath)` + To: `encode.RunSynthesis(collectedSnapshots, outputPath, freqCfgs)` + + After all changes, run `go build ./cmd/netsynth/...` to verify compilation. + + + cd /home/dev/workspace/yoloyolo && go build ./cmd/netsynth/... && go vet ./cmd/netsynth/... && go test ./... -count=1 2>&1 | tail -20 + + + - cmd/netsynth/main.go contains `configPath string` + - cmd/netsynth/main.go contains `rootCmd.Flags().StringVar(&configPath, "config", ""` + - cmd/netsynth/main.go contains `config.Load(configPath)` + - cmd/netsynth/main.go contains `"github.com/netsynth/netsynth/config"` in imports + - cmd/netsynth/main.go contains `encode.RunSynthesis(collectedSnapshots, outputPath, freqCfgs)` (two occurrences — one in runLiveMode, one in runPcapMode) + - cmd/netsynth/main.go contains `runLiveMode(cmd, freqCfgs)` and `runPcapMode(cmd, freqCfgs)` + - `go build ./cmd/netsynth/...` exits 0 + - `go vet ./cmd/netsynth/...` exits 0 + - `go test ./... -count=1` exits 0 (full suite green) + + + --config flag registered in Cobra. config.Load called at startup before capture. Merged config map flows through to RunSynthesis in both live and pcap modes. Full test suite passes. + + + + + + +- `go build ./...` compiles entire project +- `go test ./... -count=1` all tests pass +- `go vet ./...` no issues +- `./netsynth --help` shows `--config` flag in output + + + +- --config flag appears in CLI help output (CFG-03) +- Explicit --config with missing file produces error before capture (CFG-03) +- RunSynthesis uses injected config map, not hardcoded ClassFreqConfigs (D-10) +- Full test suite passes including encode and config package tests + + + +After completion, create `.planning/phases/06-config-package-and-sound-overrides/06-02-SUMMARY.md` +