diff --git a/.gitattributes b/.gitattributes index df18e443..762e5efe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ /.github export-ignore +/.task export-ignore /bin export-ignore /docs export-ignore /example export-ignore diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..9676adfd --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,1126 @@ +# Bashunit β€” Copilot Instructions + +> **Prime directive**: We practice **Test-Driven Development (TDD) by default**. Write a failing test first, make it fail **for the right reason**, implement the **smallest** change to pass, then **refactor** while keeping all tests green. + +> **🚨 MANDATORY WORKFLOW RULE (NO EXCEPTIONS) 🚨:** **STOP! BEFORE READING FURTHER** - Create a task file `./.tasks/YYYY-MM-DD-feature-title.md` for **EVERY SINGLE CHANGE** including documentation updates, instruction modifications, bug fixes, new features, refactoring - **EVERYTHING**. Work within this file throughout the entire task, documenting all progress and thought process. + +> **🚨 MANDATORY WORKFLOW RULE (NO EXCEPTIONS) 🚨:** **STOP! BEFORE READING FURTHER** - To finish any task the definition of done must be fully satisfied. + +> **Clarity Rule:** If acceptance criteria or the intended outcomes are not clear or ambiguous, **ask clarifying questions before making any change** and record the answers in the active task file. + +> **πŸ“‹ External Developer Tools**: This repository includes `AGENTS.md` in the root directory with essential workflow information for external developer tools. When making significant changes to development workflow, TDD methodology, or core patterns, consider updating `AGENTS.md` to keep it synchronized with these comprehensive instructions. + +--- + +## Cross-file synchronization with `AGENTS.md` + +To keep guidance coherent and up to date, we enforce a two-way sync policy: +- When `copilot-instructions.md` changes, evaluate whether the change belongs in `AGENTS.md` and update `AGENTS.md` automatically if so. +- When `AGENTS.md` changes, evaluate whether the change belongs in `copilot-instructions.md` and update this file automatically if so. +- If a change is intentionally not mirrored, record the rationale in the active `./.tasks/YYYY-MM-DD-slug.md`. + +--- + +## What this repository is + +An open-source **library** providing a fast, portable Bash testing framework: **bashunit**. It offers: + +* Minimal overhead, plain Bash test files. +* Rich **assertions**, **test doubles (mock/spy)**, **data providers**, **snapshots**, **skip/todo**, **globals utilities**, **custom assertions**, **benchmarks**, and **standalone** runs. + +**Compatibility**: Bash 3.2+ (macOS, Linux, WSL). No external dependencies beyond standard Unix tools. + +--- + +## πŸ›‘ STEP 0: MANDATORY TASK FILE CREATION (READ THIS FIRST) + +**DO NOT PROCEED WITHOUT COMPLETING THIS STEP** + +### EVERY agent must do this BEFORE any work: + +1. **STOP and CREATE task file**: `.tasks/YYYY-MM-DD-feature-title.md` (in English) + - Example: `.tasks/2025-09-17-add-assert-json-functionality.md` + - Example: `.tasks/2025-09-17-fix-mock-cleanup-bug.md` + - Example: `.tasks/2025-09-17-update-documentation.md` + - Example: `.tasks/2025-09-17-enhance-copilot-instructions.md` + +2. **CHOOSE appropriate template**: + - **New user capability**: Use Template A (new assertions, CLI features, test doubles) + - **Internal modifications**: Use Template B (refactors, fixes, docs) + +3. **FILL task information immediately**: Complete all sections with specific acceptance criteria + +4. **WORK within this file throughout the task**: Update test inventory, track progress, document all decisions + +### ⚠️ ABSOLUTE RULES - NO RATIONALIZATION ALLOWED: +- **"The task is simple"** β†’ **STILL REQUIRES TASK FILE** +- **"It's just a bug fix"** β†’ **STILL REQUIRES TASK FILE** +- **"It's just documentation"** β†’ **STILL REQUIRES TASK FILE** +- **"I'm updating instructions"** β†’ **STILL REQUIRES TASK FILE** +- **"It's a tiny change"** β†’ **STILL REQUIRES TASK FILE** + +**If you're reading this and haven't created a task file yet - STOP NOW and create one.** + +--- + +## Learn from existing tests (essential reference) + +**Before writing any code, study the patterns in `./tests/`:** + +* `tests/unit/` - Unit tests for individual functions and modules (22+ test files) +* `tests/functional/` - Integration tests for feature combinations (6 test files) +* `tests/acceptance/` - End-to-end CLI and workflow tests (15+ test files) +* `tests/benchmark/` - Performance and timing tests + +**Critical test files to study for patterns:** + +### Core assertion patterns (`tests/unit/assert_test.sh`) +```bash +# Data provider pattern with @data_provider comment +# @data_provider provider_successful_assert_true +function test_successful_assert_true() { + assert_empty "$(assert_true $1)" +} + +function provider_successful_assert_true() { + data_set true + data_set "true" + data_set 0 +} + +# Testing assertion failures with expected console output +function test_unsuccessful_assert_true() { + assert_same\ + "$(console_results::print_failed_test\ + "Unsuccessful assert true" \ + "true or 0" \ + "but got " "false")"\ + "$(assert_true false)" +} +``` + +### Setup/teardown patterns (`tests/unit/setup_teardown_test.sh`) +```bash +TEST_COUNTER=1 + +function set_up_before_script() { + TEST_COUNTER=$(( TEST_COUNTER + 1 )) +} + +function set_up() { + TEST_COUNTER=$(( TEST_COUNTER + 1 )) +} + +function tear_down() { + TEST_COUNTER=$(( TEST_COUNTER - 1 )) +} + +function tear_down_after_script() { + TEST_COUNTER=$(( TEST_COUNTER - 1 )) +} + +function test_counter_is_incremented_after_setup_before_script_and_setup() { + assert_same "3" "$TEST_COUNTER" +} +``` + +### Test doubles patterns (`tests/functional/doubles_test.sh`) +```bash +function test_mock_ps_when_executing_a_script() { + mock ps cat ./tests/functional/fixtures/doubles_ps_output + + assert_match_snapshot "$(source ./tests/functional/fixtures/doubles_script.sh)" +} + +function test_spy_commands_called_when_executing_a_sourced_function() { + source ./tests/functional/fixtures/doubles_function.sh + spy ps + spy awk + spy head + + top_mem + + assert_have_been_called ps + assert_have_been_called awk + assert_have_been_called head +} + +function test_spy_commands_called_once_when_executing_a_script() { + spy ps + ./tests/functional/fixtures/doubles_script.sh + assert_have_been_called_times 1 ps +} +``` + +### Data provider patterns (`tests/functional/provider_test.sh`) +```bash +function set_up() { + _GLOBAL="aa-bb" +} + +# @data_provider provide_multiples_values +function test_multiple_values_from_data_provider() { + local first=$1 + local second=$2 + assert_equals "${_GLOBAL}" "$first-$second" +} + +function provide_multiples_values() { + echo "aa" "bb" +} + +# @data_provider provide_single_values +function test_single_values_from_data_provider() { + local data="$1" + assert_not_equals "zero" "$data" +} + +function provide_single_values() { + echo "one" + echo "two" + echo "three" +} +``` + +### CLI acceptance patterns (`tests/acceptance/bashunit_fail_test.sh`) +```bash +function set_up_before_script() { + TEST_ENV_FILE="tests/acceptance/fixtures/.env.default" + TEST_ENV_FILE_SIMPLE="tests/acceptance/fixtures/.env.simple" +} + +function test_bashunit_when_a_test_fail_verbose_output_env() { + local test_file=./tests/acceptance/fixtures/test_bashunit_when_a_test_fail.sh + + assert_match_snapshot "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")" + assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")" +} +``` + +### Custom assertions (`tests/functional/custom_asserts.sh`) +```bash +function assert_foo() { + local actual="$1" + local expected="foo" + + if [[ "$expected" != "$actual" ]]; then + bashunit::assertion_failed "$expected" "${actual}" + return + fi + + bashunit::assertion_passed +} + +function assert_positive_number() { + local actual="$1" + + if [[ "$actual" -le 0 ]]; then + bashunit::assertion_failed "positive number" "${actual}" "got" + return + fi + + bashunit::assertion_passed +} +``` + +--- + +## Before you touch any code + +1. **Read ADRs first** + * Review existing ADRs in the `adrs/` folder to understand decisions, constraints, and paved-road patterns. + * Current ADRs: error detection, booleans, parallel testing, metadata prefix, copilot instructions + * If your change introduces a significant decision, **create a new ADR** using `adrs/TEMPLATE.md`. + +2. **Create a task file (required)** + * Path: `./.tasks/YYYY-MM-DD-slug.md` (format: `YYYY-MM-DD-slug.md`) + * This file is **versioned** and is the single source of truth for your current task. + +3. **Study existing test patterns extensively** + * **Unit tests**: Look at `tests/unit/assert_test.sh`, `tests/unit/globals_test.sh`, `tests/unit/test_doubles_test.sh` + * **Functional tests**: Check `tests/functional/doubles_test.sh`, `tests/functional/provider_test.sh` + * **Acceptance tests**: Study `tests/acceptance/bashunit_test.sh`, `tests/acceptance/mock_test.sh` + * Follow established naming, structure, and assertion patterns exactly + +--- + +## Double-Loop TDD + +We practice two nested feedback loops to deliver behavior safely and quickly. + +### Outer loop: acceptance first + +- Start from user value. For any new user-visible capability, write a high-level acceptance test that exercises the system through its public entry point (CLI, function API). +- Keep the acceptance test red. It defines the next slice of behavior we must implement. +- When the acceptance test is too broad, split it into thinner vertical slices that still provide visible progress. + +### Inner loop: design-driving tests + +- Drive the implementation with smaller tests created only when needed: + - Unit tests for individual functions and modules + - Functional tests for integration between components +- Follow the classic cycle: + 1) **Red**: write a failing test for the next micro-behavior + 2) **Green**: write the minimum production code to pass + 3) **Refactor**: improve design in both production and tests while keeping all tests green + +### Test inventory and prioritization + +- Maintain a living test inventory in `./.tasks/Task.md` for the active task +- Track acceptance tests, unit tests, and functional tests that define the capability +- After every refactor, review the inventory. Add missing cases, then re-prioritize +- The top priority is the test that is currently red + +### Important rules + +- **Never stop at tests only**: Always add the production code that actually uses the new behavior in the application flow +- **Avoid speculative tests**: Write the next test only when a failing acceptance path or design pressure calls for it +- **Keep tests deterministic**: No hidden time, randomness, or cross-test coupling +- **Prefer observable behavior over internal structure**: If refactoring breaks a test without changing behavior, fix the test, not the refactor + +--- + +## Bash coding standards (bashunit-specific) + +### Compatibility & Portability +```bash +# βœ… GOOD - Works on Bash 3.2+ +[[ -n "${var:-}" ]] && echo "set" +array=("item1" "item2") + +# ❌ BAD - Bash 4+ only +declare -A assoc_array +readarray -t lines < file +``` + +### Error handling & safety (observed patterns) +```bash +# βœ… GOOD - Safe parameter expansion (from tests) +local param="${1:-}" +[[ -z "${param}" ]] && return 1 + +# βœ… GOOD - Function existence check (from globals_test.sh) +function existing_fn(){ + return 0 +} +assert_successful_code "$(is_command_available existing_fn)" + +# ❌ BAD - Unsafe expansion +local param=$1 # fails if $1 is unset with set -u +``` + +### Function naming & organization (actual patterns) +```bash +# βœ… GOOD - Module namespacing (from actual codebase) +function console_results::print_failed_test() { ... } +function console_results::print_skipped_test() { ... } +function console_results::print_incomplete_test() { ... } +function state::add_assertions_failed() { ... } +function helper::normalize_test_function_name() { ... } + +# βœ… GOOD - Test function naming (from actual tests) +function test_successful_assert_true() { ... } +function test_unsuccessful_assert_true_with_custom_message() { ... } +function test_bashunit_when_a_test_fail_verbose_output_env() { ... } + +# Data provider naming (from functional/provider_test.sh) +function provide_multiples_values() { ... } +function provide_single_values() { ... } +``` + +### String handling & output (real examples) +```bash +# βœ… GOOD - Line continuation for readability (from assert_test.sh) +assert_same\ + "$(console_results::print_failed_test\ + "Unsuccessful assert true" \ + "true or 0" \ + "but got " "false")"\ + "$(assert_true false)" + +# βœ… GOOD - Proper quoting and color handling +local colored=$(printf '\e[31mHello\e[0m World!') +assert_empty "$(assert_match_snapshot_ignore_colors "$colored")" +``` + +--- + +## Assertion patterns (real examples from tests/unit/assert_test.sh) + +### Complete assertion catalog (verified in codebase) +```bash +# Equality assertions +assert_same "expected" "${actual}" +assert_not_same "unexpected" "${actual}" +assert_equals "expected" "${actual}" # alias for assert_same +assert_not_equals "unexpected" "${actual}" # alias for assert_not_same + +# Truthiness assertions +assert_true "command_or_function" +assert_false "failing_command" +assert_successful_code "command" # tests exit code 0 +assert_general_error "failing_command" # tests exit code != 0 + +# String assertions +assert_contains "needle" "${haystack}" +assert_not_contains "needle" "${haystack}" +assert_matches "^[0-9]+$" "${value}" # regex matching +assert_string_starts_with "prefix" "${string}" +assert_string_ends_with "suffix" "${string}" + +# Numeric assertions +assert_greater_than 10 "${n}" +assert_less_than 5 "${m}" +assert_greater_or_equal_than 10 "${n}" +assert_less_or_equal_than 5 "${m}" + +# Emptiness assertions +assert_empty "${maybe_empty}" +assert_not_empty "${something}" + +# File/directory assertions (from tests/unit/file_test.sh) +assert_file_exists "${filepath}" +assert_file_not_exists "${filepath}" +assert_directory_exists "${dirpath}" +assert_directory_not_exists "${dirpath}" + +# Array assertions (from tests/unit/assert_arrays_test.sh if exists) +assert_array_contains "element" "${array[@]}" +assert_array_not_contains "element" "${array[@]}" + +# Snapshot assertions (from tests/unit/assert_snapshot_test.sh) +assert_match_snapshot "${output}" +assert_match_snapshot "${output}" "custom_snapshot_name" +assert_match_snapshot_ignore_colors "${colored_output}" +``` + +### Advanced assertion patterns (from real tests) +```bash +# Output capture and assertion (common pattern) +assert_empty "$(assert_true true)" # success case produces no output + +# Multiple assertions on same output +local output +output="$(complex_function)" +assert_contains "expected_part" "${output}" +assert_not_contains "unexpected_part" "${output}" + +# Testing assertion failures (critical pattern from assert_test.sh) +assert_same\ + "$(console_results::print_failed_test\ + "Test name" \ + "expected_value" \ + "but got " "actual_value")"\ + "$(failing_assertion)" +``` + +--- + +## Test doubles patterns (from tests/functional/doubles_test.sh & tests/unit/test_doubles_test.sh) + +### Mock patterns (with file fixtures) +```bash +function test_mock_with_file_content() { + # Mock with file content + mock ps cat ./tests/functional/fixtures/doubles_ps_output + assert_match_snapshot "$(source ./tests/functional/fixtures/doubles_script.sh)" +} + +function test_mock_with_inline_content() { + # Mock with heredoc + mock ps< + +## Context and intent + +- Business value: +- User story or job to be done: +- Scope boundaries and out of scope: + +## Acceptance test - outer loop + +- Entry point: +- Scenarios: + - Happy path: + - Alternatives and errors: +- Data and fixtures: +- How to run: + - Command: ./bashunit + +## Test inventory + +### Acceptance tests + +- [ ] AT-1: +- [ ] AT-2: + +### Unit tests + +- [ ] U-1: +- [ ] U-2: + +### Functional tests + +- [ ] F-1: +- [ ] F-2: + +## Current red bar + +- Failing test: +- Test file: +- Smallest next step: + +## Design notes during refactor + +- Coupling reduced: +- Names improved: +- Risks mitigated: + +## Logbook (agent thought process) + +### [YYYY-MM-DD HH:mm] Initial analysis +- What I understand about the task: +- Key assumptions I'm making: +- Test patterns I'll follow from existing code: + +### [YYYY-MM-DD HH:mm] TDD cycle progress +- Current test being worked on: +- Why I chose this test first: +- What I expect this test to validate: + +### [YYYY-MM-DD HH:mm] Implementation decisions +- Functions/patterns I'm using and why: +- Existing code patterns I'm following: +- Error handling approach: + +### [YYYY-MM-DD HH:mm] Obstacles and solutions +- Issues encountered: +- How I'm resolving them: +- Alternative approaches considered: + +## Done checklist + +- [ ] All acceptance, unit, and functional tests green +- [ ] Production code used in real application flow +- [ ] Lint clean (shellcheck + shfmt) +- [ ] All existing tests still pass +- [ ] Follows exact patterns from existing test files +- [ ] **AGENTS.md updated** if changes affect development workflow, TDD methodology, or core patterns +- [ ] **Two-way sync validated** (`AGENTS.md` ↔ `.github/copilot-instructions.md`) +- [ ] Docs updated if needed +- [ ] ADR created if architectural decision made +- [ ] Timestamp: +``` + +### Task Template B - Modification + +Use this template for internal changes, fixes, refactors, documentation. + +```markdown +# Task: + +## Context and intent + +- Why this change is needed: +- Impacted area or module: +- Out of scope: + +## Acceptance criteria + +- AC-1: +- AC-2: +- AC-3: + +## Test inventory + +### Unit tests + +- [ ] U-1: +- [ ] U-2: + +### Functional tests (if needed) + +- [ ] F-1: + +### Acceptance tests (only if externally observable) + +- [ ] AT-1: + +## Current red bar + +- Failing test: +- Test file: +- Smallest next step: + +## Logbook (agent thought process) + +### [YYYY-MM-DD HH:mm] Initial analysis +- Current state understanding: +- Why this change is needed: +- Risk assessment: + +### [YYYY-MM-DD HH:mm] Implementation approach +- Strategy chosen and rationale: +- Tests that need updating: +- Backward compatibility considerations: + +### [YYYY-MM-DD HH:mm] Progress updates +- What's working: +- What's challenging: +- Adjustments made: + +## Done checklist + +- [ ] All listed tests green +- [ ] Production code actually used by the application flow +- [ ] Lint clean (shellcheck + shfmt) +- [ ] All existing tests still pass +- [ ] Follows exact patterns from existing test files +- [ ] Mock/spy cleanup verified if applicable +- [ ] Snapshot tests updated if output changed +- [ ] Docs updated if needed +- [ ] ADR added/updated if architectural decision +- [ ] Updated copilot-instructions if relevant and needed +- [ ] **AGENTS.md updated** if changes affect development workflow, TDD methodology, or core patterns +- [ ] **Two-way sync validated** (`AGENTS.md` ↔ `.github/copilot-instructions.md`) +- [ ] Timestamp: +``` + +--- + +## Definition of Done (enhanced) + +### βœ… Code quality (verified standards) +- **All tests pass** (`./bashunit tests/`) +- **Shellcheck passes** with existing exceptions (`shellcheck -x $(find . -name "*.sh")`) +- **Code formatted** (`shfmt -w .`) +- **Bash 3.2+ compatible** (no `declare -A`, no `readarray`, no `${var^^}`) +- **Follows established module namespacing** patterns + +### βœ… Testing (following observed patterns) +- **Unit tests** follow exact patterns from `tests/unit/assert_test.sh` + - Use line continuation for complex assertions + - Test both success and failure cases + - Follow `test_successful_*` and `test_unsuccessful_*` naming +- **Functional tests** use patterns from `tests/functional/doubles_test.sh` + - Proper mock/spy setup and cleanup verification + - Integration with fixture files when appropriate +- **CLI tests** follow patterns from `tests/acceptance/bashunit_*_test.sh` + - Use proper environment file setup + - Test both output content and exit codes + - Snapshot testing for stable CLI output +- **Data providers** use exact `@data_provider` comment syntax +- **Lifecycle hooks** follow `set_up_before_script` patterns exactly + +### βœ… TDD compliance (critical) +- **Production code actually used** by the application flow +- **Tests written first** and failed for the right reason +- **Refactoring performed** while keeping all tests green +- **Test inventory completed** as documented in task file + +### βœ… Pattern compliance (critical) +- **Function naming** follows module::function or test_description patterns +- **Variable declarations** use safe `"${var:-}"` expansion +- **Error handling** follows existing `if [[ condition ]]; then` patterns +- **Output capture** uses `"$(command)"` pattern consistently +- **Mock/spy tests** verify cleanup between tests + +### βœ… Documentation +- **README updated** if public API changes +- **Relevant docs/** files updated when functionality changes +- **Code comments** only for complex logic (prefer clear code) +- **Examples** are executable and follow existing test patterns + +### βœ… Process +- **Task file completed** with specific patterns studied and all progress documented +- **ADR created** if architectural decision made +- **Git history** follows existing commit message patterns +- **No deviation** from established patterns without documented reason +- **AGENTS.md updated** if changes affect development workflow, TDD methodology, or core patterns + +--- + +## Examples from the codebase (mandatory study list) + +**Critical files to study for any change:** + +### Core patterns (study first, always) +- `tests/unit/assert_test.sh` - Master template for assertion patterns and failure testing +- `tests/unit/setup_teardown_test.sh` - Definitive lifecycle hook patterns +- `tests/unit/globals_test.sh` - Canonical utility function testing patterns +- `src/console_results.sh` - Output formatting functions used in tests + +### Feature-specific patterns (study for relevant changes) +- `tests/functional/doubles_test.sh` - Master template for mock/spy patterns +- `tests/functional/provider_test.sh` - Canonical data provider implementation +- `tests/functional/custom_asserts_test.sh` - Template for custom assertion testing +- `tests/acceptance/bashunit_test.sh` - Primary CLI testing patterns +- `tests/acceptance/mock_test.sh` - Critical mock cleanup verification patterns + +### Advanced patterns (study for complex features) +- `tests/unit/assert_snapshot_test.sh` - Complete snapshot testing patterns +- `tests/unit/skip_todo_test.sh` - Skip/todo functionality patterns +- `tests/unit/test_doubles_test.sh` - Advanced test double usage and edge cases +- `tests/acceptance/bashunit_fail_test.sh` - Error handling and output testing + +### Fixture and environment patterns +- `tests/acceptance/fixtures/.env.default` - Standard test environment +- `tests/functional/fixtures/` - External script and data patterns +- `tests/*/snapshots/` - Expected output storage patterns + +**Golden rule: If a pattern exists in the tests, use it exactly. Never invent new patterns when established ones exist.** + +--- + +## Quick reference checklist + +### Before starting any work: +1. βœ… **CREATE TASK FILE** `.tasks/YYYY-MM-DD-feature-title.md` (MANDATORY) +2. βœ… Study relevant test files from the mandatory list above +3. βœ… Read relevant ADRs in `adrs/` folder + +### During TDD cycle: +1. βœ… Create a list of all the test you plan to write in the task file +2. βœ… Prioritize tests based on the smallest next step to deliver value +3. βœ… Always write the first test prioritized +4. βœ… Write failing test following exact existing patterns +5. βœ… Verify test fails for the right reason +6. βœ… Implement minimal code to pass +7. βœ… Run full test suite to ensure no regressions +8. βœ… Refactor while keeping all tests green +9. βœ… Analyze if the test code can be improved if so do it +10. βœ… Analyze if the production code can be improved if so do it +11. βœ… Update test list and mark the test as done +12. βœ… Analyze if you are missing any tests if so add them to te list +13. βœ… Re-prioritize remaining tests +14. βœ… Update task file logbook with progress +15. βœ… Repeat until all tests are green and the test list is complete and the acceptance criteria are met + +### Before finishing: +1. βœ… All tests green (`./bashunit tests/`) +2. βœ… Linting clean (`shellcheck` + `shfmt`) +3. βœ… Production code actually used in application flow +4. βœ… Patterns match existing codebase exactly +5. βœ… Task file completed with timestamp +6. βœ… ADR created if architectural decision made + +--- + +## Pull Request Checklist + +Use this checklist before requesting review: + +- [ ] All tests green for the right reason (`./bashunit tests/`) +- [ ] Lint/format clean (`shellcheck -x $(find . -name "*.sh")` + `shfmt -w .`) +- [ ] Task file updated (acceptance criteria, test inventory, logbook, done timestamp) +- [ ] Docs/README updated; CHANGELOG updated if user-visible +- [ ] ADR added/updated if a decision was made +- [ ] **Two-way sync validated** between `AGENTS.md` and `.github/copilot-instructions.md` + +**Remember: This project has 40+ test files with established patterns. Always follow them exactly. NEVER proceed without creating a task file first.** diff --git a/.gitignore b/.gitignore index bd5d5b84..c2dcd455 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ report.html local/ tmp/ dev.log + +# ai +.tasks/* diff --git a/.tasks/.gitkeep b/.tasks/.gitkeep new file mode 100644 index 00000000..c413e988 --- /dev/null +++ b/.tasks/.gitkeep @@ -0,0 +1,3 @@ +# Tasks directory +This directory contains task files for tracking development work. + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..a084b0e5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,98 @@ +# AGENTS instructions + +**bashunit is a fast, portable Bash testing framework/library.** This guide complements (does not replace) `.github/copilot-instructions.md`. + +## Prime Directives + +- **TDD by default**: Red β†’ Green β†’ Refactor. Fail **for the right reason**. Implement the **smallest** code to pass. Refactor with all tests green. +- **Task file is mandatory**: Create **`./.tasks/YYYY-MM-DD-slug.md`** before any work; keep it updated (acceptance criteria, **test inventory**, current red bar, timestamped logbook). +- **Definition of Done** must be satisfied to finish. +- **Clarity rule**: If something is ambiguous, ask first; record answers in the task file. +- **ADRs**: Read existing ADRs first; for new decisions, create an ADR using the repo's template and match existing format. + +## Agent Workflow + +1) **Before coding** + - Create `./.tasks/YYYY-MM-DD-slug.md` with context, acceptance criteria, test inventory, current red bar, and a timestamped **Logbook**. + - Read `.github/copilot-instructions.md` + relevant ADRs; record links/assumptions in the task file. + - Create a list with all tests needed to cover acceptance criteria + - Add this list to the task file as a **test inventory** (unit, functional, acceptance). + - Prioritize tests by the smallest next step. + - Pick the first test to implement. + - For the testing approach, see the concise overview of the **TDD approach** in `.github/copilot-instructions.md` and keep this file concise. + +2) **Red** + - Add a test that fails for the intended reason, using **only existing patterns** from `./tests/**`. + +3) **Green** + - Implement the **minimal** change in `./src/**` to pass; update the Logbook. + +4) **Refactor** + - Improve code/tests incrementally while keeping all tests green. Update docs/ADR if behavior or decisions change. + - Use `shellcheck -x $(find . -name "*.sh")` and `shfmt -w .` to ensure lint/format compliance. + - Run the test suite with `./bashunit tests/` to ensure everything remains green. + - Run the linting/formatting checks again and ensure compliance. + - Evaluate if any existing tests can be removed or simplified due to refactoring; Or if new tests are needed to cover edge cases discovered during refactoring, add them to the test inventory in the task file. + - Update the task file's Logbook with details of the refactoring process, including any challenges faced and how they were addressed. + - if all the tests are green and the code is clean easy to read and maintain, pick the next test from the inventory and repeat steps 2-4 untill all tests in the inventory are done. and the acceptance criteria are met. + +5) **Quality Gate (pre-commit)** + - Run repo's real lint/format: `shellcheck -x $(find . -name "*.sh")` and `shfmt -w .` + - Run tests with `./bashunit tests/` (or scoped runs as appropriate). + +6) **Docs & ADR** + - Update `README`/docs when CLI/assertions/behavior changes. + - Add/update ADRs for significant decisions; link from the task file. + +7) **Finish (Definition of Done)** + - Linters/formatters **clean**. + - All tests **green for the right reason**. + - Acceptance criteria **met** in the task file. + - Docs/CHANGELOG updated when user-visible changes occur. + +## bashunit Guardrails + +- Use **only verified** features/patterns proven by `./src/**` and `./tests/**` (assertions, test doubles `mock`/`spy` + `assert_have_been_called*`, data providers, snapshots, skip/todo, globals like `temp_file`/`temp_dir`/`data_set`, lifecycle hooks). +- Prefer spies/mocks for time/OS/tooling; avoid depending on external binaries in unit tests. +- Don't break public API/CLI without semver + docs/CHANGELOG. +- No speculative work: every change starts from a failing test and explicit acceptance criteria. +- Isolation/cleanup: use `temp_file`/`temp_dir`; do not leak state across tests. + +## Tests & Patterns (usage, not code) + +Examples must mirror **real** patterns from `./tests/**` exactly: +- **Core assertions**: Study `tests/unit/assert_test.sh` for line continuation patterns and failure testing +- **Test doubles**: Study `tests/functional/doubles_test.sh` for mock/spy with fixtures +- **Data providers**: Study `tests/functional/provider_test.sh` for `@data_provider` syntax +- **Lifecycle hooks**: Study `tests/unit/setup_teardown_test.sh` for `set_up_before_script` patterns +- **CLI acceptance**: Study `tests/acceptance/bashunit_test.sh` for snapshot testing + +## Path-Scoped Guidance + +- `./src/**`: small, portable functions, namespaced; maintain Bash 3.2+ compatibility +- `./tests/**`: behavior-focused tests using official assertions/doubles; avoid networks/unverified tools +- `./.tasks/**`: one file per change (`YYYY-MM-DD-slug.md`); keep AC, test inventory, current red bar, and timestamped Logbook updated +- `./adrs/**`: read first; when adding, use template and match existing ADR style + +## Prohibitions + +- Don't invent commands or interfaces; extract from repo only. +- Don't change CI/report paths without explicit acceptance criteria and doc/test updates. +- Don't skip the task-file requirement; don't batch unrelated changes in one PR. + +## Two-Way Sync (mandatory) + +- When **`.github/copilot-instructions.md`** changes, **evaluate** whether the change belongs in `AGENTS.md` and **update `AGENTS.md`** to stay aligned. +- When **`AGENTS.md`** changes, **evaluate** whether the change belongs in `.github/copilot-instructions.md` and **update `.github/copilot-instructions.md`** to stay aligned. +- If a change is intentionally **not** mirrored, record the rationale in the active `./.tasks/YYYY-MM-DD-slug.md`. + +## PR Checklist + +- βœ… All tests green for the **right reason** +- βœ… Linters/formatters clean +- βœ… Task file updated (AC, test inventory, Logbook, Done timestamp) +- βœ… Docs/README updated; CHANGELOG updated if user-visible +- βœ… ADR added/updated if a decision was made +- βœ… **Two-way sync validated** (`AGENTS.md` ↔ `.github/copilot-instructions.md`) + +For complete details, patterns, and examples, see `.github/copilot-instructions.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e9beccf..4739c107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## Unreleased +- Add Project-wide `copilot-instructions.md` + - Add comprehensive TDD workflow with mandatory test-first approach (Red-Green-Refactor cycle) + - Add mandatory task file workflow in `.tasks/` directory with Template A (new capabilities) and Template B (modifications) + - Add private `.task/` directory to `.gitignore` for scratch notes and logs + - Add enhanced definition of done with TDD compliance, pattern compliance, and process requirements + - Add complete assertion catalog and testing patterns documentation based on existing codebase analysis + - Add logbook requirement for agent thought process tracking with timestamps + - Add strict workflow rules with no exceptions for task file creation on every change +- Add `AGENTS.md` for external developer tools integration +- Add two-way synchronization policy between `AGENTS.md` and `copilot-instructions.md` with automatic validation in task templates and PR checklist +- Add tasks storage policy clarifying `.tasks/` (versioned) vs `.task/` (private scratch, git-ignored) + ## [0.24.0](https://github.com/TypedDevs/bashunit/compare/0.23.0...0.24.0) - 2025-09-14 - Improve `assert_have_been_called_with` with strict argument matching diff --git a/README.md b/README.md index 225950ec..569ae497 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ MIT Software License + + Ask DeepWiki +


diff --git a/adrs/adr-005-copilot-instruction-or-spec-kit.md b/adrs/adr-005-copilot-instruction-or-spec-kit.md new file mode 100644 index 00000000..1321ab89 --- /dev/null +++ b/adrs/adr-005-copilot-instruction-or-spec-kit.md @@ -0,0 +1,71 @@ +# Choose Copilot Custom Instructions over Spec Kit for bashunit + +* Status: proposed +* Deciders: @khru +* Date: 2025-09-17 + +Technical Story: We need a lightweight, high leverage AI assist that improves contribution quality and speed without adding process overhead to a small Bash library. + +## Context and Problem Statement + +bashunit is a compact open source Bash testing library. We want AI assistance that nudges contributors toward consistent style, portability, and test structure. Two candidates exist: GitHub Copilot Custom Instructions and GitHub Spec Kit. Which approach best fits bashunit’s size and workflow? + +## Decision Drivers + +* Keep contributor workflow simple and fast +* Enforce consistent Bash and test conventions with minimal tooling +* Reduce review friction and style nitpicks +* Avoid heavy bootstrapping or new runtime dependencies +* Leave room to explore structured specs later if needed + +## Considered Options + +* Copilot Custom Instructions at repository scope +* Spec Kit as the core workflow +* Hybrid approach: Copilot now, Spec Kit only for large initiatives + +## Decision Outcome + +Chosen option: "Copilot Custom Instructions at repository scope", because it delivers immediate guidance in Chat, coding agent, and code review with near zero overhead, matches bashunit’s scale, and supports path specific rules for Bash and docs. Spec Kit is valuable for multi phase feature work but introduces extra setup and process that bashunit does not currently need. + +### Positive Consequences + +* Faster, more consistent PRs with fewer style and portability fixes +* Guidance lives in the repo, visible and versioned with code +* Path specific rules help tailor guidance for `lib/`, `tests/`, and docs + +### Negative Consequences + +* Possible conflicts with personal or organization instructions, require clear precedence awareness +* Preview features in Copilot instructions can change, we must monitor docs + +## Pros and Cons of the Options + +### Copilot Custom Instructions at repository scope + +* Good, because setup is trivial, just add `.github/copilot-instructions.md` and optional `.github/instructions/*.instructions.md` +* Good, because guidance is applied in Chat, coding agent, and code review where contributors already work +* Good, because path based `applyTo` rules let us enforce Bash portability and test naming in specific folders +* Bad, because it is not a full specification or planning framework if we ever need complex multi step delivery + +### Spec Kit as the core workflow + +* Good, because it structures specs, plans, and tasks for complex features and parallel exploration +* Good, because it can coordinate with multiple agents and make specifications executable +* Bad, because it adds Python and `uv` dependencies plus a new CLI and multi step process +* Bad, because that overhead is unnecessary for a small Bash library with simple APIs and docs + +### Hybrid approach + +* Good, because we keep the repo light while reserving Spec Kit for large, time boxed initiatives +* Good, because it lets us validate Spec Kit on a real feature without changing the whole workflow +* Bad, because it introduces two patterns to maintain if used frequently +* Bad, because contributors may be unsure when to use which process without clear guidance + +## Links + +* Spec Kit repository: [https://github.com/github/spec-kit](https://github.com/github/spec-kit) +* Spec Kit blog overview: [https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/](https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/) +* Copilot repository instructions: [https://docs.github.com/es/copilot/how-tos/configure-custom-instructions/add-repository-instructions](https://docs.github.com/es/copilot/how-tos/configure-custom-instructions/add-repository-instructions) +* Copilot personal instructions: [https://docs.github.com/es/copilot/how-tos/configure-custom-instructions/add-personal-instructions](https://docs.github.com/es/copilot/how-tos/configure-custom-instructions/add-personal-instructions) +* Copilot organization instructions: [https://docs.github.com/es/copilot/how-tos/configure-custom-instructions/add-organization-instructions](https://docs.github.com/es/copilot/how-tos/configure-custom-instructions/add-organization-instructions)