Files
yoloyolo/.planning/milestones/v1.2-phases/11-synthesis-and-config-layer/11-02-PLAN.md
T
gurixandClaude Opus 4.6 494385b528 chore: archive v1.2 milestone — Extended Protocol Coverage
35 traffic classes across 9 protocol families shipped. Archives
ROADMAP, REQUIREMENTS, and phase directories to milestones/v1.2-*.
Updates README with new protocol families, sound design table,
and [groups] TOML config documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:48:53 +01:00

16 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
11-synthesis-and-config-layer 02 execute 2
11-01
config/config.go
config/config_test.go
true
GRP-02
GRP-03
truths artifacts key_links
PrintConfig output groups classes by family with section header comments
Groups appear in canonical order: Infrastructure, Web, Mail, Remote Access, File Transfer, Database, Discovery, VoIP, Unknown
Within each group, classes are sorted by ascending BaseHz
User-defined classes appear under a User-defined section header after all built-in groups
Users can define [groups] in TOML to reassign a class to a different group
Unknown class names in [groups] produce a warning, not an error
Group reassignment only affects PrintConfig grouping, not frequency or waveform
path provides exports
config/config.go Group-ordered PrintConfig, [groups] TOML support, applyGroupOverrides function
PrintConfig
Load
LoadResult
path provides contains
config/config_test.go Tests for group headers, group reassignment, unknown class warning TestPrintConfigGroupHeaders
from to via pattern
config/config.go synth/config.go PrintConfig reads FreqConfig.Group field cfg.Group
from to via pattern
config/config.go classify/types.go PrintConfig iterates AllClasses() and groups by Group field classify.AllClasses
Refactor PrintConfig to group classes by their Group field with section headers (GRP-02), and add [groups] TOML config support for reassigning protocols to different sound families (GRP-03).

Purpose: This is the user-facing output change that makes --print-config show organized, family-coherent class listings, and gives users the ability to rearrange groupings via TOML config. Output: PrintConfig emits group headers in canonical order; [groups] TOML table parsed and applied; tests cover group headers, reassignment, and unknown class warnings.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/11-synthesis-and-config-layer/11-01-SUMMARY.md @config/config.go @config/config_test.go

From synth/config.go (after Plan 01):

type FreqConfig struct {
    BaseHz       float64
    Harmonics    []HarmonicDef
    Pan          float64
    WaveformType WaveformType
    Group        string       // "Infrastructure", "Web", "Mail", "Remote Access", "File Transfer", "Database", "Discovery", "VoIP", "Unknown"
}
var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{...}  // 35 entries

From classify/types.go (after Plan 01):

func AllClasses() []TrafficClass  // returns 35 entries including LDAP/Kerberos/Syslog

From config/config.go (current):

type rawConfig struct {
    Sounds map[string]SoundOverride `toml:"sounds"`
    Rules  []RawRule                `toml:"rules"`
}
type LoadResult struct {
    FreqCfgs    map[classify.TrafficClass]synth.FreqConfig
    UserRules   []classify.Rule
    ConfigPath  string
    AutoClasses map[classify.TrafficClass]bool
}
func Load(configPath string) (LoadResult, error)
func PrintConfig(result LoadResult) string
Task 1: Add [groups] TOML support and refactor PrintConfig for group headers config/config.go config/config.go Three changes to config/config.go:

1. Add Groups field to rawConfig struct (per D-07):

type rawConfig struct {
    Sounds map[string]SoundOverride `toml:"sounds"`
    Rules  []RawRule                `toml:"rules"`
    Groups map[string]string        `toml:"groups"`
}

2. Add applyGroupOverrides function and wire into Load() (per D-07/D-08/D-09):

Add a new function applyGroupOverrides:

// applyGroupOverrides overlays [groups] reassignments onto freqCfgs.Group in-place.
// Unknown class names produce a warning to stderr (D-09).
// Unknown group names are silently accepted -- users can invent custom groups (D-09).
func applyGroupOverrides(cfgs map[classify.TrafficClass]synth.FreqConfig, groups map[string]string) {
    for className, groupName := range groups {
        cls := classify.TrafficClass(className)
        cfg, known := cfgs[cls]
        if !known {
            fmt.Fprintf(os.Stderr, "Warning: config: [groups]: unknown class %q (ignored)\n", className)
            continue
        }
        cfg.Group = groupName
        cfgs[cls] = cfg
    }
}

In Load(), after the merge(freqCfgs, raw.Sounds) call (around line 101), add:

applyGroupOverrides(freqCfgs, raw.Groups)

This goes AFTER merge so that group reassignment is the last transformation before returning. The call should be present in both code paths (with config file). The no-config path (line 77) does not need it since there is no raw.Groups to apply.

3. Refactor PrintConfig for group-ordered output (per D-04/D-05/D-06):

Replace the current flat AllClasses() iteration (lines 298-321) and the user-defined section (lines 323-333) with group-ordered output.

Add a package-level variable for canonical group order:

// groupOrder defines the canonical display order for --print-config section headers (D-04).
var groupOrder = []string{
    "Infrastructure", "Web", "Mail", "Remote Access",
    "File Transfer", "Database", "Discovery", "VoIP", "Unknown",
}

Replace the built-in and user-defined emission blocks with:

// Build group -> []TrafficClass index from AllClasses()
builtinByGroup := map[string][]classify.TrafficClass{}
builtinSet := map[classify.TrafficClass]bool{}
for _, cls := range classify.AllClasses() {
    builtinSet[cls] = true
    cfg := result.FreqCfgs[cls]
    grp := cfg.Group
    builtinByGroup[grp] = append(builtinByGroup[grp], cls)
}

// Sort each group by ascending BaseHz (D-05) using result.FreqCfgs (effective Hz, not defaults)
for grp := range builtinByGroup {
    classes := builtinByGroup[grp]
    sort.Slice(classes, func(i, j int) bool {
        return result.FreqCfgs[classes[i]].BaseHz < result.FreqCfgs[classes[j]].BaseHz
    })
}

// Emit built-in classes grouped with headers (D-04)
for _, grp := range groupOrder {
    classes, ok := builtinByGroup[grp]
    if !ok || len(classes) == 0 {
        continue
    }
    fmt.Fprintf(&sb, "# %s\n\n", grp)
    for _, cls := range classes {
        cfg := result.FreqCfgs[cls]
        annotation := classAnnotation(cls, cfg, result.AutoClasses)
        fmt.Fprintf(&sb, "# %s -- %.1f Hz (%s)\n", string(cls), cfg.BaseHz, annotation)
        fmt.Fprintf(&sb, "[sounds.%s]\n", string(cls))
        fmt.Fprintf(&sb, "frequency = %.1f\n", cfg.BaseHz)
        fmt.Fprintf(&sb, "waveform = %q\n", waveformString(cfg.WaveformType))
        fmt.Fprintf(&sb, "\n")
    }
}

// Check for custom groups (from [groups] reassignment) that are not in groupOrder
// These classes were already emitted under their reassigned group if the group is canonical.
// For non-canonical group names (user-invented), collect and emit separately.
customGroups := map[string][]classify.TrafficClass{}
for _, cls := range classify.AllClasses() {
    cfg := result.FreqCfgs[cls]
    grp := cfg.Group
    isCanonical := false
    for _, cg := range groupOrder {
        if grp == cg {
            isCanonical = true
            break
        }
    }
    if !isCanonical {
        customGroups[grp] = append(customGroups[grp], cls)
    }
}
// Sort and emit custom group sections
var customGroupNames []string
for grp := range customGroups {
    customGroupNames = append(customGroupNames, grp)
}
sort.Strings(customGroupNames)
for _, grp := range customGroupNames {
    classes := customGroups[grp]
    sort.Slice(classes, func(i, j int) bool {
        return result.FreqCfgs[classes[i]].BaseHz < result.FreqCfgs[classes[j]].BaseHz
    })
    fmt.Fprintf(&sb, "# %s\n\n", grp)
    for _, cls := range classes {
        cfg := result.FreqCfgs[cls]
        annotation := classAnnotation(cls, cfg, result.AutoClasses)
        fmt.Fprintf(&sb, "# %s -- %.1f Hz (%s)\n", string(cls), cfg.BaseHz, annotation)
        fmt.Fprintf(&sb, "[sounds.%s]\n", string(cls))
        fmt.Fprintf(&sb, "frequency = %.1f\n", cfg.BaseHz)
        fmt.Fprintf(&sb, "waveform = %q\n", waveformString(cfg.WaveformType))
        fmt.Fprintf(&sb, "\n")
    }
}

// Emit user-defined classes (in FreqCfgs but not in AllClasses) under "# User-defined" (D-06)
var userClasses []string
for cls := range result.FreqCfgs {
    if !builtinSet[cls] {
        userClasses = append(userClasses, string(cls))
    }
}
sort.Strings(userClasses)
if len(userClasses) > 0 {
    fmt.Fprintf(&sb, "# User-defined\n\n")
    for _, clsStr := range userClasses {
        cls := classify.TrafficClass(clsStr)
        cfg := result.FreqCfgs[cls]
        annotation := classAnnotation(cls, cfg, result.AutoClasses)
        fmt.Fprintf(&sb, "# %s -- %.1f Hz (%s)\n", clsStr, cfg.BaseHz, annotation)
        fmt.Fprintf(&sb, "[sounds.%s]\n", clsStr)
        fmt.Fprintf(&sb, "frequency = %.1f\n", cfg.BaseHz)
        fmt.Fprintf(&sb, "waveform = %q\n", waveformString(cfg.WaveformType))
        fmt.Fprintf(&sb, "\n")
    }
}

Remove the old builtinSet declaration (line 298) since it is now declared in the new block. Remove the old userClasses collection and sort (lines 304-310). Remove the old built-in emission loop (lines 313-321) and user-defined emission loop (lines 323-333).

Do NOT modify the header section (lines 273-281) or rules section (lines 283-295) -- those stay unchanged.

Important: the sort package is already imported. The builtinSet map is now declared inside the new block, so remove the old one. cd /home/dev/workspace/yoloyolo && go build ./config/... <acceptance_criteria>

  • grep "groupOrder" config/config.go returns the canonical group order slice
  • grep "applyGroupOverrides" config/config.go returns the function definition
  • grep 'Groups map\[string\]string' config/config.go shows the new rawConfig field
  • grep "# User-defined" config/config.go shows the user-defined section header
  • go build ./config/... succeeds </acceptance_criteria> PrintConfig emits group-ordered output with section headers. rawConfig has Groups field. applyGroupOverrides function exists and is called in Load(). Compiles successfully.
Task 2: Add tests for group headers, group reassignment, and unknown class warning config/config_test.go config/config_test.go, config/config.go Add 4 new test functions to config/config_test.go:

TestPrintConfigGroupHeaders -- Verifies GRP-02 group header output:

func TestPrintConfigGroupHeaders(t *testing.T) {
    t.Chdir(t.TempDir())
    result, err := config.Load("")
    if err != nil {
        t.Fatalf("Load: %v", err)
    }
    output := config.PrintConfig(result)

    // Verify all populated group headers appear
    expectedGroups := []string{"# Infrastructure", "# Web", "# Mail", "# Remote Access", "# File Transfer", "# Database", "# VoIP", "# Unknown"}
    for _, header := range expectedGroups {
        if !strings.Contains(output, header+"\n") {
            t.Errorf("PrintConfig output missing group header %q", header)
        }
    }

    // Verify canonical order: Infrastructure before Web before Mail etc.
    infraIdx := strings.Index(output, "# Infrastructure\n")
    webIdx := strings.Index(output, "# Web\n")
    mailIdx := strings.Index(output, "# Mail\n")
    remoteIdx := strings.Index(output, "# Remote Access\n")
    ftIdx := strings.Index(output, "# File Transfer\n")
    dbIdx := strings.Index(output, "# Database\n")
    voipIdx := strings.Index(output, "# VoIP\n")
    unknownIdx := strings.Index(output, "# Unknown\n")

    if infraIdx >= webIdx || webIdx >= mailIdx || mailIdx >= remoteIdx ||
        remoteIdx >= ftIdx || ftIdx >= dbIdx || dbIdx >= voipIdx || voipIdx >= unknownIdx {
        t.Errorf("Group headers not in canonical order: infra=%d web=%d mail=%d remote=%d ft=%d db=%d voip=%d unknown=%d",
            infraIdx, webIdx, mailIdx, remoteIdx, ftIdx, dbIdx, voipIdx, unknownIdx)
    }
}

TestLoadGroupOverride -- Verifies GRP-03 basic reassignment:

func TestLoadGroupOverride(t *testing.T) {
    path := writeTOML(t, "[groups]\nIMAP = \"Web\"\n")
    result, err := config.Load(path)
    if err != nil {
        t.Fatalf("Load: %v", err)
    }
    cfg := result.FreqCfgs[classify.ClassIMAP]
    if cfg.Group != "Web" {
        t.Errorf("IMAP Group: got %q, want %q", cfg.Group, "Web")
    }
    // Frequency and waveform unchanged (D-08)
    defaultCfg := synth.ClassFreqConfigs[classify.ClassIMAP]
    if cfg.BaseHz != defaultCfg.BaseHz {
        t.Errorf("IMAP BaseHz changed: got %v, want %v (should be unchanged by group reassignment)", cfg.BaseHz, defaultCfg.BaseHz)
    }
}

TestLoadGroupUnknownClass -- Verifies D-09 warning for unknown class:

func TestLoadGroupUnknownClass(t *testing.T) {
    path := writeTOML(t, "[groups]\nBOGUS = \"Web\"\n")
    result, err := config.Load(path)
    if err != nil {
        t.Fatalf("Load should not error on unknown [groups] class: %v", err)
    }
    // Should still have all default classes
    if len(result.FreqCfgs) != len(classify.AllClasses()) {
        t.Errorf("FreqCfgs len: got %d, want %d", len(result.FreqCfgs), len(classify.AllClasses()))
    }
}

TestPrintConfigGroupReassignment -- Verifies PrintConfig reflects reassignment:

func TestPrintConfigGroupReassignment(t *testing.T) {
    path := writeTOML(t, "[groups]\nIMAP = \"Web\"\n")
    result, err := config.Load(path)
    if err != nil {
        t.Fatalf("Load: %v", err)
    }
    output := config.PrintConfig(result)

    // Find the "# Web" section and check IMAP appears after it
    webIdx := strings.Index(output, "# Web\n")
    mailIdx := strings.Index(output, "# Mail\n")
    imapIdx := strings.Index(output, "[sounds.IMAP]")
    if imapIdx < webIdx || imapIdx > mailIdx {
        t.Errorf("IMAP (reassigned to Web) should appear between Web and Mail headers; web=%d imap=%d mail=%d", webIdx, imapIdx, mailIdx)
    }
}

Also update TestPrintConfigContainsAllClasses if it was not already updated in Plan 01 -- it should use classify.AllClasses() loop instead of hardcoded class names. (Plan 01 should have done this, but verify and fix if needed.) cd /home/dev/workspace/yoloyolo && go test ./config/... -v -run "TestPrintConfigGroup|TestLoadGroup" <acceptance_criteria>

  • go test ./config/... -run TestPrintConfigGroupHeaders passes
  • go test ./config/... -run TestLoadGroupOverride passes
  • go test ./config/... -run TestLoadGroupUnknownClass passes
  • go test ./config/... -run TestPrintConfigGroupReassignment passes
  • go test ./config/... all tests pass (no regressions) </acceptance_criteria> Four new tests cover GRP-02 (group headers in canonical order) and GRP-03 (group reassignment, unknown class warning, PrintConfig reflects reassignment). Full config test suite passes.
```bash cd /home/dev/workspace/yoloyolo && go test ./... ``` All tests pass. PrintConfig shows group headers. [groups] TOML works for reassignment.

Manual verification: go run . --print-config shows classes organized by group with # Infrastructure, # Web, # Mail, # Remote Access, # File Transfer, # Database, # VoIP, # Unknown section headers.

<success_criteria>

  • go test ./... passes fully
  • PrintConfig output contains group section headers in canonical order
  • [groups] TOML table reassigns a class's group in PrintConfig output
  • Unknown class names in [groups] produce stderr warning, not error
  • Group reassignment does not change frequency or waveform (D-08)
  • User-defined classes appear under "# User-defined" section </success_criteria>
After completion, create `.planning/phases/11-synthesis-and-config-layer/11-02-SUMMARY.md`