cyclo fails

quietlight
May 19, 2026, 3:23 AM
DGGHHKY6RVX4GMFAMUURMOQAOR4PRW6GBAJXYBAOAAR6AEBW6QYQC

Dependencies

  • [2] GSLFNY4P cilint cyclo and gocyclo now both agree at max 12
  • [3] XVJ2RNWU changelog
  • [4] V2HX6HEB claude going nuts all over the place
  • [5] JMDW37LV new import tests
  • [6] WVNOO43B some complex tests require changes to lint rules
  • [7] E27ZWCDP cyclo over 18
  • [8] I4CMOMXF dot files
  • [9] BZ6KQRYD added complexity lint test
  • [10] ZDZDASRT complexity over 12 now gone, but have some lint fails
  • [11] XU7FTYK3 third phase of utils refactor, wav/
  • [12] RUVJ3V4N cyclo to 14 now
  • [13] 2P27XV3D fixed cyclo over 30
  • [14] DD3LCTLZ tidy up lat lng timezone api for calls classify and push certainty
  • [15] JAT3DXOL cyclo over 15
  • [16] ZCCQ4P5T reduce complexity to under 14, gocyclo but cilint test still has 3 functions over
  • [17] LHZQOX64 complexity in tools calls and import
  • [18] QFPEKXL5 ck 6
  • [19] HYCZTLSZ fixed tests with cyclo over 15
  • [20] DNLR36BZ minor comments
  • [21] NS4TDPLN cyclomatic complexity
  • [22] SMWSHUOW cyclo over 15
  • [23] KZKLAINJ run out of space on nest, cleaned out
  • [24] T2WZBTVF cyclo 22
  • [25] LBWQJEDH minor refactor and more tests for utils/

Change contents

  • edit in wav/wav_metadata.go at line 308
    [7.34525]
    [7.34525]
    }
    // handleFmtChunk processes the fmt chunk and updates info.
    func handleFmtChunk(file *os.File, chunkSize int64, info *wavChunkInfo) error {
    fmtData := make([]byte, chunkSize)
    if _, err := io.ReadFull(file, fmtData); err != nil {
    return fmt.Errorf("failed to read fmt chunk: %w", err)
    }
    if len(fmtData) >= 16 {
    info.channels = int(binary.LittleEndian.Uint16(fmtData[2:4]))
    info.sampleRate = int(binary.LittleEndian.Uint32(fmtData[4:8]))
    info.bitsPerSample = int(binary.LittleEndian.Uint16(fmtData[14:16]))
    }
    return nil
  • edit in wav/wav_metadata.go at line 324
    [7.34528]
    [7.34528]
    // skipUnknownChunk skips an unknown chunk and its padding.
    func skipUnknownChunk(file *os.File, chunkSize int64) error {
    if _, err := file.Seek(chunkSize, io.SeekCurrent); err != nil {
    return fmt.Errorf("failed to skip chunk: %w", err)
    }
    if chunkSize%2 != 0 {
    if _, err := file.Seek(1, io.SeekCurrent); err != nil {
    return fmt.Errorf("failed to skip padding: %w", err)
    }
    }
    return nil
    }
  • replacement in wav/wav_metadata.go at line 355
    [7.35117][7.35117:35277]()
    fmtData := make([]byte, chunkSize)
    if _, err := io.ReadFull(file, fmtData); err != nil {
    return info, fmt.Errorf("failed to read fmt chunk: %w", err)
    [7.35117]
    [7.35277]
    if err := handleFmtChunk(file, chunkSize, &info); err != nil {
    return info, err
  • edit in wav/wav_metadata.go at line 358
    [7.35282][7.35282:35521]()
    if len(fmtData) >= 16 {
    info.channels = int(binary.LittleEndian.Uint16(fmtData[2:4]))
    info.sampleRate = int(binary.LittleEndian.Uint32(fmtData[4:8]))
    info.bitsPerSample = int(binary.LittleEndian.Uint16(fmtData[14:16]))
    }
  • replacement in wav/wav_metadata.go at line 363
    [7.35649][7.35649:35777]()
    if _, err := file.Seek(chunkSize, io.SeekCurrent); err != nil {
    return info, fmt.Errorf("failed to skip chunk: %w", err)
    [7.35649]
    [7.35777]
    if err := skipUnknownChunk(file, chunkSize); err != nil {
    return info, err
  • edit in wav/wav_metadata.go at line 366
    [7.35782]
    [7.35782]
    continue
  • edit in wav/wav_metadata.go at line 369
    [7.35787]
    [7.35787]
    // Skip padding for fmt chunk (data chunk returns above)
  • replacement in wav/filename_parser.go at line 122
    [4.16667][4.16667:16884]()
    // detectDateFormat analyzes filenames to determine the date format
    func detectDateFormat(filenames []string) (DateFormat, error) {
    // Extract all date parts from filenames
    var parts []dateParts
    var has8Digit bool
    [4.16667]
    [4.16884]
    // datePartsResult holds the result of collecting date parts from filenames
    type datePartsResult struct {
    parts []dateParts
    has8Digit bool
    }
  • edit in wav/filename_parser.go at line 128
    [4.16885]
    [4.16885]
    // collectDateParts extracts date parts from filenames for format detection.
    func collectDateParts(filenames []string) datePartsResult {
    var result datePartsResult
  • edit in wav/filename_parser.go at line 139
    [4.17083][4.17083:17125]()
    // Check for 8-digit format (YYYYMMDD)
  • replacement in wav/filename_parser.go at line 140
    [4.17150][4.17150:17170]()
    has8Digit = true
    [4.17150]
    [4.17170]
    result.has8Digit = true
  • edit in wav/filename_parser.go at line 144
    [4.17187][4.17187:17213]()
    // Parse 6-digit format
  • replacement in wav/filename_parser.go at line 148
    [4.17354][4.17354:17412]()
    parts = append(parts, dateParts{x1: x1, m: m, x2: x2})
    [4.17354]
    [4.17412]
    result.parts = append(result.parts, dateParts{x1: x1, m: m, x2: x2})
  • edit in wav/filename_parser.go at line 151
    [4.17419]
    [4.17419]
    return result
    }
  • edit in wav/filename_parser.go at line 154
    [4.17420]
    [4.17420]
    // determineFormatFromParts determines the date format from collected parts.
    func determineFormatFromParts(result datePartsResult) (DateFormat, error) {
  • replacement in wav/filename_parser.go at line 157
    [4.17468][4.17468:17503]()
    if has8Digit && len(parts) == 0 {
    [4.17468]
    [4.17503]
    if result.has8Digit && len(result.parts) == 0 {
  • replacement in wav/filename_parser.go at line 162
    [4.17581][4.17581:17615]()
    if has8Digit && len(parts) > 0 {
    [4.17581]
    [4.17615]
    if result.has8Digit && len(result.parts) > 0 {
  • replacement in wav/filename_parser.go at line 167
    [4.17743][4.17743:17765]()
    if len(parts) == 0 {
    [4.17743]
    [4.17765]
    if len(result.parts) == 0 {
  • replacement in wav/filename_parser.go at line 172
    [4.17910][4.17910:17932]()
    if len(parts) == 1 {
    [4.17910]
    [4.17932]
    if len(result.parts) == 1 {
  • replacement in wav/filename_parser.go at line 177
    [4.18095][4.18095:18365]()
    // Compare uniqueness of x1 (first 2 digits) vs x2 (last 2 digits)
    // Day values vary more than year values across recordings
    uniqueX1 := countUnique(parts, func(p dateParts) int { return p.x1 })
    uniqueX2 := countUnique(parts, func(p dateParts) int { return p.x2 })
    [4.18095]
    [4.18365]
    uniqueX1 := countUnique(result.parts, func(p dateParts) int { return p.x1 })
    uniqueX2 := countUnique(result.parts, func(p dateParts) int { return p.x2 })
  • edit in wav/filename_parser.go at line 181
    [4.18393][4.18393:18459]()
    // x2 has more variance → likely day values → YYMMDD format
  • edit in wav/filename_parser.go at line 182
    [4.18487][4.18487:18591]()
    } else {
    // x1 has more variance → likely day values → DDMMYY format
    return Format6DDMMYY, nil
  • edit in wav/filename_parser.go at line 183
    [4.18594]
    [4.18594]
    return Format6DDMMYY, nil
    }
    // detectDateFormat analyzes filenames to determine the date format
    func detectDateFormat(filenames []string) (DateFormat, error) {
    result := collectDateParts(filenames)
    return determineFormatFromParts(result)
  • edit in utils/fs.go at line 35
    [7.281]
    [7.281]
    }
    // shouldSkipHidden checks if a path should be skipped due to hidden file rules.
    func shouldSkipHidden(name, path, rootPath string, info os.FileInfo, skipHidden bool) (bool, error) {
    if !skipHidden || !strings.HasPrefix(name, ".") || path == rootPath {
    return false, nil
    }
    if info.IsDir() {
    return true, filepath.SkipDir
    }
    return true, nil
  • edit in utils/fs.go at line 48
    [7.4050]
    [7.284]
    // shouldSkipDir checks if a directory should be skipped based on prefixes.
    func shouldSkipDir(name, path, rootPath string, skipPrefixes []string) bool {
    if path == rootPath {
    return false
    }
    for _, prefix := range skipPrefixes {
    if strings.HasPrefix(name, prefix) {
    return true
    }
    }
    return false
    }
  • replacement in utils/fs.go at line 70
    [7.612][7.612:736](),[7.736][7.4464:4469](),[7.4464][7.4464:4469](),[7.4469][7.737:751]()
    if opts.SkipHidden && strings.HasPrefix(name, ".") && path != rootPath {
    if info.IsDir() {
    return filepath.SkipDir
    }
    return nil
    [7.612]
    [7.751]
    if skip, skipErr := shouldSkipHidden(name, path, rootPath, info, opts.SkipHidden); skip {
    return skipErr
  • replacement in utils/fs.go at line 75
    [7.791][7.791:947](),[7.947][7.4635:4641](),[7.4635][7.4635:4641]()
    if info.IsDir() && path != rootPath {
    for _, prefix := range opts.SkipPrefixes {
    if strings.HasPrefix(name, prefix) {
    return filepath.SkipDir
    }
    [7.791]
    [7.4641]
    if info.IsDir() {
    if shouldSkipDir(name, path, rootPath, opts.SkipPrefixes) {
    return filepath.SkipDir
  • replacement in utils/fs.go at line 83
    [7.964][7.964:1061](),[7.1061][7.4867:4872](),[7.4867][7.4867:4872]()
    if !info.IsDir() {
    if fileMatches(name, info, opts) {
    *results = append(*results, path)
    }
    [7.964]
    [7.1062]
    if fileMatches(name, info, opts) {
    *results = append(*results, path)
  • replacement in tools/import/import_unstructured_test.go at line 11
    [5.11380][5.11380:11424]()
    func TestImportUnstructured(t *testing.T) {
    [5.11380]
    [5.11424]
    func TestImportUnstructured_HappyPath(t *testing.T) {
  • edit in tools/import/import_unstructured_test.go at line 13
    [5.11453]
    [5.11453]
    dbPath := setupFileBasedTestDB(t)
  • replacement in tools/import/import_unstructured_test.go at line 15
    [5.11454][5.11454:11557]()
    t.Run("happy path - import single WAV file", func(t *testing.T) {
    dbPath := setupFileBasedTestDB(t)
    [5.11454]
    [5.11557]
    // Create temp folder with a WAV file
    tmpDir := t.TempDir()
    wavPath := filepath.Join(tmpDir, "test_recording.wav")
    hash := createTestWAV(t, wavPath)
  • replacement in tools/import/import_unstructured_test.go at line 20
    [5.11558][5.11558:11715]()
    // Create temp folder with a WAV file
    tmpDir := t.TempDir()
    wavPath := filepath.Join(tmpDir, "test_recording.wav")
    hash := createTestWAV(t, wavPath)
    [5.11558]
    [5.11715]
    // Import to unstructured dataset
    output, err := ImportUnstructured(ctx, ImportUnstructuredInput{
    DBPath: dbPath,
    DatasetID: "dstest000002", // unstructured dataset
    FolderPath: tmpDir,
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("ImportUnstructured failed: %v", err)
    }
  • replacement in tools/import/import_unstructured_test.go at line 31
    [5.11716][5.11716:12022]()
    // Import to unstructured dataset
    output, err := ImportUnstructured(ctx, ImportUnstructuredInput{
    DBPath: dbPath,
    DatasetID: "dstest000002", // unstructured dataset
    FolderPath: tmpDir,
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("ImportUnstructured failed: %v", err)
    }
    [5.11716]
    [5.12022]
    // Verify output
    if output.TotalFiles != 1 {
    t.Errorf("expected 1 total file, got %d", output.TotalFiles)
    }
    if output.ImportedFiles != 1 {
    t.Errorf("expected 1 imported file, got %d", output.ImportedFiles)
    }
    if output.SkippedFiles != 0 {
    t.Errorf("expected 0 skipped files, got %d", output.SkippedFiles)
    }
    if len(output.Errors) != 0 {
    t.Errorf("unexpected errors: %v", output.Errors)
    }
  • replacement in tools/import/import_unstructured_test.go at line 45
    [5.12023][5.12023:12439]()
    // Verify output
    if output.TotalFiles != 1 {
    t.Errorf("expected 1 total file, got %d", output.TotalFiles)
    }
    if output.ImportedFiles != 1 {
    t.Errorf("expected 1 imported file, got %d", output.ImportedFiles)
    }
    if output.SkippedFiles != 0 {
    t.Errorf("expected 0 skipped files, got %d", output.SkippedFiles)
    }
    if len(output.Errors) != 0 {
    t.Errorf("unexpected errors: %v", output.Errors)
    }
    [5.12023]
    [5.12439]
    // Verify file was inserted into database
    database, err := sql.Open("duckdb", dbPath)
    if err != nil {
    t.Fatalf("failed to open database for verification: %v", err)
    }
    defer database.Close()
  • replacement in tools/import/import_unstructured_test.go at line 52
    [5.12440][5.12440:12642]()
    // Verify file was inserted into database
    database, err := sql.Open("duckdb", dbPath)
    if err != nil {
    t.Fatalf("failed to open database for verification: %v", err)
    }
    defer database.Close()
    [5.12440]
    [5.12642]
    var fileCount int
    err = database.QueryRow("SELECT COUNT(*) FROM file WHERE xxh64_hash = ? AND active = true", hash).Scan(&fileCount)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if fileCount != 1 {
    t.Errorf("expected 1 file in database, got %d", fileCount)
    }
  • replacement in tools/import/import_unstructured_test.go at line 61
    [5.12643][5.12643:12935]()
    var fileCount int
    err = database.QueryRow("SELECT COUNT(*) FROM file WHERE xxh64_hash = ? AND active = true", hash).Scan(&fileCount)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if fileCount != 1 {
    t.Errorf("expected 1 file in database, got %d", fileCount)
    }
    [5.12643]
    [5.12935]
    // Verify file_dataset link
    var linkCount int
    err = database.QueryRow(`
    SELECT COUNT(*) FROM file_dataset fd
    JOIN file f ON fd.file_id = f.id
    WHERE f.xxh64_hash = ? AND fd.dataset_id = 'dstest000002'
    `, hash).Scan(&linkCount)
    if err != nil {
    t.Fatalf("failed to query file_dataset: %v", err)
    }
    if linkCount != 1 {
    t.Errorf("expected 1 file_dataset link, got %d", linkCount)
    }
  • replacement in tools/import/import_unstructured_test.go at line 75
    [5.12936][5.12936:13343]()
    // Verify file_dataset link
    var linkCount int
    err = database.QueryRow(`
    SELECT COUNT(*) FROM file_dataset fd
    JOIN file f ON fd.file_id = f.id
    WHERE f.xxh64_hash = ? AND fd.dataset_id = 'dstest000002'
    `, hash).Scan(&linkCount)
    if err != nil {
    t.Fatalf("failed to query file_dataset: %v", err)
    }
    if linkCount != 1 {
    t.Errorf("expected 1 file_dataset link, got %d", linkCount)
    }
    [5.12936]
    [5.13343]
    // Verify location_id and cluster_id are NULL for unstructured
    var locID, clID sql.NullString
    err = database.QueryRow("SELECT location_id, cluster_id FROM file WHERE xxh64_hash = ?", hash).Scan(&locID, &clID)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if locID.Valid {
    t.Errorf("expected NULL location_id for unstructured file, got %s", locID.String)
    }
    if clID.Valid {
    t.Errorf("expected NULL cluster_id for unstructured file, got %s", clID.String)
    }
    }
  • replacement in tools/import/import_unstructured_test.go at line 89
    [5.13344][5.13344:13843]()
    // Verify location_id and cluster_id are NULL for unstructured
    var locID, clID sql.NullString
    err = database.QueryRow("SELECT location_id, cluster_id FROM file WHERE xxh64_hash = ?", hash).Scan(&locID, &clID)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if locID.Valid {
    t.Errorf("expected NULL location_id for unstructured file, got %s", locID.String)
    }
    if clID.Valid {
    t.Errorf("expected NULL cluster_id for unstructured file, got %s", clID.String)
    }
    })
    [5.13344]
    [5.13843]
    func TestImportUnstructured_DuplicateHandling(t *testing.T) {
    ctx := context.Background()
    dbPath := setupFileBasedTestDB(t)
  • replacement in tools/import/import_unstructured_test.go at line 93
    [5.13844][5.13844:13961]()
    t.Run("duplicate handling - skip file with existing hash", func(t *testing.T) {
    dbPath := setupFileBasedTestDB(t)
    [5.13844]
    [5.13961]
    // Create temp folder with a WAV file
    tmpDir := t.TempDir()
    wavPath := filepath.Join(tmpDir, "test_recording.wav")
    hash := createTestWAV(t, wavPath)
  • replacement in tools/import/import_unstructured_test.go at line 98
    [5.13962][5.13962:14119]()
    // Create temp folder with a WAV file
    tmpDir := t.TempDir()
    wavPath := filepath.Join(tmpDir, "test_recording.wav")
    hash := createTestWAV(t, wavPath)
    [5.13962]
    [5.14119]
    // First import
    _, err := ImportUnstructured(ctx, ImportUnstructuredInput{
    DBPath: dbPath,
    DatasetID: "dstest000002",
    FolderPath: tmpDir,
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("first import failed: %v", err)
    }
  • replacement in tools/import/import_unstructured_test.go at line 109
    [5.14120][5.14120:14373]()
    // First import
    _, err := ImportUnstructured(ctx, ImportUnstructuredInput{
    DBPath: dbPath,
    DatasetID: "dstest000002",
    FolderPath: tmpDir,
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("first import failed: %v", err)
    }
    [5.14120]
    [5.14373]
    // Second import of same file (should be skipped as duplicate)
    output, err := ImportUnstructured(ctx, ImportUnstructuredInput{
    DBPath: dbPath,
    DatasetID: "dstest000002",
    FolderPath: tmpDir,
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("second import failed: %v", err)
    }
  • replacement in tools/import/import_unstructured_test.go at line 120
    [5.14374][5.14374:14680]()
    // Second import of same file (should be skipped as duplicate)
    output, err := ImportUnstructured(ctx, ImportUnstructuredInput{
    DBPath: dbPath,
    DatasetID: "dstest000002",
    FolderPath: tmpDir,
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("second import failed: %v", err)
    }
    [5.14374]
    [5.14680]
    // Verify output
    if output.TotalFiles != 1 {
    t.Errorf("expected 1 total file, got %d", output.TotalFiles)
    }
    if output.ImportedFiles != 0 {
    t.Errorf("expected 0 imported files (duplicate), got %d", output.ImportedFiles)
    }
    if output.SkippedFiles != 1 {
    t.Errorf("expected 1 skipped file (duplicate), got %d", output.SkippedFiles)
    }
  • replacement in tools/import/import_unstructured_test.go at line 131
    [5.14681][5.14681:15034]()
    // Verify output
    if output.TotalFiles != 1 {
    t.Errorf("expected 1 total file, got %d", output.TotalFiles)
    }
    if output.ImportedFiles != 0 {
    t.Errorf("expected 0 imported files (duplicate), got %d", output.ImportedFiles)
    }
    if output.SkippedFiles != 1 {
    t.Errorf("expected 1 skipped file (duplicate), got %d", output.SkippedFiles)
    }
    [5.14681]
    [5.15034]
    // Verify only one file in database (not duplicated)
    database, err := sql.Open("duckdb", dbPath)
    if err != nil {
    t.Fatalf("failed to open database for verification: %v", err)
    }
    defer database.Close()
  • replacement in tools/import/import_unstructured_test.go at line 138
    [5.15035][5.15035:15248]()
    // Verify only one file in database (not duplicated)
    database, err := sql.Open("duckdb", dbPath)
    if err != nil {
    t.Fatalf("failed to open database for verification: %v", err)
    }
    defer database.Close()
    [5.15035]
    [5.15248]
    var fileCount int
    err = database.QueryRow("SELECT COUNT(*) FROM file WHERE xxh64_hash = ? AND active = true", hash).Scan(&fileCount)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if fileCount != 1 {
    t.Errorf("expected 1 file in database (not duplicated), got %d", fileCount)
    }
    }
  • replacement in tools/import/import_unstructured_test.go at line 148
    [5.15249][5.15249:15562]()
    var fileCount int
    err = database.QueryRow("SELECT COUNT(*) FROM file WHERE xxh64_hash = ? AND active = true", hash).Scan(&fileCount)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if fileCount != 1 {
    t.Errorf("expected 1 file in database (not duplicated), got %d", fileCount)
    }
    })
    [5.15249]
    [5.15562]
    func TestImportUnstructured_EdgeCases(t *testing.T) {
    ctx := context.Background()
  • replacement in tools/import/import_files_test.go at line 12
    [5.27798][5.27798:27840]()
    func TestImportAudioFiles(t *testing.T) {
    [5.27798]
    [5.27840]
    func TestImportAudioFiles_HappyPath(t *testing.T) {
  • edit in tools/import/import_files_test.go at line 14
    [5.27869]
    [5.27869]
    dbPath := setupFileBasedTestDB(t)
  • replacement in tools/import/import_files_test.go at line 16
    [5.27870][5.27870:28050]()
    t.Run("happy path - import single WAV file", func(t *testing.T) {
    dbPath := setupFileBasedTestDB(t)
    // Don't keep database open - ImportAudioFiles manages its own connections
    [5.27870]
    [5.28050]
    // Create temp folder with a WAV file
    tmpDir := t.TempDir()
    wavPath := filepath.Join(tmpDir, "test_recording.wav")
    hash := createTestWAV(t, wavPath)
  • replacement in tools/import/import_files_test.go at line 21
    [5.28051][5.28051:28208]()
    // Create temp folder with a WAV file
    tmpDir := t.TempDir()
    wavPath := filepath.Join(tmpDir, "test_recording.wav")
    hash := createTestWAV(t, wavPath)
    [5.28051]
    [5.28208]
    // Import
    output, err := ImportAudioFiles(ctx, ImportAudioFilesInput{
    DBPath: dbPath,
    FolderPath: tmpDir,
    DatasetID: "dstest000001",
    LocationID: "loctest00001",
    ClusterID: "cltest000001",
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("ImportAudioFiles failed: %v", err)
    }
  • replacement in tools/import/import_files_test.go at line 34
    [5.28209][5.28209:28523]()
    // Import
    output, err := ImportAudioFiles(ctx, ImportAudioFilesInput{
    DBPath: dbPath,
    FolderPath: tmpDir,
    DatasetID: "dstest000001",
    LocationID: "loctest00001",
    ClusterID: "cltest000001",
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("ImportAudioFiles failed: %v", err)
    }
    [5.28209]
    [5.28523]
    // Verify output
    if output.Summary.TotalFiles != 1 {
    t.Errorf("expected 1 total file, got %d", output.Summary.TotalFiles)
    }
    if output.Summary.ImportedFiles != 1 {
    t.Errorf("expected 1 imported file, got %d", output.Summary.ImportedFiles)
    }
    if output.Summary.SkippedFiles != 0 {
    t.Errorf("expected 0 skipped files, got %d", output.Summary.SkippedFiles)
    }
    if len(output.Errors) != 0 {
    t.Errorf("unexpected errors: %v", output.Errors)
    }
  • replacement in tools/import/import_files_test.go at line 48
    [5.28524][5.28524:28988]()
    // Verify output
    if output.Summary.TotalFiles != 1 {
    t.Errorf("expected 1 total file, got %d", output.Summary.TotalFiles)
    }
    if output.Summary.ImportedFiles != 1 {
    t.Errorf("expected 1 imported file, got %d", output.Summary.ImportedFiles)
    }
    if output.Summary.SkippedFiles != 0 {
    t.Errorf("expected 0 skipped files, got %d", output.Summary.SkippedFiles)
    }
    if len(output.Errors) != 0 {
    t.Errorf("unexpected errors: %v", output.Errors)
    }
    [5.28524]
    [5.28988]
    // Verify file was inserted into database - open new connection
    database, err := sql.Open("duckdb", dbPath)
    if err != nil {
    t.Fatalf("failed to open database for verification: %v", err)
    }
    defer database.Close()
  • replacement in tools/import/import_files_test.go at line 55
    [5.28989][5.28989:29213]()
    // Verify file was inserted into database - open new connection
    database, err := sql.Open("duckdb", dbPath)
    if err != nil {
    t.Fatalf("failed to open database for verification: %v", err)
    }
    defer database.Close()
    [5.28989]
    [5.29213]
    var fileCount int
    err = database.QueryRow("SELECT COUNT(*) FROM file WHERE xxh64_hash = ? AND active = true", hash).Scan(&fileCount)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if fileCount != 1 {
    t.Errorf("expected 1 file in database, got %d", fileCount)
    }
  • replacement in tools/import/import_files_test.go at line 64
    [5.29214][5.29214:29506]()
    var fileCount int
    err = database.QueryRow("SELECT COUNT(*) FROM file WHERE xxh64_hash = ? AND active = true", hash).Scan(&fileCount)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if fileCount != 1 {
    t.Errorf("expected 1 file in database, got %d", fileCount)
    }
    [5.29214]
    [5.29506]
    // Verify file_dataset link
    var linkCount int
    err = database.QueryRow(`
    SELECT COUNT(*) FROM file_dataset fd
    JOIN file f ON fd.file_id = f.id
    WHERE f.xxh64_hash = ? AND fd.dataset_id = 'dstest000001'
    `, hash).Scan(&linkCount)
    if err != nil {
    t.Fatalf("failed to query file_dataset: %v", err)
    }
    if linkCount != 1 {
    t.Errorf("expected 1 file_dataset link, got %d", linkCount)
    }
    }
  • replacement in tools/import/import_files_test.go at line 79
    [5.29507][5.29507:29918]()
    // Verify file_dataset link
    var linkCount int
    err = database.QueryRow(`
    SELECT COUNT(*) FROM file_dataset fd
    JOIN file f ON fd.file_id = f.id
    WHERE f.xxh64_hash = ? AND fd.dataset_id = 'dstest000001'
    `, hash).Scan(&linkCount)
    if err != nil {
    t.Fatalf("failed to query file_dataset: %v", err)
    }
    if linkCount != 1 {
    t.Errorf("expected 1 file_dataset link, got %d", linkCount)
    }
    })
    [5.29507]
    [5.29918]
    func TestImportAudioFiles_DuplicateHandling(t *testing.T) {
    ctx := context.Background()
    dbPath := setupFileBasedTestDB(t)
  • replacement in tools/import/import_files_test.go at line 83
    [5.29919][5.29919:30113]()
    t.Run("duplicate handling - skip file with existing hash", func(t *testing.T) {
    dbPath := setupFileBasedTestDB(t)
    // Don't keep database open - ImportAudioFiles manages its own connections
    [5.29919]
    [5.30113]
    // Create temp folder with a WAV file
    tmpDir := t.TempDir()
    wavPath := filepath.Join(tmpDir, "test_recording.wav")
    hash := createTestWAV(t, wavPath)
  • replacement in tools/import/import_files_test.go at line 88
    [5.30114][5.30114:30271]()
    // Create temp folder with a WAV file
    tmpDir := t.TempDir()
    wavPath := filepath.Join(tmpDir, "test_recording.wav")
    hash := createTestWAV(t, wavPath)
    [5.30114]
    [5.30271]
    // First import
    _, err := ImportAudioFiles(ctx, ImportAudioFilesInput{
    DBPath: dbPath,
    FolderPath: tmpDir,
    DatasetID: "dstest000001",
    LocationID: "loctest00001",
    ClusterID: "cltest000001",
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("first import failed: %v", err)
    }
  • replacement in tools/import/import_files_test.go at line 101
    [5.30272][5.30272:30583]()
    // First import
    _, err := ImportAudioFiles(ctx, ImportAudioFilesInput{
    DBPath: dbPath,
    FolderPath: tmpDir,
    DatasetID: "dstest000001",
    LocationID: "loctest00001",
    ClusterID: "cltest000001",
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("first import failed: %v", err)
    }
    [5.30272]
    [5.30583]
    // Second import of same file (should be skipped as duplicate)
    output, err := ImportAudioFiles(ctx, ImportAudioFilesInput{
    DBPath: dbPath,
    FolderPath: tmpDir,
    DatasetID: "dstest000001",
    LocationID: "loctest00001",
    ClusterID: "cltest000001",
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("second import failed: %v", err)
    }
  • replacement in tools/import/import_files_test.go at line 114
    [5.30584][5.30584:30948]()
    // Second import of same file (should be skipped as duplicate)
    output, err := ImportAudioFiles(ctx, ImportAudioFilesInput{
    DBPath: dbPath,
    FolderPath: tmpDir,
    DatasetID: "dstest000001",
    LocationID: "loctest00001",
    ClusterID: "cltest000001",
    Recursive: new(true),
    })
    if err != nil {
    t.Fatalf("second import failed: %v", err)
    }
    [5.30584]
    [5.30948]
    // Verify output
    if output.Summary.TotalFiles != 1 {
    t.Errorf("expected 1 total file, got %d", output.Summary.TotalFiles)
    }
    if output.Summary.ImportedFiles != 0 {
    t.Errorf("expected 0 imported files (duplicate), got %d", output.Summary.ImportedFiles)
    }
    if output.Summary.SkippedFiles != 1 {
    t.Errorf("expected 1 skipped file (duplicate), got %d", output.Summary.SkippedFiles)
    }
  • replacement in tools/import/import_files_test.go at line 125
    [5.30949][5.30949:31350]()
    // Verify output
    if output.Summary.TotalFiles != 1 {
    t.Errorf("expected 1 total file, got %d", output.Summary.TotalFiles)
    }
    if output.Summary.ImportedFiles != 0 {
    t.Errorf("expected 0 imported files (duplicate), got %d", output.Summary.ImportedFiles)
    }
    if output.Summary.SkippedFiles != 1 {
    t.Errorf("expected 1 skipped file (duplicate), got %d", output.Summary.SkippedFiles)
    }
    [5.30949]
    [5.31350]
    // Verify only one file in database (not duplicated) - open new connection
    database, err := sql.Open("duckdb", dbPath)
    if err != nil {
    t.Fatalf("failed to open database for verification: %v", err)
    }
    defer database.Close()
  • replacement in tools/import/import_files_test.go at line 132
    [5.31351][5.31351:31586]()
    // Verify only one file in database (not duplicated) - open new connection
    database, err := sql.Open("duckdb", dbPath)
    if err != nil {
    t.Fatalf("failed to open database for verification: %v", err)
    }
    defer database.Close()
    [5.31351]
    [5.31586]
    var fileCount int
    err = database.QueryRow("SELECT COUNT(*) FROM file WHERE xxh64_hash = ? AND active = true", hash).Scan(&fileCount)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if fileCount != 1 {
    t.Errorf("expected 1 file in database (not duplicated), got %d", fileCount)
    }
    }
  • replacement in tools/import/import_files_test.go at line 142
    [5.31587][5.31587:31900]()
    var fileCount int
    err = database.QueryRow("SELECT COUNT(*) FROM file WHERE xxh64_hash = ? AND active = true", hash).Scan(&fileCount)
    if err != nil {
    t.Fatalf("failed to query file: %v", err)
    }
    if fileCount != 1 {
    t.Errorf("expected 1 file in database (not duplicated), got %d", fileCount)
    }
    })
    [5.31587]
    [5.31900]
    func TestImportAudioFiles_ErrorCases(t *testing.T) {
    ctx := context.Background()
  • replacement in lint_test.go at line 48
    [7.158][6.146:224]()
    cmd := exec.Command("gocyclo", "-over", "12", "-ignore", "_test\\.go$", ".")
    [7.158]
    [7.210]
    cmd := exec.Command("gocyclo", "-over", "11", "-ignore", "_test\\.go$", ".")
  • replacement in lint_test.go at line 52
    [7.276][7.15937:15996]()
    t.Errorf("cyclometric complexity is above 12:\n%s", out)
    [7.276]
    [7.335]
    t.Errorf("cyclometric complexity is above 11:\n%s", out)
  • replacement in db/schema.go at line 227
    [7.1005872][7.9108:9322]()
    // topologicalSort orders tables so dependencies come first (Kahn's algorithm).
    // Tables in cycles are appended at the end.
    func topologicalSort(tables map[string]bool, dependsOnMe map[string][]string) []string {
    [7.1005872]
    [7.9322]
    // countDependencies counts FK dependencies for each table.
    func countDependencies(tables map[string]bool, dependsOnMe map[string][]string) map[string]int {
  • edit in db/schema.go at line 238
    [7.1006133]
    [7.1006133]
    return fkCount
    }
  • edit in db/schema.go at line 241
    [7.1006134]
    [7.1006230]
    // buildInitialQueue builds the initial queue of tables with no dependencies.
    func buildInitialQueue(tables map[string]bool, fkCount map[string]int) []string {
  • edit in db/schema.go at line 247
    [7.1006338]
    [7.1006338]
    }
    }
    return queue
    }
    // appendCyclicTables adds tables that are part of cycles to the result.
    func appendCyclicTables(result []string, tables map[string]bool) []string {
    if len(result) == len(tables) {
    return result
    }
    for table := range tables {
    if !slices.Contains(result, table) {
    result = append(result, table)
  • edit in db/schema.go at line 262
    [7.1006345]
    [7.1006345]
    return result
    }
    // topologicalSort orders tables so dependencies come first (Kahn's algorithm).
    // Tables in cycles are appended at the end.
    func topologicalSort(tables map[string]bool, dependsOnMe map[string][]string) []string {
    fkCount := countDependencies(tables, dependsOnMe)
    queue := buildInitialQueue(tables, fkCount)
  • replacement in db/schema.go at line 285
    [7.1006735][7.9369:9412](),[7.9412][7.1006793:1006826](),[7.1006793][7.1006793:1006826](),[7.1006869][7.1006869:1006899](),[7.1006899][7.9413:9453](),[7.9453][7.1006957:1007004](),[7.1006957][7.1006957:1007004](),[7.1007004][7.9454:9469]()
    // Handle cycles: append remaining tables
    if len(result) != len(tables) {
    for table := range tables {
    if !slices.Contains(result, table) {
    result = append(result, table)
    }
    }
    }
    return result
    [7.1006735]
    [7.1007025]
    return appendCyclicTables(result, tables)
  • replacement in cmd/calls_push_certainty.go at line 52
    [7.1106231][7.1106231:1106265](),[7.1106265][7.3493:3539](),[7.3539][7.1106408:1106425](),[7.24861][7.1106408:1106425](),[7.1106408][7.1106408:1106425](),[7.1106425][7.3540:3582](),[7.3582][7.1106564:1106583](),[7.24902][7.1106564:1106583](),[7.1106564][7.1106564:1106583](),[7.1106583][7.3583:3629](),[7.3629][7.1106726:1106746](),[7.24945][7.1106726:1106746](),[7.1106726][7.1106726:1106746](),[7.1106746][7.3630:3678](),[7.3678][7.1106891:1106909](),[7.24989][7.1106891:1106909](),[7.1106891][7.1106891:1106909](),[7.1106909][7.24990:25008](),[7.25008][7.1106925:1106932](),[7.1106925][7.1106925:1106932](),[7.1106933][7.1106933:1106949](),[7.1106949][7.25009:25025](),[7.25025][7.1106963:1106970](),[7.1106963][7.1106963:1106970](),[7.1106970][7.1037:1180](),[7.1180][7.3679:3729](),[7.1224][7.1107759:1107825](),[7.3729][7.1107759:1107825](),[7.25188][7.1107759:1107825](),[7.1107759][7.1107759:1107825](),[7.1107826][7.1107826:1107837]()
    switch arg {
    case "--folder":
    f.folder = mustValue(args, &i, "--folder")
    case "--file":
    f.file = mustValue(args, &i, "--file")
    case "--filter":
    f.filter = mustValue(args, &i, "--filter")
    case "--species":
    f.species = mustValue(args, &i, "--species")
    case "--night":
    f.night = true
    i++
    case "--day":
    f.day = true
    i++
    case "--location":
    if f.location != "" {
    fmt.Fprintf(os.Stderr, "Error: --location can only be specified once\n")
    os.Exit(1)
    }
    f.location = mustValue(args, &i, "--location")
    case "--help", "-h":
    printPushCertaintyUsage()
    os.Exit(0)
    default:
    [7.1106231]
    [7.1107899]
    if !parsePushCertaintyFlag(arg, args, &i, &f) {
  • edit in cmd/calls_push_certainty.go at line 59
    [7.25199]
    [7.25483]
    }
    // parsePushCertaintyFlag handles a single flag. Returns false if unknown flag.
    func parsePushCertaintyFlag(arg string, args []string, i *int, f *pushCertaintyFlags) bool {
    switch arg {
    case "--folder":
    f.folder = mustValue(args, i, "--folder")
    case "--file":
    f.file = mustValue(args, i, "--file")
    case "--filter":
    f.filter = mustValue(args, i, "--filter")
    case "--species":
    f.species = mustValue(args, i, "--species")
    case "--night":
    f.night = true
    *i++
    case "--day":
    f.day = true
    *i++
    case "--location":
    if f.location != "" {
    fmt.Fprintf(os.Stderr, "Error: --location can only be specified once\n")
    os.Exit(1)
    }
    f.location = mustValue(args, i, "--location")
    case "--help", "-h":
    printPushCertaintyUsage()
    os.Exit(0)
    default:
    return false
    }
    return true
  • edit in CHANGELOG.md at line 4
    [7.1198010]
    [3.218]
    ## [2026-05-19] tools/calls and tools/import refactoring complete — all 5 phases done
    Refactored `tools/calls/` and `tools/import/` to improve code quality, test coverage, and interface consistency.
    ### Phase 0: Tests for tools/import Critical Paths
    - Created `tools/import/test_helpers.go` with shared test utilities
    - Added tests for `ImportAudioFiles`, `ImportUnstructured`, `ImportSegments`, `writeIDsToDataFiles`, `BulkFileImport`
    - **Coverage increased from 20.7% to 71.9%**
    ### Phase 1: Reduce Cyclomatic Complexity
    - Extracted `resolveLabelIDs`, `insertLabel`, `insertLabelMetadata` from `importSingleLabel`
    - Extracted `detectBirdaColumns`, `validateBirdaColumns` from `parseBirdaCSVHeader`
    - Extracted `validateAndParseShowImagesInput`, `resolveImageSize`, `resolveGraphicsProtocol`, `displaySegmentSpectrograms` from `CallsShowImages`
    - Fixed `scanWavFiles`, `CallsRemove`, `findMatchingLabels` complexity issues
    - **All functions now under complexity 10**
    ### Phase 2: Split import_segments.go
    - Created `import_segments_prepare.go` (429 lines) — validation, scanning, ID lookup
    - Created `import_segments_db.go` (422 lines) — DB write, writeback
    - Reduced `import_segments.go` to 155 lines — orchestrator + types
    ### Phase 3: Fix *sql.DB Usage
    - **3A (tools/):** Changed `validateClusterActive`, `validateClusterCyclicPattern`, `executeSQLQuery` to use `db.Querier`
    - **3A (tools/export.go):** Defined local `writer` interface; replaced `sql.Open` with `db.OpenWriteableDB`
    - **3A (db/schema.go):** Changed `GetFKOrder`, `buildFKDependencyGraph`, `collectAllTables` to `db.Querier`
    - **3B (tools/import/):** Changed all bulk functions to accept `Mutator` interface
    - Updated `ImportSegments`, `ImportFile` to use `WithReadDB`/`WithWriteTx` patterns
    - Raw `*sql.DB` now only appears in callback signatures and type assertions
    ### Phase 4: Move Classify TUI-Adjacent Types to Dedicated Files
    - Created `classify_bindings.go` (123 lines) — `KeyBinding`, `BindingResult`, binding methods
    - Created `classify_format.go` (32 lines) — `FormatLabels`
    - Created `classify_io.go` (106 lines) — `LoadFilteredSegment`, `SaveClip`, `PlaySegmentAtSpeed`
    - Created `classify_bookmarks.go` (135 lines) — bookmark navigation methods
    - Created `classify_labels.go` (87 lines) — label manipulation methods
    - Added documentation for TUI-only fields in `ClassifyConfig`
    - **`calls_classify.go` reduced from 802 to 375 lines**
    ### Phase 5: Consolidate Shared Types into from_common.go
    - Moved `ClusteredCall`, `DirCache`, `ParseFilterFromFilename`, `clusterStartTimes`, `clusterDetections`, `predFileSpeciesKey` to `calls_from_common.go`
    - **`calls_from_preds.go` reduced from 717 to 546 lines (24% reduction)**
    - **`calls_from_common.go` grew from 287 to 460 lines**
  • edit in CHANGELOG.md at line 48
    [3.219]
    [3.219]
    ### Summary
    | Metric | Before | After |
    |--------|--------|-------|
    | tools/import test coverage | 20.7% | 71.9% |
    | Max cyclomatic complexity | 12+ | <10 |
    | import_segments.go lines | 944 | 155 |
    | calls_classify.go lines | 802 | 375 |
    | calls_from_preds.go lines | 717 | 546 |
  • replacement in .golangci.yml at line 14
    [7.22132][2.200:225]()
    max-complexity: 12
    [7.22132]
    [7.22157]
    max-complexity: 11