tbdflow 0.32.1

A CLI to streamline your Git workflow for Trunk-Based Development.
Documentation
# ═══════════════════════════════════════════════════════════════════════════════
# tbdflow — Sync, Status & Changelog
# ═══════════════════════════════════════════════════════════════════════════════
# User Story: As a developer, I want to see the current state of my repo
# and generate changelogs so that I always know what has changed and what
# is ready for release.
# ═══════════════════════════════════════════════════════════════════════════════

feature "tbdflow Sync, Status & Changelog"

actors: Terminal, System

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

# Each scenario gets its own isolated repo
var SYNC_REPO = "/tmp/tbdflow_sync_test"
var SYNC_BARE = "/tmp/tbdflow_sync_bare.git"
var CHANGELOG_REPO = "/tmp/tbdflow_changelog_test"
var CHANGELOG_BARE = "/tmp/tbdflow_changelog_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 init_repo_with_tag(repo, bare, tag) {
    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} tag ${tag}"
    Terminal run "git -C ${repo} remote add origin ${bare}"
    Terminal run "git -C ${repo} push -u origin main --tags"
    Terminal set_cwd "${repo}"
}

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}'"
}

task commit_scoped(type, scope, message) {
    Terminal run "tbdflow commit -t ${type} -s ${scope} -m '${message}'"
}

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

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

task generate_unreleased_changelog() {
    Terminal run "tbdflow changelog --unreleased"
}

# --- Condition drivers ---

task verify_status_clean() {
    Terminal last_command succeeded
    Terminal output_contains "clean"
    Terminal output_not_contains "Error"
}

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

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

# ─────────────────────────────────────────────────────────────────────────────
# AC1: `tbdflow status` must report a clean tree when nothing has changed.
# AC2: `tbdflow sync` must pull latest and succeed without errors.
# AC3: `tbdflow status` must detect uncommitted changes.
# ─────────────────────────────────────────────────────────────────────────────

scenario "Sync and status on a clean repo" {

    test Setup "Initialise test repository" {
        given:
            Test can_start
        when:
            init_repo("${SYNC_REPO}", "${SYNC_BARE}")
        then:
            Terminal last_command succeeded
    }

    test StatusClean "Status reports a clean working tree" {
        given:
            Test has_succeeded Setup
        when:
            check_status()
        then:
            verify_status_clean()
    }

    test SyncClean "Sync succeeds on a repo with remote" {
        given:
            Test has_succeeded StatusClean
        when:
            sync()
        then:
            verify_no_errors()
    }

    test StatusAfterChanges "Status detects uncommitted changes" {
        given:
            Test has_succeeded SyncClean
        when:
            make_change("untracked.txt", "dirty")
            check_status()
        then:
            Terminal last_command succeeded
            Terminal output_contains "untracked.txt"
    }

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

# ─────────────────────────────────────────────────────────────────────────────
# AC4: `tbdflow changelog --unreleased` must list commits since the last tag.
# AC5: The changelog must include both features and fixes.
# ─────────────────────────────────────────────────────────────────────────────

scenario "Generating a changelog from commit history" {

    test Setup "Initialise test repository with a tag" {
        given:
            Test can_start
        when:
            init_repo_with_tag("${CHANGELOG_REPO}", "${CHANGELOG_BARE}", "v0.0.0")
        then:
            Terminal last_command succeeded
    }

    test SeedCommits "Create a series of conventional commits" {
        given:
            Test has_succeeded Setup
        when:
            make_change("a.txt", "a")
            commit("feat", "add feature a")
            make_change("b.txt", "b")
            commit_scoped("fix", "core", "resolve crash on startup")
            make_change("c.txt", "c")
            commit("docs", "update readme")
        then:
            verify_no_errors()
    }

    test UnreleasedChangelog "Changelog --unreleased shows recent work" {
        given:
            Test has_succeeded SeedCommits
        when:
            generate_unreleased_changelog()
        then:
            Terminal last_command succeeded
            Terminal output_contains "add feature a"
            Terminal output_contains "resolve crash on startup"
            Terminal output_not_contains "Error"
    }

    after {
        cleanup_repo("${CHANGELOG_REPO}", "${CHANGELOG_BARE}")
    }
}