more cyclo refactoring

quietlight
May 4, 2026, 4:00 AM
GVOVKH5R27K75VXGSZCP3X62FGNCSMDVFEKLR3LFXERFB54CHTUQC

Dependencies

  • [2] 54GPBNIX added +_ for tui to select segments with no calltype
  • [3] NS4TDPLN cyclomatic complexity
  • [4] FCCJNYCV more tests for utils/
  • [5] DS22DKV3 added shell script integration tests.
  • [6] KZKLAINJ run out of space on nest, cleaned out

Change contents

  • replacement in utils/wav_metadata_test.go at line 288
    [4.16117][4.10040:10126](),[4.10040][4.10040:10126]()
    func TestParseWAVHeader(t *testing.T) {
    // Create temporary directory for test files
    [4.16117]
    [4.10126]
    func TestParseWAVHeader_BasicMetadata(t *testing.T) {
  • replacement in utils/wav_metadata_test.go at line 291
    [4.10150][4.10150:10561]()
    t.Run("should parse basic WAV metadata", func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, "test_basic.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 60.0,
    sampleRate: 44100,
    channels: 2,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    })
    [4.10150]
    [4.10561]
    path := createTestWAVFile(t, tmpDir, "test_basic.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 60.0,
    sampleRate: 44100,
    channels: 2,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    })
  • replacement in utils/wav_metadata_test.go at line 307
    [4.10562][4.10562:10675]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.10562]
    [4.10675]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 312
    [4.10676][4.10676:10897]()
    if metadata.SampleRate != 44100 {
    t.Errorf("SampleRate incorrect: got %d, want 44100", metadata.SampleRate)
    }
    if metadata.Channels != 2 {
    t.Errorf("Channels incorrect: got %d, want 2", metadata.Channels)
    }
    [4.10676]
    [4.10897]
    if metadata.SampleRate != 44100 {
    t.Errorf("SampleRate incorrect: got %d, want 44100", metadata.SampleRate)
    }
    if metadata.Channels != 2 {
    t.Errorf("Channels incorrect: got %d, want 2", metadata.Channels)
    }
    if metadata.BitsPerSample != 16 {
    t.Errorf("BitsPerSample incorrect: got %d, want 16", metadata.BitsPerSample)
    }
    if metadata.Duration < 59.9 || metadata.Duration > 60.1 {
    t.Errorf("Duration incorrect: got %f, want ~60.0", metadata.Duration)
    }
    }
  • replacement in utils/wav_metadata_test.go at line 326
    [4.10898][4.10898:11018]()
    if metadata.BitsPerSample != 16 {
    t.Errorf("BitsPerSample incorrect: got %d, want 16", metadata.BitsPerSample)
    }
    [4.10898]
    [4.11018]
    func TestParseWAVHeader_CommentMetadata(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 329
    [4.11019][4.11019:11234]()
    // Duration should be approximately 60 seconds (allow small rounding error)
    if metadata.Duration < 59.9 || metadata.Duration > 60.1 {
    t.Errorf("Duration incorrect: got %f, want ~60.0", metadata.Duration)
    }
    [4.11019]
    [4.11234]
    expectedComment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549"
    path := createTestWAVFile(t, tmpDir, "test_comment.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 10.0,
    sampleRate: 48000,
    channels: 1,
    bitsPerSample: 16,
    comment: expectedComment,
    artist: "",
  • replacement in utils/wav_metadata_test.go at line 346
    [4.11239][4.11239:11759]()
    t.Run("should extract comment metadata", func(t *testing.T) {
    expectedComment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549"
    path := createTestWAVFile(t, tmpDir, "test_comment.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 10.0,
    sampleRate: 48000,
    channels: 1,
    bitsPerSample: 16,
    comment: expectedComment,
    artist: "",
    })
    [4.11239]
    [4.11759]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 351
    [4.11760][4.11760:11873]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.11760]
    [4.11873]
    if metadata.Comment != expectedComment {
    t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, expectedComment)
    }
    }
  • replacement in utils/wav_metadata_test.go at line 356
    [4.11874][4.11874:12010]()
    if metadata.Comment != expectedComment {
    t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, expectedComment)
    }
    })
    [4.11874]
    [4.12010]
    func TestParseWAVHeader_ArtistMetadata(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 359
    [4.12011][4.12011:12707]()
    t.Run("should extract artist metadata", func(t *testing.T) {
    expectedArtist := "AudioMoth"
    path := createTestWAVFile(t, tmpDir, "test_artist.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 5.0,
    sampleRate: 48000,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: expectedArtist,
    })
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    if metadata.Artist != expectedArtist {
    t.Errorf("Artist incorrect: got %q, want %q", metadata.Artist, expectedArtist)
    }
    [4.12011]
    [4.12707]
    expectedArtist := "AudioMoth"
    path := createTestWAVFile(t, tmpDir, "test_artist.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 5.0,
    sampleRate: 48000,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: expectedArtist,
  • replacement in utils/wav_metadata_test.go at line 376
    [4.12712][4.12712:13234]()
    t.Run("should extract both comment and artist", func(t *testing.T) {
    expectedComment := "Test recording comment"
    expectedArtist := "Test Artist"
    path := createTestWAVFile(t, tmpDir, "test_both.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 15.0,
    sampleRate: 44100,
    channels: 2,
    bitsPerSample: 16,
    comment: expectedComment,
    artist: expectedArtist,
    })
    [4.12712]
    [4.13234]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 381
    [4.13235][4.13235:13348]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.13235]
    [4.13348]
    if metadata.Artist != expectedArtist {
    t.Errorf("Artist incorrect: got %q, want %q", metadata.Artist, expectedArtist)
    }
    }
  • replacement in utils/wav_metadata_test.go at line 386
    [4.13349][4.13349:13481]()
    if metadata.Comment != expectedComment {
    t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, expectedComment)
    }
    [4.13349]
    [4.13481]
    func TestParseWAVHeader_CommentAndArtist(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 389
    [4.13482][4.13482:13609]()
    if metadata.Artist != expectedArtist {
    t.Errorf("Artist incorrect: got %q, want %q", metadata.Artist, expectedArtist)
    }
    [4.13482]
    [4.13609]
    expectedComment := "Test recording comment"
    expectedArtist := "Test Artist"
    path := createTestWAVFile(t, tmpDir, "test_both.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 15.0,
    sampleRate: 44100,
    channels: 2,
    bitsPerSample: 16,
    comment: expectedComment,
    artist: expectedArtist,
  • replacement in utils/wav_metadata_test.go at line 407
    [4.13614][4.13614:13806]()
    t.Run("should handle different sample rates", func(t *testing.T) {
    testCases := []struct {
    sampleRate int
    }{
    {8000},
    {16000},
    {22050},
    {44100},
    {48000},
    {96000},
    }
    [4.13614]
    [4.13806]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 412
    [4.13807][4.13807:14256]()
    for _, tc := range testCases {
    t.Run("", func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, "test_sr.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 1.0,
    sampleRate: tc.sampleRate,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    })
    [4.13807]
    [4.14256]
    if metadata.Comment != expectedComment {
    t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, expectedComment)
    }
    if metadata.Artist != expectedArtist {
    t.Errorf("Artist incorrect: got %q, want %q", metadata.Artist, expectedArtist)
    }
    }
  • replacement in utils/wav_metadata_test.go at line 420
    [4.14257][4.14257:14378]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.14257]
    [4.14378]
    func TestParseWAVHeader_SampleRates(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 423
    [4.14379][4.14379:14522]()
    if metadata.SampleRate != tc.sampleRate {
    t.Errorf("SampleRate incorrect: got %d, want %d", metadata.SampleRate, tc.sampleRate)
    }
    [4.14379]
    [4.14522]
    testCases := []int{8000, 16000, 22050, 44100, 48000, 96000}
    for _, sr := range testCases {
    t.Run(fmt.Sprintf("%dHz", sr), func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, fmt.Sprintf("test_sr_%d.wav", sr), struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 1.0,
    sampleRate: sr,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
  • edit in utils/wav_metadata_test.go at line 441
    [4.14528][4.14528:14536]()
    }
    })
  • replacement in utils/wav_metadata_test.go at line 442
    [4.14537][4.14537:14692]()
    t.Run("should handle different channel counts", func(t *testing.T) {
    testCases := []struct {
    channels int
    }{
    {1}, // Mono
    {2}, // Stereo
    }
    [4.14537]
    [4.14692]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 447
    [4.14693][4.14693:15144]()
    for _, tc := range testCases {
    t.Run("", func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, "test_ch.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 1.0,
    sampleRate: 44100,
    channels: tc.channels,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    })
    [4.14693]
    [4.15144]
    if metadata.SampleRate != sr {
    t.Errorf("SampleRate incorrect: got %d, want %d", metadata.SampleRate, sr)
    }
    })
    }
    }
  • replacement in utils/wav_metadata_test.go at line 454
    [4.15145][4.15145:15266]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.15145]
    [4.15266]
    func TestParseWAVHeader_ChannelCounts(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 457
    [4.15267][4.15267:15400]()
    if metadata.Channels != tc.channels {
    t.Errorf("Channels incorrect: got %d, want %d", metadata.Channels, tc.channels)
    }
    [4.15267]
    [4.15400]
    testCases := []int{1, 2}
    for _, ch := range testCases {
    t.Run(fmt.Sprintf("%dch", ch), func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, fmt.Sprintf("test_ch_%d.wav", ch), struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 1.0,
    sampleRate: 44100,
    channels: ch,
    bitsPerSample: 16,
    comment: "",
    artist: "",
  • edit in utils/wav_metadata_test.go at line 475
    [4.15406][4.15406:15414]()
    }
    })
  • replacement in utils/wav_metadata_test.go at line 476
    [4.15415][4.15415:15572]()
    t.Run("should handle different bit depths", func(t *testing.T) {
    testCases := []struct {
    bitsPerSample int
    }{
    {8},
    {16},
    {24},
    {32},
    }
    [4.15415]
    [4.15572]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 481
    [4.15573][4.15573:16030]()
    for _, tc := range testCases {
    t.Run("", func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, "test_bits.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 1.0,
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: tc.bitsPerSample,
    comment: "",
    artist: "",
    })
    [4.15573]
    [4.16030]
    if metadata.Channels != ch {
    t.Errorf("Channels incorrect: got %d, want %d", metadata.Channels, ch)
    }
    })
    }
    }
  • replacement in utils/wav_metadata_test.go at line 488
    [4.16031][4.16031:16152]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.16031]
    [4.16152]
    func TestParseWAVHeader_BitDepths(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 491
    [4.16153][4.16153:16311]()
    if metadata.BitsPerSample != tc.bitsPerSample {
    t.Errorf("BitsPerSample incorrect: got %d, want %d", metadata.BitsPerSample, tc.bitsPerSample)
    }
    [4.16153]
    [4.16311]
    testCases := []int{8, 16, 24, 32}
    for _, bits := range testCases {
    t.Run(fmt.Sprintf("%dbit", bits), func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, fmt.Sprintf("test_bits_%d.wav", bits), struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 1.0,
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: bits,
    comment: "",
    artist: "",
  • edit in utils/wav_metadata_test.go at line 509
    [4.16317][4.16317:16325]()
    }
    })
  • replacement in utils/wav_metadata_test.go at line 510
    [4.16326][4.16326:16743]()
    t.Run("should handle very short durations", func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, "test_short.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 0.1, // 100ms
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    [4.16326]
    [4.16743]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    if metadata.BitsPerSample != bits {
    t.Errorf("BitsPerSample incorrect: got %d, want %d", metadata.BitsPerSample, bits)
    }
  • edit in utils/wav_metadata_test.go at line 519
    [4.16748]
    [4.16748]
    }
    }
  • replacement in utils/wav_metadata_test.go at line 522
    [4.16749][4.16749:16862]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.16749]
    [4.16862]
    func TestParseWAVHeader_ShortDuration(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 525
    [4.16863][4.16863:16999]()
    if metadata.Duration < 0.09 || metadata.Duration > 0.11 {
    t.Errorf("Duration incorrect: got %f, want ~0.1", metadata.Duration)
    }
    [4.16863]
    [4.16999]
    path := createTestWAVFile(t, tmpDir, "test_short.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 0.1, // 100ms
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
  • replacement in utils/wav_metadata_test.go at line 541
    [4.17004][4.17004:17426]()
    t.Run("should handle long durations", func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, "test_long.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 600.0, // 10 minutes
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    })
    [4.17004]
    [4.17426]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 546
    [4.17427][4.17427:17540]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.17427]
    [4.17540]
    if metadata.Duration < 0.09 || metadata.Duration > 0.11 {
    t.Errorf("Duration incorrect: got %f, want ~0.1", metadata.Duration)
    }
    }
  • replacement in utils/wav_metadata_test.go at line 551
    [4.17541][4.17541:17685]()
    if metadata.Duration < 599.0 || metadata.Duration > 601.0 {
    t.Errorf("Duration incorrect: got %f, want ~600.0", metadata.Duration)
    }
    })
    [4.17541]
    [4.17685]
    func TestParseWAVHeader_LongDuration(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 554
    [4.17686][4.17686:17884]()
    t.Run("should return error for non-existent file", func(t *testing.T) {
    _, err := ParseWAVHeader("/nonexistent/file.wav")
    if err == nil {
    t.Error("Expected error for non-existent file")
    }
    [4.17686]
    [4.17884]
    path := createTestWAVFile(t, tmpDir, "test_long.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 600.0, // 10 minutes
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
  • replacement in utils/wav_metadata_test.go at line 570
    [4.17889][4.17889:18173]()
    t.Run("should return error for non-WAV file", func(t *testing.T) {
    // Create a non-WAV file
    path := filepath.Join(tmpDir, "not_a_wav.txt")
    if err := os.WriteFile(path, []byte("This is not a WAV file"), 0644); err != nil {
    t.Fatalf("Failed to create test file: %v", err)
    }
    [4.17889]
    [4.18173]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 575
    [4.18174][4.18174:18279]()
    _, err := ParseWAVHeader(path)
    if err == nil {
    t.Error("Expected error for non-WAV file")
    }
    })
    [4.18174]
    [4.18279]
    if metadata.Duration < 599.0 || metadata.Duration > 601.0 {
    t.Errorf("Duration incorrect: got %f, want ~600.0", metadata.Duration)
    }
    }
  • replacement in utils/wav_metadata_test.go at line 580
    [4.18280][4.18280:18573]()
    t.Run("should return error for truncated file", func(t *testing.T) {
    // Create a file that's too small to be valid WAV
    path := filepath.Join(tmpDir, "truncated.wav")
    if err := os.WriteFile(path, []byte("RIFF"), 0644); err != nil {
    t.Fatalf("Failed to create test file: %v", err)
    }
    [4.18280]
    [4.18573]
    func TestParseWAVHeader_NonExistentFile(t *testing.T) {
    _, err := ParseWAVHeader("/nonexistent/file.wav")
    if err == nil {
    t.Error("Expected error for non-existent file")
    }
    }
  • replacement in utils/wav_metadata_test.go at line 587
    [4.18574][4.18574:18681]()
    _, err := ParseWAVHeader(path)
    if err == nil {
    t.Error("Expected error for truncated file")
    }
    })
    [4.18574]
    [4.18681]
    func TestParseWAVHeader_NonWAVFile(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 590
    [4.18682][4.18682:19098]()
    t.Run("should handle empty metadata strings", func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, "test_empty.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 10.0,
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    })
    [4.18682]
    [4.19098]
    path := filepath.Join(tmpDir, "not_a_wav.txt")
    if err := os.WriteFile(path, []byte("This is not a WAV file"), 0644); err != nil {
    t.Fatalf("Failed to create test file: %v", err)
    }
    _, err := ParseWAVHeader(path)
    if err == nil {
    t.Error("Expected error for non-WAV file")
    }
    }
    func TestParseWAVHeader_TruncatedFile(t *testing.T) {
    tmpDir := t.TempDir()
    path := filepath.Join(tmpDir, "truncated.wav")
    if err := os.WriteFile(path, []byte("RIFF"), 0644); err != nil {
    t.Fatalf("Failed to create test file: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 609
    [4.19099][4.19099:19212]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.19099]
    [4.19212]
    _, err := ParseWAVHeader(path)
    if err == nil {
    t.Error("Expected error for truncated file")
    }
    }
  • replacement in utils/wav_metadata_test.go at line 615
    [4.19213][4.19213:19312]()
    if metadata.Comment != "" {
    t.Errorf("Comment should be empty, got %q", metadata.Comment)
    }
    [4.19213]
    [4.19312]
    func TestParseWAVHeader_EmptyMetadataStrings(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 618
    [4.19313][4.19313:19409]()
    if metadata.Artist != "" {
    t.Errorf("Artist should be empty, got %q", metadata.Artist)
    }
    [4.19313]
    [4.19409]
    path := createTestWAVFile(t, tmpDir, "test_empty.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 10.0,
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
  • replacement in utils/wav_metadata_test.go at line 634
    [4.19414][4.19414:19720]()
    t.Run("should handle long comment strings", func(t *testing.T) {
    longComment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at medium gain while battery was 4.3V and temperature was 15.8C. This is a very long comment with additional information about the recording session."
    [4.19414]
    [4.19720]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 639
    [4.19721][4.19721:20085]()
    path := createTestWAVFile(t, tmpDir, "test_long_comment.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 10.0,
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: longComment,
    artist: "",
    })
    [4.19721]
    [4.20085]
    if metadata.Comment != "" {
    t.Errorf("Comment should be empty, got %q", metadata.Comment)
    }
    if metadata.Artist != "" {
    t.Errorf("Artist should be empty, got %q", metadata.Artist)
    }
    }
    func TestParseWAVHeader_LongCommentString(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 650
    [4.20086][4.20086:20199]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.20086]
    [4.20199]
    longComment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at medium gain while battery was 4.3V and temperature was 15.8C. This is a very long comment with additional information about the recording session."
  • replacement in utils/wav_metadata_test.go at line 652
    [4.20200][4.20200:20324]()
    if metadata.Comment != longComment {
    t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, longComment)
    }
    [4.20200]
    [4.20324]
    path := createTestWAVFile(t, tmpDir, "test_long_comment.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 10.0,
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: longComment,
    artist: "",
  • replacement in utils/wav_metadata_test.go at line 668
    [4.20329][4.20329:20747]()
    t.Run("should extract file modification time", func(t *testing.T) {
    path := createTestWAVFile(t, tmpDir, "test_modtime.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 5.0,
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    })
    [4.20329]
    [4.20747]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    if metadata.Comment != longComment {
    t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, longComment)
    }
    }
    func TestParseWAVHeader_FileModTime(t *testing.T) {
    tmpDir := t.TempDir()
  • replacement in utils/wav_metadata_test.go at line 681
    [4.20748][4.20748:20906]()
    // Get expected mod time
    info, err := os.Stat(path)
    if err != nil {
    t.Fatalf("Failed to stat file: %v", err)
    }
    expectedModTime := info.ModTime()
    [4.20748]
    [4.20906]
    path := createTestWAVFile(t, tmpDir, "test_modtime.wav", struct {
    duration float64
    sampleRate int
    channels int
    bitsPerSample int
    comment string
    artist string
    }{
    duration: 5.0,
    sampleRate: 44100,
    channels: 1,
    bitsPerSample: 16,
    comment: "",
    artist: "",
    })
  • replacement in utils/wav_metadata_test.go at line 697
    [4.20907][4.20907:21020]()
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
    [4.20907]
    [4.21020]
    info, err := os.Stat(path)
    if err != nil {
    t.Fatalf("Failed to stat file: %v", err)
    }
    expectedModTime := info.ModTime()
  • replacement in utils/wav_metadata_test.go at line 703
    [4.21021][4.21021:21301]()
    // Allow 1 second tolerance for filesystem granularity
    diff := metadata.FileModTime.Sub(expectedModTime)
    if diff < -1*time.Second || diff > 1*time.Second {
    t.Errorf("FileModTime incorrect: got %v, want %v (diff: %v)",
    metadata.FileModTime, expectedModTime, diff)
    }
    [4.21021]
    [4.21301]
    metadata, err := ParseWAVHeader(path)
    if err != nil {
    t.Fatalf("Failed to parse WAV header: %v", err)
    }
  • replacement in utils/wav_metadata_test.go at line 708
    [4.21302][4.21302:21428]()
    // Ensure FileModTime is not zero
    if metadata.FileModTime.IsZero() {
    t.Error("FileModTime should not be zero")
    }
    })
    [4.21302]
    [4.21428]
    // Allow 1 second tolerance for filesystem granularity
    diff := metadata.FileModTime.Sub(expectedModTime)
    if diff < -1*time.Second || diff > 1*time.Second {
    t.Errorf("FileModTime incorrect: got %v, want %v (diff: %v)",
    metadata.FileModTime, expectedModTime, diff)
    }
    if metadata.FileModTime.IsZero() {
    t.Error("FileModTime should not be zero")
    }
  • edit in tui/classify.go at line 206
    [4.232167][4.232167:232216]()
    // If in comment mode, route to comment handler
  • edit in tui/classify.go at line 209
    [4.232272][4.232272:232316]()
    // If in clip mode, route to clip handler
  • edit in tui/classify.go at line 215
    [4.232380][4.232380:232526]()
    key := msg.Key()
    // Secondary-wait mode: next keypress is interpreted as a calltype key
    // for the species we just labeled via Shift+primary.
  • replacement in tui/classify.go at line 216
    [4.232561][4.232561:232803]()
    primary := m.awaitingSecondaryFor
    m.awaitingSecondaryFor = ""
    // Esc cancels wait mode; species stays labeled without calltype,
    // segment does not advance.
    if key.Code == tea.KeyEscape || key.Code == tea.KeyEsc {
    return m, nil
    [4.232561]
    [4.232803]
    if handled, model, cmd := m.handleSecondaryWait(msg); handled {
    return model, cmd
  • edit in tui/classify.go at line 219
    [4.232807]
    [4.232807]
    }
  • replacement in tui/classify.go at line 221
    [4.232808][4.232808:233218]()
    s := msg.String()
    if len(s) == 1 {
    if callType, ok := m.state.Config.SecondaryBindings[primary][s]; ok {
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    m.state.ApplyCallTypeOnly(callType)
    if err := m.state.Save(); err != nil {
    m.err = err.Error()
    }
    if !m.state.NextSegment() {
    m.quitting = true
    return m, tea.Quit
    }
    return m, m.segmentChangeCmd()
    [4.232808]
    [4.233218]
    if handled, model, cmd := m.handleSpecialKey(msg); handled {
    return model, cmd
    }
    return m.handleSwitchKey(msg)
    }
    // handleSecondaryWait handles keypresses while awaiting a secondary calltype key.
    // Returns (true, model, cmd) if the key was consumed; (false, model, cmd) to fall through.
    func (m Model) handleSecondaryWait(msg tea.KeyPressMsg) (bool, tea.Model, tea.Cmd) {
    primary := m.awaitingSecondaryFor
    m.awaitingSecondaryFor = ""
    if msg.Key().Code == tea.KeyEscape || msg.Key().Code == tea.KeyEsc {
    return true, m, nil
    }
    s := msg.String()
    if len(s) == 1 {
    if callType, ok := m.state.Config.SecondaryBindings[primary][s]; ok {
    m.stopPlayer()
    m.state.ApplyCallTypeOnly(callType)
    if err := m.state.Save(); err != nil {
    m.err = err.Error()
  • edit in tui/classify.go at line 246
    [4.233223]
    [4.233223]
    model, cmd := m.advanceOrQuit()
    return true, model, cmd
  • edit in tui/classify.go at line 249
    [4.233227][4.233227:233298]()
    // Unknown key — fall through to normal handling of this keypress.
  • edit in tui/classify.go at line 250
    [4.233301]
    [4.233301]
    return false, m, nil
    }
  • replacement in tui/classify.go at line 253
    [4.233302][4.233302:233371]()
    // Handle Enter key (main or numpad, check code to catch modifiers)
    [4.233302]
    [4.233371]
    // handleSpecialKey handles single-key-code bindings (Enter, Esc, Space, Ctrl+S).
    // Returns (true, model, cmd) if the key was consumed.
    func (m Model) handleSpecialKey(msg tea.KeyPressMsg) (bool, tea.Model, tea.Cmd) {
    key := msg.Key()
  • replacement in tui/classify.go at line 266
    [4.233594][4.233594:233621]()
    return m, playbackTick()
    [4.233594]
    [4.233621]
    return true, m, playbackTick()
  • edit in tui/classify.go at line 269
    [4.233625][4.233625:233659]()
    // Check for Escape key for quit
  • replacement in tui/classify.go at line 270
    [4.233717][4.233717:233775]()
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    [4.233717]
    [4.233775]
    m.stopPlayer()
  • replacement in tui/classify.go at line 272
    [4.233795][4.233795:233816]()
    return m, tea.Quit
    [4.233795]
    [4.233816]
    return true, m, tea.Quit
  • edit in tui/classify.go at line 275
    [4.233820][4.233820:233866]()
    // Check for Space key (open comment dialog)
  • replacement in tui/classify.go at line 277
    [4.233943][4.233943:233998]()
    m.commentCursor = len(m.commentText) // start at end
    [4.233943]
    [4.233998]
    m.commentCursor = len(m.commentText)
  • replacement in tui/classify.go at line 279
    [4.234021][4.234021:234037]()
    return m, nil
    [4.234021]
    [4.234037]
    return true, m, nil
  • edit in tui/classify.go at line 282
    [4.234041][4.234041:234081]()
    // Check for Ctrl+S (save clip dialog)
  • replacement in tui/classify.go at line 285
    [4.234151][4.234151:234167]()
    return m, nil
    [4.234151]
    [4.234167]
    return true, m, nil
  • edit in tui/classify.go at line 288
    [4.234171]
    [4.234171]
    return false, m, nil
    }
    // handleSwitchKey handles string-based key bindings (ctrl+c, arrows, digits, bindings).
    func (m Model) handleSwitchKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
  • replacement in tui/classify.go at line 295
    [4.234210][4.234210:234268]()
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    [4.234210]
    [4.234268]
    m.stopPlayer()
  • replacement in tui/classify.go at line 300
    [4.234329][4.234329:234409]()
    // Previous segment
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    [4.234329]
    [4.234409]
    m.stopPlayer()
  • replacement in tui/classify.go at line 305
    [4.234487][4.234487:234573]()
    // Next segment (no edit)
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    [4.234487]
    [4.234573]
    m.stopPlayer()
  • edit in tui/classify.go at line 313
    [4.234700][4.234700:234721]()
    // Toggle bookmark
  • replacement in tui/classify.go at line 320
    [4.234849][4.234849:234930]()
    // Previous bookmark
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    [4.234849]
    [4.234930]
    m.stopPlayer()
  • replacement in tui/classify.go at line 328
    [4.235062][4.235062:235139]()
    // Next bookmark
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    [4.235062]
    [4.235139]
    m.stopPlayer()
  • replacement in tui/classify.go at line 336
    [4.235266][4.235266:235379]()
    // Confirm existing label (upgrade certainty to 100)
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    [4.235266]
    [4.235379]
    m.stopPlayer()
  • replacement in tui/classify.go at line 343
    [4.235502][4.235502:235612]()
    if !m.state.NextSegment() {
    m.quitting = true
    return m, tea.Quit
    }
    return m, m.segmentChangeCmd()
    [4.235502]
    [4.235612]
    return m.advanceOrQuit()
  • replacement in tui/classify.go at line 346
    [4.235623][4.235623:235695]()
    // Check for binding
    s := msg.String()
    if len(s) == 1 {
    k := s
    [4.235623]
    [4.235695]
    return m.handleBindingKey(msg)
    }
    }
  • replacement in tui/classify.go at line 350
    [4.235696][4.235696:236390]()
    // Shift+letter: if the lowercase primary has secondary bindings,
    // label species-only and enter wait mode. Otherwise map to the
    // lowercase equivalent and dispatch as a normal primary keypress.
    if key.Mod&tea.ModShift != 0 {
    lower := strings.ToLower(s)
    if lower != s {
    if m.state.HasSecondary(lower) {
    if result := m.state.ParseKeyBuffer(lower); result != nil {
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    m.state.ApplyBinding(&tools.BindingResult{Species: result.Species})
    if err := m.state.Save(); err != nil {
    m.err = err.Error()
    }
    m.awaitingSecondaryFor = lower
    return m, nil
    }
    [4.235696]
    [4.236390]
    // handleBindingKey handles single-character key bindings (species/calltype shortcuts).
    func (m Model) handleBindingKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
    s := msg.String()
    if len(s) != 1 {
    return m, nil
    }
    k := s
    key := msg.Key()
    // Shift+letter: if the lowercase primary has secondary bindings,
    // label species-only and enter wait mode. Otherwise map to the
    // lowercase equivalent and dispatch as a normal primary keypress.
    if key.Mod&tea.ModShift != 0 {
    lower := strings.ToLower(s)
    if lower != s {
    if m.state.HasSecondary(lower) {
    if result := m.state.ParseKeyBuffer(lower); result != nil {
    m.stopPlayer()
    m.state.ApplyBinding(&tools.BindingResult{Species: result.Species})
    if err := m.state.Save(); err != nil {
    m.err = err.Error()
  • replacement in tui/classify.go at line 373
    [4.236397][4.236397:236412]()
    k = lower
    [4.236397]
    [4.236412]
    m.awaitingSecondaryFor = lower
    return m, nil
  • edit in tui/classify.go at line 377
    [4.236423]
    [4.236423]
    k = lower
    }
    }
  • replacement in tui/classify.go at line 381
    [4.236424][4.236424:236779]()
    if result := m.state.ParseKeyBuffer(k); result != nil {
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    m.state.ApplyBinding(result)
    if err := m.state.Save(); err != nil {
    m.err = err.Error()
    }
    if !m.state.NextSegment() {
    m.quitting = true
    return m, tea.Quit
    }
    return m, m.segmentChangeCmd()
    }
    [4.236424]
    [4.236779]
    if result := m.state.ParseKeyBuffer(k); result != nil {
    m.stopPlayer()
    m.state.ApplyBinding(result)
    if err := m.state.Save(); err != nil {
    m.err = err.Error()
  • replacement in tui/classify.go at line 387
    [4.236783][4.236783:236799]()
    return m, nil
    [4.236783]
    [4.236799]
    return m.advanceOrQuit()
    }
    return m, nil
    }
    // stopPlayer stops the audio player if it exists.
    func (m Model) stopPlayer() {
    if m.state.Player != nil {
    m.state.Player.Stop()
    }
    }
    // advanceOrQuit advances to the next segment, or quits if none remain.
    func (m Model) advanceOrQuit() (tea.Model, tea.Cmd) {
    if !m.state.NextSegment() {
    m.quitting = true
    return m, tea.Quit
  • edit in tui/classify.go at line 405
    [4.236802]
    [4.236802]
    return m, m.segmentChangeCmd()
  • edit in lint_test.go at line 8
    [4.776214][4.776214:776423]()
    func TestGolangciLint(t *testing.T) {
    cmd := exec.Command("golangci-lint", "run", "./...")
    cmd.Dir = "."
    out, err := cmd.CombinedOutput()
    if err != nil {
    t.Errorf("golangci-lint failed:\n%s", out)
    }
    }
  • replacement in lint_test.go at line 20
    [4.776737][4.776737:776813]()
    func TestDeadcode(t *testing.T) {
    cmd := exec.Command("deadcode", "./...")
    [4.776737]
    [4.776813]
    func TestFix(t *testing.T) {
    cmd := exec.Command("go", "fix", "./...")
  • replacement in lint_test.go at line 25
    [4.776879][4.776879:776919]()
    t.Errorf("deadcode failed:\n%s", out)
    [4.776879]
    [4.776919]
    t.Errorf("go fix failed:\n%s", out)
  • replacement in lint_test.go at line 29
    [3.7662][3.7662:7734]()
    func TestFix(t *testing.T) {
    cmd := exec.Command("go", "fix", "./...")
    [3.7662]
    [3.7734]
    func TestGolangciLint(t *testing.T) {
    cmd := exec.Command("golangci-lint", "run", "./...")
    cmd.Dir = "."
    out, err := cmd.CombinedOutput()
    if err != nil {
    t.Errorf("golangci-lint failed:\n%s", out)
    }
    }
    func TestDeadcode(t *testing.T) {
    cmd := exec.Command("deadcode", "./...")
  • replacement in lint_test.go at line 43
    [3.7800][3.7800:7838]()
    t.Errorf("go fix failed:\n%s", out)
    [3.7800]
    [3.7838]
    t.Errorf("deadcode failed:\n%s", out)
  • replacement in db/tx_logger.go at line 332
    [4.850798][4.850798:850864]()
    // marshalParam converts a parameter to a JSON-serializable value
    [4.850798]
    [4.850864]
    // marshalParam converts a parameter to a JSON-serializable value.
    // Pointer types (including all *T) are handled via reflection: nil → null,
    // non-nil → dereference and recurse.
  • replacement in db/tx_logger.go at line 340
    [4.850935][4.850935:851050]()
    switch v := param.(type) {
    case time.Time:
    return v.Format(time.RFC3339Nano)
    case *time.Time:
    if v == nil {
    [4.850935]
    [4.851050]
    // Handle pointer types via reflection: nil → null, else dereference and recurse.
    // This covers all *T cases (including *time.Time) without explicit type switches.
    rv := reflect.ValueOf(param)
    if rv.Kind() == reflect.Pointer {
    if rv.IsNil() {
  • edit in db/tx_logger.go at line 347
    [4.851068]
    [4.851068]
    return marshalParam(rv.Elem().Interface())
    }
    // Value types
    switch v := param.(type) {
    case time.Time:
  • edit in db/tx_logger.go at line 355
    [4.851118][4.851118:852131]()
    return v
    case *string:
    if v == nil {
    return nil
    }
    return *v
    case int:
    return v
    case *int:
    if v == nil {
    return nil
    }
    return *v
    case int8:
    return v
    case *int8:
    if v == nil {
    return nil
    }
    return *v
    case int16:
    return v
    case *int16:
    if v == nil {
    return nil
    }
    return *v
    case int32:
    return v
    case *int32:
    if v == nil {
    return nil
    }
    return *v
    case int64:
    return v
    case *int64:
    if v == nil {
    return nil
    }
    return *v
    case uint:
    return v
    case *uint:
    if v == nil {
    return nil
    }
    return *v
    case uint8:
    return v
    case *uint8:
    if v == nil {
    return nil
    }
    return *v
    case uint16:
    return v
    case *uint16:
    if v == nil {
    return nil
    }
    return *v
    case uint32:
    return v
    case *uint32:
    if v == nil {
    return nil
    }
    return *v
    case uint64:
    return v
    case *uint64:
    if v == nil {
    return nil
    }
    return *v
    case float32:
    return v
    case *float32:
    if v == nil {
    return nil
    }
    return *v
    case float64:
  • replacement in db/tx_logger.go at line 356
    [4.852142][4.852142:852216]()
    case *float64:
    if v == nil {
    return nil
    }
    return *v
    case bool:
    [4.852142]
    [4.852216]
    case int, int8, int16, int32, int64,
    uint, uint8, uint16, uint32, uint64,
    float32, float64, bool:
  • edit in db/tx_logger.go at line 360
    [4.852227][4.852227:852286]()
    case *bool:
    if v == nil {
    return nil
    }
    return *v
  • edit in db/tx_logger.go at line 363
    [4.852321][4.852321:852676]()
    // Handle pointer types via reflection (e.g., *GainLevel, *CustomType)
    rv := reflect.ValueOf(param)
    if rv.Kind() == reflect.Pointer {
    if rv.IsNil() {
    return nil
    }
    // Dereference and recursively marshal the underlying value
    return marshalParam(rv.Elem().Interface())
    }
    // For other types, try to convert to string via fmt.Sprintf
  • edit in cmd/calls_clip.go at line 12
    [4.1133177]
    [4.1133177]
    // clipArgParser holds state for parsing CLI arguments incrementally.
    type clipArgParser struct {
    args []string
    i int
    }
    // nextValue returns the next argument after the current flag, or exits with an error.
    func (p *clipArgParser) nextValue(flag string) string {
    if p.i+1 >= len(p.args) {
    fmt.Fprintf(os.Stderr, "Error: %s requires a value\n", flag)
    os.Exit(1)
    }
    v := p.args[p.i+1]
    p.i += 2
    return v
    }
    // nextInt parses the next argument as an integer, or exits with an error.
    func (p *clipArgParser) nextInt(flag string) int {
    s := p.nextValue(flag)
    v, err := strconv.Atoi(s)
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: %s must be an integer\n", flag)
    os.Exit(1)
    }
    return v
    }
  • edit in cmd/calls_clip.go at line 41
    [4.1133178]
    [4.1133178]
    // nextFloat parses the next argument as a float64, or exits with an error.
    func (p *clipArgParser) nextFloat(flag string) float64 {
    s := p.nextValue(flag)
    v, err := strconv.ParseFloat(s, 64)
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: %s must be a number\n", flag)
    os.Exit(1)
    }
    return v
    }
  • replacement in cmd/calls_clip.go at line 111
    [4.1137119][4.1137119:1137165]()
    i := 0
    for i < len(args) {
    arg := args[i]
    [4.1137119]
    [4.1137165]
    p := &clipArgParser{args: args}
    for p.i < len(args) {
    arg := args[p.i]
  • replacement in cmd/calls_clip.go at line 117
    [4.1137198][4.1137198:1137337]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --file requires a value\n")
    os.Exit(1)
    }
    file = args[i+1]
    i += 2
    [4.1137198]
    [4.1137337]
    file = p.nextValue(arg)
  • replacement in cmd/calls_clip.go at line 119
    [4.1137356][4.1137356:1137499]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --folder requires a value\n")
    os.Exit(1)
    }
    folder = args[i+1]
    i += 2
    [4.1137356]
    [4.1137499]
    folder = p.nextValue(arg)
  • replacement in cmd/calls_clip.go at line 121
    [4.1137518][4.1137518:1137661]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --output requires a value\n")
    os.Exit(1)
    }
    output = args[i+1]
    i += 2
    [4.1137518]
    [4.1137661]
    output = p.nextValue(arg)
  • replacement in cmd/calls_clip.go at line 123
    [4.1137680][4.1137680:1137823]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --prefix requires a value\n")
    os.Exit(1)
    }
    prefix = args[i+1]
    i += 2
    [4.1137680]
    [4.1137823]
    prefix = p.nextValue(arg)
  • edit in cmd/calls_clip.go at line 125
    [4.1137842][4.1137842:1137952]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --filter requires a value\n")
    os.Exit(1)
    }
  • replacement in cmd/calls_clip.go at line 129
    [4.1138068][4.1138068:1138101]()
    filter = args[i+1]
    i += 2
    [4.1138068]
    [4.1138101]
    filter = p.nextValue(arg)
  • edit in cmd/calls_clip.go at line 131
    [4.1138121][4.1138121:1138232]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --species requires a value\n")
    os.Exit(1)
    }
  • replacement in cmd/calls_clip.go at line 135
    [4.1138350][4.1138350:1138384]()
    species = args[i+1]
    i += 2
    [4.1138350]
    [4.1138384]
    species = p.nextValue(arg)
  • replacement in cmd/calls_clip.go at line 137
    [4.1138406][4.1138406:1138665]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --certainty requires a value\n")
    os.Exit(1)
    }
    v, err := strconv.Atoi(args[i+1])
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: --certainty must be an integer\n")
    os.Exit(1)
    }
    [4.1138406]
    [4.1138665]
    v := p.nextInt(arg)
  • edit in cmd/calls_clip.go at line 143
    [4.1138804][4.1138804:1138815]()
    i += 2
  • replacement in cmd/calls_clip.go at line 144
    [4.1138832][4.1138832:1139104]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --size requires a value\n")
    os.Exit(1)
    }
    v, err := strconv.Atoi(args[i+1])
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: --size must be an integer\n")
    os.Exit(1)
    }
    size = v
    i += 2
    [4.1138832]
    [4.1139104]
    size = p.nextInt(arg)
  • replacement in cmd/calls_clip.go at line 147
    [4.1139138][4.1139138:1139146]()
    i++
    [4.1139138]
    [4.1139146]
    p.i++
  • replacement in cmd/calls_clip.go at line 150
    [4.1139185][4.1139185:1139193]()
    i++
    [4.1139185]
    [4.1139193]
    p.i++
  • replacement in cmd/calls_clip.go at line 153
    [4.1139227][4.1139227:1139235]()
    i++
    [4.1139227]
    [4.1139235]
    p.i++
  • replacement in cmd/calls_clip.go at line 156
    [4.1139265][4.1139265:1139273]()
    i++
    [4.1139265]
    [4.1139273]
    p.i++
  • replacement in cmd/calls_clip.go at line 158
    [4.1139289][4.1139289:1139555]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --lat requires a value\n")
    os.Exit(1)
    }
    v, err := strconv.ParseFloat(args[i+1], 64)
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: --lat must be a number\n")
    os.Exit(1)
    }
    lat = v
    [4.1139289]
    [4.1139555]
    lat = p.nextFloat(arg)
  • edit in cmd/calls_clip.go at line 160
    [4.1139572][4.1139572:1139583]()
    i += 2
  • replacement in cmd/calls_clip.go at line 161
    [4.1139599][4.1139599:1139865]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --lng requires a value\n")
    os.Exit(1)
    }
    v, err := strconv.ParseFloat(args[i+1], 64)
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: --lng must be a number\n")
    os.Exit(1)
    }
    lng = v
    [4.1139599]
    [4.1139865]
    lng = p.nextFloat(arg)
  • edit in cmd/calls_clip.go at line 163
    [4.1139882][4.1139882:1139893]()
    i += 2
  • replacement in cmd/calls_clip.go at line 164
    [4.1139914][4.1139914:1140061]()
    if i+1 >= len(args) {
    fmt.Fprintf(os.Stderr, "Error: --timezone requires a value\n")
    os.Exit(1)
    }
    timezone = args[i+1]
    i += 2
    [4.1139914]
    [4.1140061]
    timezone = p.nextValue(arg)
  • edit in cmd/calls_clip.go at line 168
    [4.1140118][4.1140118:1140119]()
  • edit in cmd/calls_clip.go at line 169
    [4.1140130][4.1140130:1140160]()
    // Check for unknown flags
  • replacement in cmd/calls_clip.go at line 174
    [4.1140301][4.1140301:1140308]()
    i++
    [4.1140301]
    [4.1140308]
    p.i++
  • edit in CHANGELOG.md at line 4
    [4.1198010]
    [2.2429]
    ## [2026-05-04] Reduce cyclomatic complexity of marshalParam, RunCallsClip, TestParseWAVHeader
    Three functions exceeded gocyclo threshold of 40:
    1. **`marshalParam` (db/tx_logger.go) — 50 → ~8**: Replaced 25 repetitive pointer-type
    cases (`*int`, `*string`, `*float64`, etc.) with the existing reflection-based pointer
    handling that was already in the `default` branch. All value types kept as a single
    multi-type switch case.
    2. **`RunCallsClip` (cmd/calls_clip.go) — 50 → 35**: Extracted `clipArgParser` struct
    with `nextValue()`, `nextInt()`, `nextFloat()` helpers that encapsulate the
    "check bounds + advance + error" pattern repeated 13 times in the arg loop.
    3. **`TestParseWAVHeader` (utils/wav_metadata_test.go) — 44 → ~5 each**: Split the
    monolithic test with 14 `t.Run` sub-tests into 14 top-level `TestParseWAVHeader_*`
    functions. Each now has minimal complexity.
    ## [2026-05-04] Reduce cyclomatic complexity of handleKey (51 → 6)
    Refactored `Model.handleKey` in `tui/classify.go` from a single 51-complexity
    monolith into 6 focused methods plus 2 utility helpers:
    - `handleKey` (6) — dispatcher routing to mode-specific handlers
    - `handleSecondaryWait` (6) — awaiting-secondary-calltype logic
    - `handleSpecialKey` (9) — Enter/Esc/Space/Ctrl+S
    - `handleSwitchKey` (14) — switch-based key dispatch
    - `handleBindingKey` (9) — single-char species/calltype bindings
    - `stopPlayer` — extracted repeated nil-check + Stop pattern
    - `advanceOrQuit` — extracted repeated next-segment-or-quit pattern
    No behavioral changes.