cyclo 22
Dependencies
- [2]
LBWQJEDHminor refactor and more tests for utils/ - [3]
GPQSOVBPcyclo complexity over 25 - [4]
KZKLAINJrun out of space on nest, cleaned out
Change contents
- edit in utils/fs.go at line 22
extTarget := strings.ToLower(opts.Extension) - replacement in utils/fs.go at line 24
err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {if err != nil {return err}if err := filepath.Walk(rootPath, makeWalkFunc(&results, opts, rootPath)); err != nil {return nil, err}} else {if err := scanFlatDir(rootPath, &results, opts); err != nil {return nil, err}} - replacement in utils/fs.go at line 33
name := info.Name()sort.Strings(results)return results, nil} - replacement in utils/fs.go at line 37
// Skip hidden files/directoriesif opts.SkipHidden && strings.HasPrefix(name, ".") && path != rootPath {if info.IsDir() {return filepath.SkipDir}return nil}// makeWalkFunc returns a filepath.WalkFunc that collects matching files.func makeWalkFunc(results *[]string, opts FindFilesOptions, rootPath string) filepath.WalkFunc {return func(path string, info os.FileInfo, err error) error {if err != nil {return err}name := info.Name() - replacement in utils/fs.go at line 45
// Check directory skip prefixesif info.IsDir() && path != rootPath {for _, prefix := range opts.SkipPrefixes {if strings.HasPrefix(name, prefix) {return filepath.SkipDir}}return nil// Skip hidden files/directoriesif opts.SkipHidden && strings.HasPrefix(name, ".") && path != rootPath {if info.IsDir() {return filepath.SkipDir - edit in utils/fs.go at line 50
return nil} - replacement in utils/fs.go at line 53
// Check fileif !info.IsDir() {if strings.ToLower(filepath.Ext(name)) == extTarget && info.Size() >= opts.MinSize {results = append(results, path)// Check directory skip prefixesif info.IsDir() && path != rootPath {for _, prefix := range opts.SkipPrefixes {if strings.HasPrefix(name, prefix) {return filepath.SkipDir - edit in utils/fs.go at line 60
- edit in utils/fs.go at line 61
})if err != nil {return nil, err}} else {entries, err := os.ReadDir(rootPath)if err != nil {return nil, err - replacement in utils/fs.go at line 63
for _, entry := range entries {if entry.IsDir() {continue// Check fileif !info.IsDir() {if fileMatches(name, info, opts) {*results = append(*results, path) - edit in utils/fs.go at line 68
} - replacement in utils/fs.go at line 70
name := entry.Name()if opts.SkipHidden && strings.HasPrefix(name, ".") {continue}return nil}} - replacement in utils/fs.go at line 74
if strings.ToLower(filepath.Ext(name)) == extTarget {path := filepath.Join(rootPath, name)if info, err := os.Stat(path); err == nil && info.Size() >= opts.MinSize {results = append(results, path)}}}// fileMatches checks if a file name meets the extension and size criteria.func fileMatches(name string, info os.FileInfo, opts FindFilesOptions) bool {if !strings.EqualFold(filepath.Ext(name), opts.Extension) {return false}if info.Size() < opts.MinSize {return false - edit in utils/fs.go at line 82
return true} - replacement in utils/fs.go at line 85
sort.Strings(results)return results, nil// scanFlatDir scans a single directory level for matching files.func scanFlatDir(rootPath string, results *[]string, opts FindFilesOptions) error {entries, err := os.ReadDir(rootPath)if err != nil {return err}for _, entry := range entries {if entry.IsDir() {continue}name := entry.Name()if opts.SkipHidden && strings.HasPrefix(name, ".") {continue}if !strings.EqualFold(filepath.Ext(name), opts.Extension) {continue}path := filepath.Join(rootPath, name)if info, err := os.Stat(path); err == nil && info.Size() >= opts.MinSize {*results = append(*results, path)}}return nil - replacement in tools/sql.go at line 69
// Validate query is not emptyif strings.TrimSpace(input.Query) == "" {return ExecuteSQLOutput{}, fmt.Errorf("query cannot be empty")if err := validateSQLQuery(input.Query, input.Limit); err != nil {return ExecuteSQLOutput{}, err - replacement in tools/sql.go at line 73
// Validate query starts with SELECT or WITHif !selectPattern.MatchString(input.Query) {return ExecuteSQLOutput{}, fmt.Errorf("only SELECT and WITH queries are allowed")}limit := resolveLimit(input.Limit)query, autoAddedLimit := applyLimit(input.Query, limit) - replacement in tools/sql.go at line 76
// Check for forbidden keywords (defense in depth - database is already read-only)if forbiddenPattern.MatchString(input.Query) {return ExecuteSQLOutput{}, fmt.Errorf("query contains forbidden keywords (INSERT/UPDATE/DELETE/DROP/CREATE/ALTER)")database, err := db.OpenReadOnlyDB(dbPath)if err != nil {return ExecuteSQLOutput{}, fmt.Errorf("database connection failed: %w", err) - edit in tools/sql.go at line 80
defer database.Close() - replacement in tools/sql.go at line 82
// Determine row limitlimit := defaultLimitif input.Limit != nil {if *input.Limit < 1 || *input.Limit > maxLimit {return ExecuteSQLOutput{}, fmt.Errorf("limit must be between 1 and %d", maxLimit)}limit = *input.Limitrows, err := executeSQLQuery(ctx, database, query, input.Parameters)if err != nil {return ExecuteSQLOutput{}, err - edit in tools/sql.go at line 86
defer rows.Close() - replacement in tools/sql.go at line 88
// Add LIMIT clause if not present// Query for limit+1 rows to detect truncationquery := input.QueryautoAddedLimit := falseif !limitPattern.MatchString(query) {query = fmt.Sprintf("%s LIMIT %d", strings.TrimSpace(query), limit+1)autoAddedLimit = truecolumnInfo, columns, err := buildColumnInfo(rows)if err != nil {return ExecuteSQLOutput{}, err - replacement in tools/sql.go at line 93
// Get database connection (read-only for security)database, err := db.OpenReadOnlyDB(dbPath)results, err := scanResultRows(rows, columns) - replacement in tools/sql.go at line 95
return ExecuteSQLOutput{}, fmt.Errorf("database connection failed: %w", err)return ExecuteSQLOutput{}, err - edit in tools/sql.go at line 97
defer database.Close() // Always close when done - replacement in tools/sql.go at line 98
// Execute query with parametersvar rows *sql.Rowsif len(input.Parameters) > 0 {rows, err = database.QueryContext(ctx, query, input.Parameters...)} else {rows, err = database.QueryContext(ctx, query)// Handle empty results (return empty array, not error)if results == nil {results = []map[string]any{} - replacement in tools/sql.go at line 102
if err != nil {return ExecuteSQLOutput{}, fmt.Errorf("query execution failed: %w", err)// Detect truncation: if we auto-added limit+1 and got more than limit rowslimited := falseif autoAddedLimit && len(results) > limit {limited = trueresults = results[:limit] - replacement in tools/sql.go at line 109
defer rows.Close()queryReported := buildQueryReported(input.Query, autoAddedLimit, limit)return ExecuteSQLOutput{Rows: results,RowCount: len(results),Columns: columnInfo,Limited: limited,Query: queryReported,}, nil} - replacement in tools/sql.go at line 121
// Get column metadata// validateSQLQuery checks the query is a safe SELECT/WITH statement.func validateSQLQuery(query string, limit *int) error {if strings.TrimSpace(query) == "" {return fmt.Errorf("query cannot be empty")}if !selectPattern.MatchString(query) {return fmt.Errorf("only SELECT and WITH queries are allowed")}if forbiddenPattern.MatchString(query) {return fmt.Errorf("query contains forbidden keywords (INSERT/UPDATE/DELETE/DROP/CREATE/ALTER)")}if limit != nil {if *limit < 1 || *limit > maxLimit {return fmt.Errorf("limit must be between 1 and %d", maxLimit)}}return nil}// resolveLimit returns the effective row limit from input or default.func resolveLimit(limit *int) int {if limit != nil {return *limit}return defaultLimit}// applyLimit appends a LIMIT clause if not already present.// Returns the modified query and whether a limit was auto-added.func applyLimit(query string, limit int) (string, bool) {if !limitPattern.MatchString(query) {return fmt.Sprintf("%s LIMIT %d", strings.TrimSpace(query), limit+1), true}return query, false}// executeSQLQuery runs the query and returns the result rows.func executeSQLQuery(ctx context.Context, database *sql.DB, query string, params []any) (*sql.Rows, error) {if len(params) > 0 {return database.QueryContext(ctx, query, params...)}return database.QueryContext(ctx, query)}// buildColumnInfo extracts column metadata from the result set.func buildColumnInfo(rows *sql.Rows) ([]ColumnInfo, []string, error) { - replacement in tools/sql.go at line 169
return ExecuteSQLOutput{}, fmt.Errorf("failed to get columns: %w", err)return nil, nil, fmt.Errorf("failed to get columns: %w", err) - edit in tools/sql.go at line 171
- replacement in tools/sql.go at line 173
return ExecuteSQLOutput{}, fmt.Errorf("failed to get column types: %w", err)return nil, nil, fmt.Errorf("failed to get column types: %w", err) - edit in tools/sql.go at line 175
// Build column info - edit in tools/sql.go at line 182
return columnInfo, columns, nil} - replacement in tools/sql.go at line 185
// Process rows// scanResultRows scans all rows from the result set into maps.func scanResultRows(rows *sql.Rows, columns []string) ([]map[string]any, error) { - edit in tools/sql.go at line 188
- edit in tools/sql.go at line 189
// Create slice to hold column values - edit in tools/sql.go at line 194
// Scan row - replacement in tools/sql.go at line 195
return ExecuteSQLOutput{}, fmt.Errorf("row scan failed: %w", err)return nil, fmt.Errorf("row scan failed: %w", err) - edit in tools/sql.go at line 197
// Convert to map with type conversion - edit in tools/sql.go at line 201
- replacement in tools/sql.go at line 203
// Check for errors during iterationif err = rows.Err(); err != nil {return ExecuteSQLOutput{}, fmt.Errorf("row iteration failed: %w", err)if err := rows.Err(); err != nil {return nil, fmt.Errorf("row iteration failed: %w", err) - edit in tools/sql.go at line 206
return results, nil} - replacement in tools/sql.go at line 209
// Handle empty results (return empty array, not error)if results == nil {results = []map[string]any{}}// Detect truncation: if we auto-added limit+1 and got more than limit rowslimited := falseif autoAddedLimit && len(results) > limit {limited = trueresults = results[:limit]}// Build the query string to report (show effective limit, not internal limit+1)queryReported := query// buildQueryReported constructs the query string to report in output.func buildQueryReported(originalQuery string, autoAddedLimit bool, limit int) string { - replacement in tools/sql.go at line 212
queryReported = fmt.Sprintf("%s LIMIT %d", strings.TrimSpace(input.Query), limit)}// Create output structureoutput := ExecuteSQLOutput{Rows: results,RowCount: len(results),Columns: columnInfo,Limited: limited,Query: queryReported,return fmt.Sprintf("%s LIMIT %d", strings.TrimSpace(originalQuery), limit) - replacement in tools/sql.go at line 214
return output, nilreturn originalQuery - edit in tools/location.go at line 5
"database/sql" - edit in tools/location.go at line 68
func createLocation(ctx context.Context, input LocationInput) (LocationOutput, error) {var output LocationOutput - replacement in tools/location.go at line 69
// Validate required fields for create// validateCreateFields validates required fields for creating a location.func validateCreateFields(input LocationInput) error { - replacement in tools/location.go at line 72
return output, fmt.Errorf("dataset_id is required when creating a location")return fmt.Errorf("dataset_id is required when creating a location") - replacement in tools/location.go at line 75
return output, fmt.Errorf("name is required when creating a location")return fmt.Errorf("name is required when creating a location") - replacement in tools/location.go at line 78
return output, fmt.Errorf("latitude is required when creating a location")return fmt.Errorf("latitude is required when creating a location") - replacement in tools/location.go at line 81
return output, fmt.Errorf("longitude is required when creating a location")return fmt.Errorf("longitude is required when creating a location") - replacement in tools/location.go at line 84
return output, fmt.Errorf("timezone_id is required when creating a location")return fmt.Errorf("timezone_id is required when creating a location") - edit in tools/location.go at line 86
// Validate ID format for dataset_id - replacement in tools/location.go at line 87
return output, errreturn err - edit in tools/location.go at line 89
return validateLocationFields(input)} - replacement in tools/location.go at line 92
if err := validateLocationFields(input); err != nil {// fetchLocationByID scans a full location row from a query that selects// id, dataset_id, name, latitude, longitude, description, created_at, last_modified, active, timezone_id.func fetchLocationByID(ctx context.Context, queryer interface {QueryRowContext(context.Context, string, ...any) *sql.Row}, id string) (db.Location, error) {const selectCols = "SELECT id, dataset_id, name, latitude, longitude, description, created_at, last_modified, active, timezone_id FROM location WHERE id = ?"var loc db.Locationerr := queryer.QueryRowContext(ctx, selectCols, id).Scan(&loc.ID, &loc.DatasetID, &loc.Name, &loc.Latitude, &loc.Longitude,&loc.Description, &loc.CreatedAt, &loc.LastModified, &loc.Active, &loc.TimezoneID)return loc, err}func createLocation(ctx context.Context, input LocationInput) (LocationOutput, error) {var output LocationOutputif err := validateCreateFields(input); err != nil { - replacement in tools/location.go at line 130
// Verify dataset exists and is activevar datasetExists, datasetActive boolerr = tx.QueryRowContext(ctx,"SELECT EXISTS(SELECT 1 FROM dataset WHERE id = ?), COALESCE((SELECT active FROM dataset WHERE id = ?), false)",*input.DatasetID, *input.DatasetID,).Scan(&datasetExists, &datasetActive)if err != nil {return output, fmt.Errorf("failed to verify dataset: %w", err)}if !datasetExists {return output, fmt.Errorf("dataset with ID '%s' does not exist", *input.DatasetID)if err := verifyDatasetExistsAndActive(ctx, tx, *input.DatasetID); err != nil {return output, err - edit in tools/location.go at line 133
if !datasetActive {return output, fmt.Errorf("dataset (ID: %s) is not active", *input.DatasetID)} - replacement in tools/location.go at line 142
// Location with this name already exists in dataset - return existing (consistent duplicate handling)var location db.Locationerr = tx.QueryRowContext(ctx,"SELECT id, dataset_id, name, latitude, longitude, description, created_at, last_modified, active, timezone_id FROM location WHERE id = ?",existingID,).Scan(&location.ID, &location.DatasetID, &location.Name, &location.Latitude, &location.Longitude,&location.Description, &location.CreatedAt, &location.LastModified, &location.Active, &location.TimezoneID)if err != nil {return output, fmt.Errorf("failed to fetch existing location: %w", err)}if err = tx.Commit(); err != nil {return output, fmt.Errorf("failed to commit transaction: %w", err)}output.Location = locationoutput.Message = fmt.Sprintf("Location '%s' already exists in dataset (ID: %s) - returning existing location", location.Name, location.ID)return output, nilreturn returnExistingLocation(ctx, tx, existingID, output) - replacement in tools/location.go at line 161
var location db.Locationerr = tx.QueryRowContext(ctx,"SELECT id, dataset_id, name, latitude, longitude, description, created_at, last_modified, active, timezone_id FROM location WHERE id = ?",id,).Scan(&location.ID, &location.DatasetID, &location.Name, &location.Latitude, &location.Longitude,&location.Description, &location.CreatedAt, &location.LastModified, &location.Active, &location.TimezoneID)location, err := fetchLocationByID(ctx, tx, id) - edit in tools/location.go at line 173
return output, nil} - edit in tools/location.go at line 176
// returnExistingLocation handles the case where a location already exists in the dataset.func returnExistingLocation(ctx context.Context, tx *db.LoggedTx, existingID string, output LocationOutput) (LocationOutput, error) {location, err := fetchLocationByID(ctx, tx, existingID)if err != nil {return output, fmt.Errorf("failed to fetch existing location: %w", err)}if err = tx.Commit(); err != nil {return output, fmt.Errorf("failed to commit transaction: %w", err)}output.Location = locationoutput.Message = fmt.Sprintf("Location '%s' already exists in dataset (ID: %s) - returning existing location", location.Name, location.ID) - edit in tools/location.go at line 190
// verifyDatasetExistsAndActive checks that a dataset exists and is active.func verifyDatasetExistsAndActive(ctx context.Context, queryer interface {QueryRowContext(context.Context, string, ...any) *sql.Row}, datasetID string) error {var exists, active boolerr := queryer.QueryRowContext(ctx,"SELECT EXISTS(SELECT 1 FROM dataset WHERE id = ?), COALESCE((SELECT active FROM dataset WHERE id = ?), false)",datasetID, datasetID,).Scan(&exists, &active)if err != nil {return fmt.Errorf("failed to verify dataset: %w", err)}if !exists {return fmt.Errorf("dataset with ID '%s' does not exist", datasetID)}if !active {return fmt.Errorf("dataset (ID: %s) is not active", datasetID)}return nil} - replacement in tools/location.go at line 236
// Verify location exists and check active statusvar exists, active boolvar currentDatasetID stringerr = database.QueryRow("SELECT EXISTS(SELECT 1 FROM location WHERE id = ?), COALESCE((SELECT active FROM location WHERE id = ?), false), COALESCE((SELECT dataset_id FROM location WHERE id = ?), '')",locationID, locationID, locationID,).Scan(&exists, &active, ¤tDatasetID)if err != nil {return output, fmt.Errorf("failed to query location: %w", err)}if !exists {return output, fmt.Errorf("location not found: %s", locationID)}if !active {return output, fmt.Errorf("location '%s' is not active (cannot update inactive locations)", locationID)if err := verifyLocationExistsAndActive(database, locationID); err != nil {return output, err - replacement in tools/location.go at line 242
var datasetExists, datasetActive boolerr = database.QueryRow("SELECT EXISTS(SELECT 1 FROM dataset WHERE id = ?), COALESCE((SELECT active FROM dataset WHERE id = ?), false)",*input.DatasetID, *input.DatasetID,).Scan(&datasetExists, &datasetActive)if err := verifyDatasetExistsAndActive(context.Background(), database, *input.DatasetID); err != nil {return output, err}}updates, args, err := buildLocationUpdates(input, locationID)if err != nil {return output, err}query := fmt.Sprintf("UPDATE location SET %s WHERE id = ?", strings.Join(updates, ", "))// Begin logged transaction for updatetx, err := db.BeginLoggedTx(ctx, database, "create_or_update_location")if err != nil {return output, fmt.Errorf("failed to begin transaction: %w", err)}defer func() { - replacement in tools/location.go at line 260
return output, fmt.Errorf("failed to query dataset: %w", err)tx.Rollback() - replacement in tools/location.go at line 262
if !datasetExists {return output, fmt.Errorf("dataset not found: %s", *input.DatasetID)}if !datasetActive {return output, fmt.Errorf("dataset '%s' is not active", *input.DatasetID)}}()_, err = tx.ExecContext(ctx, query, args...)if err != nil {return output, fmt.Errorf("failed to update location: %w", err)}// Fetch the updated locationlocation, err := fetchLocationByID(ctx, tx, locationID)if err != nil {return output, fmt.Errorf("failed to fetch updated location: %w", err)}if err = tx.Commit(); err != nil {return output, fmt.Errorf("failed to commit transaction: %w", err)}output.Location = locationoutput.Message = fmt.Sprintf("Successfully updated location '%s' (ID: %s)", location.Name, location.ID)return output, nil}// verifyLocationExistsAndActive checks that a location exists and is active.func verifyLocationExistsAndActive(queryer interface {QueryRow(string, ...any) *sql.Row}, locationID string) error {var exists, active boolerr := queryer.QueryRow("SELECT EXISTS(SELECT 1 FROM location WHERE id = ?), COALESCE((SELECT active FROM location WHERE id = ?), false)",locationID, locationID,).Scan(&exists, &active)if err != nil {return fmt.Errorf("failed to query location: %w", err)}if !exists {return fmt.Errorf("location not found: %s", locationID)}if !active {return fmt.Errorf("location '%s' is not active (cannot update inactive locations)", locationID) - edit in tools/location.go at line 302
return nil} - replacement in tools/location.go at line 305
// Build dynamic UPDATE query// buildLocationUpdates builds the dynamic SET clauses and args for an UPDATE.func buildLocationUpdates(input LocationInput, locationID string) ([]string, []any, error) { - replacement in tools/location.go at line 336
return output, fmt.Errorf("no fields provided to update")return nil, nil, fmt.Errorf("no fields provided to update") - edit in tools/location.go at line 339
// Always update last_modified - replacement in tools/location.go at line 341
query := fmt.Sprintf("UPDATE location SET %s WHERE id = ?", strings.Join(updates, ", "))// Begin logged transaction for updatetx, err := db.BeginLoggedTx(ctx, database, "create_or_update_location")if err != nil {return output, fmt.Errorf("failed to begin transaction: %w", err)}defer func() {if err != nil {tx.Rollback()}}()_, err = tx.ExecContext(ctx, query, args...)if err != nil {return output, fmt.Errorf("failed to update location: %w", err)}// Fetch the updated locationvar location db.Locationerr = tx.QueryRow("SELECT id, dataset_id, name, latitude, longitude, description, created_at, last_modified, active, timezone_id FROM location WHERE id = ?",locationID,).Scan(&location.ID, &location.DatasetID, &location.Name, &location.Latitude, &location.Longitude,&location.Description, &location.CreatedAt, &location.LastModified, &location.Active, &location.TimezoneID)if err != nil {return output, fmt.Errorf("failed to fetch updated location: %w", err)}if err = tx.Commit(); err != nil {return output, fmt.Errorf("failed to commit transaction: %w", err)}output.Location = locationoutput.Message = fmt.Sprintf("Successfully updated location '%s' (ID: %s)", location.Name, location.ID)return output, nilreturn updates, args, nil - replacement in tools/export.go at line 89
// Verify dataset exists and get name/typevar datasetName, datasetType stringerr = sourceDB.QueryRowContext(ctx,"SELECT name, type FROM dataset WHERE id = ? AND active = true",input.DatasetID,).Scan(&datasetName, &datasetType)datasetName, err := verifyExportDataset(ctx, sourceDB, input) - replacement in tools/export.go at line 92
return output, fmt.Errorf("dataset not found: %s", input.DatasetID)return output, err - replacement in tools/export.go at line 96
// Only structured datasets can be exportedif datasetType != "structured" {if err := checkOutputFile(input); err != nil { - replacement in tools/export.go at line 98
return output, fmt.Errorf("cannot export dataset of type '%s': only structured datasets are supported", datasetType)}// Check if output file existsif !input.DryRun {if _, err := os.Stat(input.Output); err == nil && !input.Force {sourceDB.Close()return output, fmt.Errorf("output file exists: %s (use --force to overwrite)", input.Output)}return output, err - replacement in tools/export.go at line 101
// Get FK order for tablesfkOrder, err := db.GetFKOrder(sourceDB)orderedTables, err := getOrderedTableManifest(sourceDB) - replacement in tools/export.go at line 104
return output, fmt.Errorf("failed to compute table order: %w", err)return output, err - edit in tools/export.go at line 106
// Sort our manifest by FK orderorderedTables := orderByFKDependency(datasetTables, fkOrder) - replacement in tools/export.go at line 107
// Calculate row counts for each tablefor _, tr := range orderedTables {count, err := countTableRows(ctx, sourceDB, tr, input.DatasetID)if err != nil {sourceDB.Close()return output, fmt.Errorf("failed to count rows in %s: %w", tr.Table, err)}if count > 0 {output.RowCounts[tr.Table] = count}if err := countAllTableRows(ctx, sourceDB, orderedTables, input.DatasetID, &output); err != nil {sourceDB.Close()return output, err - replacement in tools/export.go at line 122
// Create output directory if neededoutputDir := filepath.Dir(input.Output)if outputDir != "" && outputDir != "." {if err := os.MkdirAll(outputDir, 0755); err != nil {return output, fmt.Errorf("failed to create output directory: %w", err)}if err := createOutputDir(input.Output); err != nil {return output, err - edit in tools/export.go at line 126
// Create output database - edit in tools/export.go at line 129
}defer outputDB.Close()// Attach source database_, err = outputDB.ExecContext(ctx, fmt.Sprintf("ATTACH '%s' AS source", dbPath))if err != nil {return output, fmt.Errorf("failed to attach source database: %w", err) - replacement in tools/export.go at line 131
// Copy data in FK orderfor _, tr := range orderedTables {if tr.Relation == "copy" {// Copy entire table as-iserr = copyTableAsIs(ctx, outputDB, tr.Table)} else {// Owned or owned-via: filter by dataseterr = copyTableData(ctx, outputDB, tr, input.DatasetID)}if err != nil {return output, fmt.Errorf("failed to copy %s: %w", tr.Table, err)}if err := copyDataToOutput(ctx, outputDB, orderedTables, input.DatasetID); err != nil {return output, err - replacement in tools/export.go at line 136
_, err = outputDB.ExecContext(ctx, "DETACH source")if err != nil {if _, err := outputDB.ExecContext(ctx, "DETACH source"); err != nil { - edit in tools/export.go at line 140
// Close output DB before getting file size - edit in tools/export.go at line 141
outputDB = nil - edit in tools/export.go at line 163
// checkOutputFile returns an error if the output file already exists and force is not set.func checkOutputFile(input ExportDatasetInput) error {if input.DryRun {return nil}if _, err := os.Stat(input.Output); err == nil && !input.Force {return fmt.Errorf("output file exists: %s (use --force to overwrite)", input.Output)}return nil}// verifyExportDataset checks the dataset exists, is active, and is structured.func verifyExportDataset(ctx context.Context, sourceDB *sql.DB, input ExportDatasetInput) (string, error) {var datasetName, datasetType stringerr := sourceDB.QueryRowContext(ctx,"SELECT name, type FROM dataset WHERE id = ? AND active = true",input.DatasetID,).Scan(&datasetName, &datasetType)if err != nil {return "", fmt.Errorf("dataset not found: %s", input.DatasetID)}if datasetType != "structured" {return "", fmt.Errorf("cannot export dataset of type '%s': only structured datasets are supported", datasetType)}return datasetName, nil}// getOrderedTableManifest returns the dataset tables sorted by FK dependency.func getOrderedTableManifest(sourceDB *sql.DB) ([]TableRelationship, error) {fkOrder, err := db.GetFKOrder(sourceDB)if err != nil {return nil, fmt.Errorf("failed to compute table order: %w", err)}return orderByFKDependency(datasetTables, fkOrder), nil}// countAllTableRows populates output.RowCounts for all tables in the manifest.func countAllTableRows(ctx context.Context, sourceDB *sql.DB, tables []TableRelationship, datasetID string, output *ExportDatasetOutput) error {for _, tr := range tables {count, err := countTableRows(ctx, sourceDB, tr, datasetID)if err != nil {return fmt.Errorf("failed to count rows in %s: %w", tr.Table, err)}if count > 0 {output.RowCounts[tr.Table] = count}}return nil}// createOutputDir creates the output directory if needed.func createOutputDir(outputPath string) error {outputDir := filepath.Dir(outputPath)if outputDir != "" && outputDir != "." {if err := os.MkdirAll(outputDir, 0755); err != nil {return fmt.Errorf("failed to create output directory: %w", err)}}return nil}// copyDataToOutput attaches the source DB and copies all tables in FK order.func copyDataToOutput(ctx context.Context, outputDB *sql.DB, tables []TableRelationship, datasetID string) error {_, err := outputDB.ExecContext(ctx, fmt.Sprintf("ATTACH '%s' AS source", dbPath))if err != nil {return fmt.Errorf("failed to attach source database: %w", err)}for _, tr := range tables {if tr.Relation == "copy" {err = copyTableAsIs(ctx, outputDB, tr.Table)} else {err = copyTableData(ctx, outputDB, tr, datasetID)}if err != nil {return fmt.Errorf("failed to copy %s: %w", tr.Table, err)}}return nil} - replacement in tools/calls_detect_anomalies.go at line 124
// Collect ALL labeled segments per model — no scope filtering here.// Scope is applied to anchor selection only, so a "Don't Know" label in model[1]// against a "Kiwi" anchor in model[0] is correctly surfaced as a label_mismatch.modelSegs := make(map[string][]labeledSeg, len(models))for _, seg := range df.Segments {for _, lbl := range seg.Labels {for _, model := range models {if lbl.Filter == model {modelSegs[model] = append(modelSegs[model], labeledSeg{seg: seg, label: lbl})break}}}}modelSegs := collectModelSegments(df, models) - edit in tools/calls_detect_anomalies.go at line 134
// Use models[0] as anchor. Scope filtering applies here only — other models// contribute whatever they actually say for the overlapping time range. - replacement in tools/calls_detect_anomalies.go at line 135
if len(scope) > 0 {key := anchor.label.Speciesif anchor.label.CallType != "" {key += "+" + anchor.label.CallType}if !scope[key] && !scope[anchor.label.Species] {continueif !inScope(anchor, scope) {continue}if matches := findOverlappingMatches(anchor, models, modelSegs); matches == nil {continue} else {group := buildComparisonGroup(anchor, models, matches)if a := checkGroupAnomaly(group, path, models); a != nil {anomalies = append(anomalies, *a) - edit in tools/calls_detect_anomalies.go at line 146
}return anomalies} - replacement in tools/calls_detect_anomalies.go at line 150
// Find overlapping segments in every other model.matches := make(map[string][]labeledSeg, len(models)-1)lonely := falsefor _, model := range models[1:] {for _, candidate := range modelSegs[model] {if overlaps(anchor.seg, candidate.seg) {matches[model] = append(matches[model], candidate)// collectModelSegments groups labeled segments by model filter name.func collectModelSegments(df *utils.DataFile, models []string) map[string][]labeledSeg {modelSegs := make(map[string][]labeledSeg, len(models))for _, seg := range df.Segments {for _, lbl := range seg.Labels {for _, model := range models {if lbl.Filter == model {modelSegs[model] = append(modelSegs[model], labeledSeg{seg: seg, label: lbl})break - edit in tools/calls_detect_anomalies.go at line 161
if len(matches[model]) == 0 {lonely = truebreak} - replacement in tools/calls_detect_anomalies.go at line 162
if lonely {continue}}return modelSegs} - replacement in tools/calls_detect_anomalies.go at line 166
// Build comparison group: anchor + first overlapping match per other model// (consistent with propagate's approach).group := []labeledSeg{anchor}for _, model := range models[1:] {group = append(group, matches[model][0])}// inScope returns true if the anchor's label is within the species scope filter.func inScope(anchor labeledSeg, scope map[string]bool) bool {if len(scope) == 0 {return true}key := anchor.label.Speciesif anchor.label.CallType != "" {key += "+" + anchor.label.CallType}return scope[key] || scope[anchor.label.Species]} - replacement in tools/calls_detect_anomalies.go at line 178
// Check species+calltype agreement.refSpecies := group[0].label.SpeciesrefCallType := group[0].label.CallTypelabelMatch := truefor _, ls := range group[1:] {if ls.label.Species != refSpecies || ls.label.CallType != refCallType {labelMatch = falsebreak// findOverlappingMatches returns matches[model] = overlapping segments from that model,// or nil if any model has no overlap (lonely anchor).func findOverlappingMatches(anchor labeledSeg, models []string, modelSegs map[string][]labeledSeg) map[string][]labeledSeg {matches := make(map[string][]labeledSeg, len(models)-1)for _, model := range models[1:] {for _, candidate := range modelSegs[model] {if overlaps(anchor.seg, candidate.seg) {matches[model] = append(matches[model], candidate) - edit in tools/calls_detect_anomalies.go at line 188
if len(matches[model]) == 0 {return nil}}return matches} - replacement in tools/calls_detect_anomalies.go at line 195
if !labelMatch {anomalies = append(anomalies, Anomaly{File: path, Type: "label_mismatch", Segments: buildAnomalySegs(group, models)})continue}// buildComparisonGroup assembles anchor + first match per other model.func buildComparisonGroup(anchor labeledSeg, models []string, matches map[string][]labeledSeg) []labeledSeg {group := []labeledSeg{anchor}for _, model := range models[1:] {group = append(group, matches[model][0])}return group} - replacement in tools/calls_detect_anomalies.go at line 204
// Labels agree — check certainty.refCertainty := group[0].label.Certaintyfor _, ls := range group[1:] {if ls.label.Certainty != refCertainty {anomalies = append(anomalies, Anomaly{File: path, Type: "certainty_mismatch", Segments: buildAnomalySegs(group, models)})break}// checkGroupAnomaly checks a comparison group for label or certainty mismatches.func checkGroupAnomaly(group []labeledSeg, path string, models []string) *Anomaly {refSpecies := group[0].label.SpeciesrefCallType := group[0].label.CallTypefor _, ls := range group[1:] {if ls.label.Species != refSpecies || ls.label.CallType != refCallType {a := Anomaly{File: path, Type: "label_mismatch", Segments: buildAnomalySegs(group, models)}return &a - replacement in tools/calls_detect_anomalies.go at line 214
return anomaliesrefCertainty := group[0].label.Certaintyfor _, ls := range group[1:] {if ls.label.Certainty != refCertainty {a := Anomaly{File: path, Type: "certainty_mismatch", Segments: buildAnomalySegs(group, models)}return &a}}return nil - edit in tools/calls_clip_labels.go at line 87
- edit in tools/calls_clip_labels.go at line 262
}segs := resolveSegments(df.Segments, input.Filter, input.MinLabelOverlap, mapping, classIdx, out)rel, err := computeWavRelPath(path, cwd, folderAbs)if err != nil {return nil, err - edit in tools/calls_clip_labels.go at line 270
return labelClipWindows(windows, segs, rel, classes, input.MinLabelOverlap, out), nil} - replacement in tools/calls_clip_labels.go at line 274
// Resolve segments against the mapping. Skip:// - filter mismatch (when --filter set)// - annotation duration < min_label_overlap// - species not in mappingsegs := make([]resolvedSeg, 0, len(df.Segments))for _, seg := range df.Segments {if seg.EndTime-seg.StartTime < input.MinLabelOverlap {// resolveSegments maps segments to their classification and filters out mismatches.func resolveSegments(segments []*utils.Segment,filter string,minLabelOverlap float64,mapping utils.MappingFile,classIdx map[string]int,out *CallsClipLabelsOutput,) []resolvedSeg {segs := make([]resolvedSeg, 0, len(segments))for _, seg := range segments {if seg.EndTime-seg.StartTime < minLabelOverlap { - replacement in tools/calls_clip_labels.go at line 289
if input.Filter != "" && lbl.Filter != input.Filter {if filter != "" && lbl.Filter != filter { - replacement in tools/calls_clip_labels.go at line 299
segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind,})segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind}) - replacement in tools/calls_clip_labels.go at line 301
segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind,})segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind}) - replacement in tools/calls_clip_labels.go at line 307
segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind, classIdx: idx,})segs = append(segs, resolvedSeg{start: seg.StartTime, end: seg.EndTime, kind: kind, classIdx: idx}) - edit in tools/calls_clip_labels.go at line 311
return segs} - replacement in tools/calls_clip_labels.go at line 314
// Compute relative path for the WAV file.wavName := strings.TrimSuffix(filepath.Base(path), ".data")// computeWavRelPath computes the relative path from cwd to the WAV file corresponding to a .data file.func computeWavRelPath(dataPath, cwd, folderAbs string) (string, error) {wavName := strings.TrimSuffix(filepath.Base(dataPath), ".data") - edit in tools/calls_clip_labels.go at line 326
return rel, nil} - replacement in tools/calls_clip_labels.go at line 329
// Label each clip window.// labelClipWindows classifies each clip window and builds the output rows.func labelClipWindows(windows []utils.ClipWindow, segs []resolvedSeg, rel string, classes []string, minLabelOverlap float64, out *CallsClipLabelsOutput) []clipLabelsRow { - replacement in tools/calls_clip_labels.go at line 333
dispo, classHits := classifyClip(w, segs, input.MinLabelOverlap, len(classes))dispo, classHits := classifyClip(w, segs, minLabelOverlap, len(classes)) - edit in tools/calls_clip_labels.go at line 350
// flags stay all-False — __NEGATIVE__ overrides positives - replacement in tools/calls_clip_labels.go at line 362
return rows, nilreturn rows