tbdflow 0.34.0

A CLI to streamline your Git workflow for Trunk-Based Development.
Documentation
# ═══════════════════════════════════════════════════════════════════════════════
# tbdflow — Radar (Overlap Detection for Social Coding)
# ═══════════════════════════════════════════════════════════════════════════════
# User Story: As a developer working in a TBD team, I want to be warned when
# my local changes overlap with work on active remote branches so that I can
# coordinate with teammates before pushing and avoid merge hell.
# ═══════════════════════════════════════════════════════════════════════════════

feature "tbdflow Radar — Overlap Detection"

actors: Terminal

settings {
    timeout_seconds = 30
    stop_on_failure = true
    shell_path = "/bin/bash"
}

# Each scenario gets its own isolated repo pair
var RADAR_REPO     = "/tmp/tbdflow_radar_test"
var RADAR_BARE     = "/tmp/tbdflow_radar_bare.git"
var DISABLED_REPO  = "/tmp/tbdflow_radar_disabled"
var DISABLED_BARE  = "/tmp/tbdflow_radar_disabled_bare.git"
var SYNC_REPO      = "/tmp/tbdflow_radar_sync"
var SYNC_BARE      = "/tmp/tbdflow_radar_sync_bare.git"

# ═══════════════════════════════════════════════════════════════════════════════
# IMPLEMENTATION LAYER — Tasks hide the "how"
# ═══════════════════════════════════════════════════════════════════════════════

# --- Setup drivers ---

task init_repo(repo, bare) {
    Terminal run "rm -rf ${repo} ${bare} && mkdir -p ${repo}"
    Terminal run "git init --bare ${bare}"
    Terminal run "git -C ${repo} init && git -C ${repo} commit --allow-empty -m 'init'"
    Terminal run "git -C ${repo} remote add origin ${bare}"
    Terminal run "git -C ${repo} push -u origin main"
    Terminal set_cwd "${repo}"
}

task write_radar_config(enabled) {
    Terminal run "cat > .tbdflow.yml << 'EOF'\nmain_branch_name: main\nstale_branch_threshold_days: 1\nradar:\n  enabled: ${enabled}\n  level: file\n  on_sync: true\n  on_commit: off\nbranch_types:\n  feat: \"feat/\"\n  fix: \"fix/\"\nautomatic_tags:\n  release_prefix: \"v\"\nEOF"
}

task commit_config() {
    Terminal run "tbdflow commit -t chore -m 'add tbdflow config' --no-verify"
}

task cleanup_repo(repo, bare) {
    Terminal run "rm -rf ${repo} ${bare}"
}

# --- Action drivers ---

task make_change(filename, content) {
    Terminal run "echo '${content}' > ${filename}"
}

task commit(type, message) {
    Terminal run "tbdflow commit -t ${type} -m '${message}' --no-verify"
}

task create_branch(type, name) {
    Terminal run "tbdflow branch -t ${type} -n ${name}"
}

task complete_branch(type, name) {
    Terminal run "tbdflow complete -t ${type} -n ${name}"
}

task radar() {
    Terminal run "tbdflow radar"
}

task sync() {
    Terminal run "tbdflow sync"
}

task check_status() {
    Terminal run "tbdflow status"
}

# Simulates a teammate: use tbdflow to create a branch, commit work,
# then switch back to main. The branch stays active on the remote (not
# completed) — it represents work-in-progress by another developer.
# Uses raw `git checkout main` at the end because `tbdflow complete`
# would merge and delete the branch — we need it to stay active.
task simulate_teammate_on_branch(type, name, filename, content) {
    Terminal run "tbdflow branch -t ${type} -n ${name}"
    Terminal run "echo '${content}' > ${filename}"
    Terminal run "tbdflow commit -t feat -m 'teammate work on ${filename}' --no-verify"
    Terminal run "git checkout main"
}

# --- Condition drivers ---

task verify_radar_disabled() {
    Terminal last_command succeeded
    Terminal output_contains "Radar is disabled"
}

task verify_no_overlaps() {
    Terminal last_command succeeded
    Terminal output_contains "No overlaps detected"
    Terminal output_not_contains "OVERLAP DETECTED"
}

task verify_no_local_changes() {
    Terminal last_command succeeded
    Terminal output_contains "No local changes detected"
}

task verify_overlap_detected() {
    Terminal last_command succeeded
    Terminal output_contains "OVERLAP DETECTED"
}

task verify_overlap_with_file(filename) {
    Terminal last_command succeeded
    Terminal output_contains "OVERLAP DETECTED"
    Terminal output_contains "${filename}"
}

task verify_overlap_with_branch(branch_name) {
    Terminal last_command succeeded
    Terminal output_contains "${branch_name}"
}

task verify_radar_hint_in_sync() {
    Terminal last_command succeeded
    Terminal output_contains "Radar"
    Terminal output_contains "tbdflow radar"
}

task verify_no_errors() {
    Terminal last_command succeeded
    Terminal output_not_contains "Error"
}

# ═══════════════════════════════════════════════════════════════════════════════
# BUSINESS SPECIFICATION LAYER — Scenarios describe the "what"
# ═══════════════════════════════════════════════════════════════════════════════

# ─────────────────────────────────────────────────────────────────────────────
# AC1: `tbdflow radar` must show a disabled message when radar is not enabled.
# AC2: `tbdflow radar` must report no overlaps when no remote branches exist.
# AC3: `tbdflow radar` must report no overlaps when branches touch different files.
# AC4: `tbdflow radar` must detect file-level overlap with an active branch.
# AC5: `tbdflow radar` must show the overlapping branch name and file.
# AC6: `tbdflow sync` must show a radar warning when overlap is detected.
# ─────────────────────────────────────────────────────────────────────────────

scenario "Radar reports disabled when not enabled in config" {

    test Setup "Initialise test repository without radar enabled" {
        given:
            Test can_start
        when:
            init_repo("${DISABLED_REPO}", "${DISABLED_BARE}")
            write_radar_config("false")
            commit_config()
        then:
            Terminal last_command succeeded
    }

    test RadarDisabled "Radar shows a disabled message" {
        given:
            Test has_succeeded Setup
        when:
            radar()
        then:
            verify_radar_disabled()
    }

    after {
        cleanup_repo("${DISABLED_REPO}", "${DISABLED_BARE}")
    }
}

scenario "Radar reports clean when no overlapping work exists" {

    test Setup "Initialise test repository with radar enabled" {
        given:
            Test can_start
        when:
            init_repo("${RADAR_REPO}", "${RADAR_BARE}")
            write_radar_config("true")
            commit_config()
        then:
            Terminal last_command succeeded
    }

    test NoLocalChanges "Radar reports no local changes when working tree is clean" {
        given:
            Test has_succeeded Setup
        when:
            radar()
        then:
            verify_no_local_changes()
    }

    test NoOverlaps "Radar reports no overlaps with only local changes" {
        given:
            Test has_succeeded NoLocalChanges
        when:
            # Modify an existing tracked file so radar sees local changes
            Terminal run "echo '# modified' >> .tbdflow.yml"
            radar()
        then:
            verify_no_overlaps()
    }

    test NoOverlapsDifferentFiles "Radar is clear when teammate touches different files" {
        given:
            Test has_succeeded NoOverlaps
        when:
            # Discard our local changes first
            Terminal run "git checkout -- . 2>/dev/null; git clean -fd 2>/dev/null"
            # Teammate creates a branch and works on their_work.txt
            simulate_teammate_on_branch("feat", "other-feature", "their_work.txt", "teammate changes")
            # I modify a different tracked file
            Terminal run "echo '# my edit' >> .tbdflow.yml"
            radar()
        then:
            verify_no_overlaps()
    }

    after {
        cleanup_repo("${RADAR_REPO}", "${RADAR_BARE}")
    }
}

scenario "Radar detects file-level overlap with an active branch" {

    test Setup "Initialise test repository with radar enabled" {
        given:
            Test can_start
        when:
            init_repo("${RADAR_REPO}", "${RADAR_BARE}")
            write_radar_config("true")
            commit_config()
            # Create a shared file on main that both sides will modify
            make_change("shared.txt", "original content")
            commit("chore", "add shared file")
        then:
            Terminal last_command succeeded
    }

    test TeammateWork "A teammate creates a branch and modifies the shared file" {
        given:
            Test has_succeeded Setup
        when:
            simulate_teammate_on_branch("feat", "teammate-work", "shared.txt", "teammate modified this")
        then:
            Terminal last_command succeeded
    }

    test RadarDetectsOverlap "Radar warns about the overlapping file and branch" {
        given:
            Test has_succeeded TeammateWork
        when:
            make_change("shared.txt", "my local modification")
            radar()
        then:
            verify_overlap_with_file("shared.txt")
            verify_overlap_with_branch("feat/teammate-work")
    }

    after {
        cleanup_repo("${RADAR_REPO}", "${RADAR_BARE}")
    }
}

scenario "Radar integrates with sync to show overlap warnings" {

    test Setup "Initialise test repository with radar and on_sync enabled" {
        given:
            Test can_start
        when:
            init_repo("${SYNC_REPO}", "${SYNC_BARE}")
            write_radar_config("true")
            commit_config()
            # Create a base file on main
            make_change("api.rs", "fn main() {}")
            commit("feat", "add api module")
        then:
            Terminal last_command succeeded
    }

    test TeammateModifiesApi "A teammate creates a branch modifying the same API file" {
        given:
            Test has_succeeded Setup
        when:
            simulate_teammate_on_branch("feat", "api-refactor", "api.rs", "fn refactored() {}")
        then:
            Terminal last_command succeeded
    }

    test SyncShowsRadarWarning "Running sync surfaces the radar warning inline" {
        given:
            Test has_succeeded TeammateModifiesApi
        when:
            make_change("api.rs", "fn my_changes() {}")
            sync()
        then:
            verify_radar_hint_in_sync()
    }

    test RadarGivesFullDetails "Running radar after sync shows the full overlap report" {
        given:
            Test has_succeeded SyncShowsRadarWarning
        when:
            radar()
        then:
            verify_overlap_with_file("api.rs")
            verify_overlap_with_branch("feat/api-refactor")
    }

    after {
        cleanup_repo("${SYNC_REPO}", "${SYNC_BARE}")
    }
}