# ═══════════════════════════════════════════════════════════════════════════════
# 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}")
}
}