event4u-app/agent-config

test-driven-development

Use when implementing a feature, fixing a bug, or refactoring — write a failing test first, then the code — even if the user just says 'add this function' or 'fix this bug'.

93CollectingRuns scripts
See how to use itView GitHub source
npx skills add https://github.com/event4u-app/agent-config --skill "src/skills/test-driven-development"

Quick start

Start using it in three steps

Install it or open the source, trigger it with a clear task, then follow the source workflow.

1

Install the Skill

npx skills add https://github.com/event4u-app/agent-config --skill "src/skills/test-driven-development"
2

Describe the task

Use test-driven-development to help me with: [describe your task]. Before you begin, tell me what input you need, the steps you will follow, and the expected output.

3

Follow the workflow

No structured workflow was detected; follow the original SKILL.md below.

Continue to the workflow

Direct answers

Answers to review before you install

What is test-driven-development?

Use when implementing a feature, fixing a bug, or refactoring — write a failing test first, then the code — even if the user just says 'add this function' or 'fix this bug'.

Who should use test-driven-development?

It is most relevant to software development workflows. Confirm the original prerequisites before using it in production.

How do you install test-driven-development?

SkillSignal detected this source-specific command: npx skills add https://github.com/event4u-app/agent-config --skill "src/skills/test-driven-development". Inspect the repository and command before running it.

Which Agent platforms does it support?

The upstream source does not declare a dedicated Agent platform.

What permissions or risks should you review?

Static analysis detected exec-script signals. Review the cited source lines before installing; these signals are not a security audit.

What are the current evidence limits?

This page combines upstream documentation with deterministic repository, quality, and static-risk signals. It is not described as a manual test or security review.

SkillSignal brief

Decide whether it fits your work first

Use when implementing a feature, fixing a bug, or refactoring — write a failing test first, then the code — even if the user just says 'add this function' or 'fix this bug'.

Useful in these contexts

Core capabilities

TestingEngineeringOperations

Distilled from the source

Understand this Skill in one minute

About 9 min · 15 sections

When it is worth using

  1. Adding a new function, method, or behavior

  2. Fixing a bug (the bug needs a regression test before the fix)

  3. Refactoring a unit whose current behavior is unclear

  4. Any task where expected behavior can be expressed as an assertion

Examples and typical usage

  1. The failing test (file + test name) with captured failure output

  2. The minimum-code diff that makes it pass

  3. Captured green-run output

Limits and cautions

  1. it('works') — no behavior described

  2. One test covering "and/and/and" — split per behavior

  3. Test that reaches into private state instead of testing observable behavior

  4. Test that duplicates the production code's algorithm (tautology)

Repository stars
7
Repository forks
1
Quality
93/100
Source repository last pushed

Quality breakdown

Based on traceable docs and repository signals; stars are not treated as quality.

93/100
Documentation30/30
Specificity25/25
Maintenance20/20
Trust signals18/25
View original Skill.mdThis page is parsed directly from the repository SKILL.md without editorial rewriting. Collected: Jul 28, 2026 · about 9 min

test-driven-development

When to use

  • Adding a new function, method, or behavior
  • Fixing a bug (the bug needs a regression test before the fix)
  • Refactoring a unit whose current behavior is unclear
  • Any task where expected behavior can be expressed as an assertion

Do NOT use when:

  • Writing throwaway prototype or spike code explicitly marked as exploration
  • Generating boilerplate (migrations, config files, scaffolding)
  • Editing pure documentation (.md, AGENTS.md, README)
  • Working inside this agent-config package on skill/rule markdown

Goal

  • Drive implementation from a verified-failing test, not from the agent's belief that the code "should work".
  • Catch edge cases before they become production bugs.
  • Leave every change with a regression test that runs in CI.

Escalate to a SPARC-style 5-phase workflow when

Plain TDD (red → green → refactor) is the right size for most work. A small subset benefits from a gated 5-phase wrapper — Spec → Pseudocode → Architecture → Refine → Complete — where each gate produces a written artifact before the next phase runs.

Decision tree — escalate if any branch is true:

  • The ticket has AC count > 5 (the spec itself is non-trivial — write it down before testing it).
  • The change modifies a contract consumed by ≥1 other module (public API signature, persisted schema, event payload, queue message).
  • The change cuts across ≥3 modules / bounded contexts at once.

When escalating: drop the Spec artifact in agents/roadmaps/ (or the project's planning location), capture it as an ADR via adr-create when the decision is load-bearing, then run plain TDD inside each Refine cycle. Do not skip RED→GREEN inside a SPARC phase — the wrapper adds gates, not exemptions.

For everything else (single-AC ticket, leaf-module change, bug fix), stay on plain TDD — the section above.

The core discipline

1. Write ONE failing test that describes the desired behavior.
2. Run it. WATCH it fail for the right reason.
3. Write the MINIMUM production code to make it pass.
4. Run it again. Watch it pass.
5. Clean up (rename, deduplicate) while keeping the test green.

If step 2 is skipped, the test is not trusted — a test that has never failed proves nothing about the code under test.

Iron Law — delete-and-restart over keep-as-reference

WHEN UNTESTED CODE EXISTS AND A TEST IS NEEDED — DELETE THE CODE,
WRITE THE TEST, REIMPLEMENT. NEVER KEEP IT "AS REFERENCE".

Reading the existing implementation while writing its test is test-after-the-fact with extra steps. The 12-row anti-rationalization table that follows expands the most common ways this Iron Law gets talked-around. Externalized to testing-anti-patterns/process-anti-patterns.md to keep this skill under the 400-line sunset trigger.

Mode contracts — Goal / Activities / Forbidden / Output

The flow runs as four modes. Each Forbidden item names how a reviewer checks it from the diff — an unverifiable prohibition does not ship.

ModeGoalActivitiesForbidden (diff check)Output contract
DesignOne-sentence behavior + enumerated casesSteps 1–2No production code (diff touches no src/** production path) · no test bodies yetCase list (happy/boundary/error)
Test-RedA failing test that fails RIGHTSteps 3–4No production edits (diff = tests/** only) · test must fail at an assertion, not an import/setup error (failure output cites the assertion line)Failing test + its observed failure reason
ImplementMinimum code to greenSteps 5–6No test edits (no tests/** paths in Implement-phase diffs — changing the assertion to fit the code is the canonical violation; genuinely-wrong test → STOP and ask, never silently edit) · no scope beyond the one caseGreen run output
DebugFix a defect found later(re-enter at 3)No bugfix before a reproducing regression test exists (the fix commit contains a tests/** addition that fails without the fix)Regression test + fix, verified red→green

Mode inference on resume

Step 0 of any resume: if agents/runtime/state/HANDOFF.md exists, resume from its Mode + Contract-owed fields (see /agent-handoff file mode) instead of re-deriving. Otherwise infer the mode from observable state — never assume Design:

Observed stateResume in
No test for the target behaviorDesign
Test exists, currently failing at an assertionImplement
Test exists + passing, defect reportedDebug
Test failing at import/collection errorTest-Red (fix the test, not the code)

At every mode transition, one consent-checkpoint sentence (per ask-when-uncertain / autonomous-execution — no new mechanism): name the mode you are leaving, the output contract you hand over, and the mode you enter; under an autonomous mandate the sentence is stated, not asked.

Procedure

1. Identify the behavior to test

State in one sentence: "When X happens, the system should do Y."

If you cannot state it in one sentence, the scope is too big — split into multiple tests, each covering one sentence.

2. Enumerate the cases — discovery before writing (MANDATORY)

Before the first test is written, run the test-case-discovery funnel for the behavior: dimension scan → case synthesis → optional subagent cross-check → prioritization. Do not proceed to step 3 with fewer than the floor:

  • 1 happy + 1 boundary + 1 error case per behavior (+1 abuse case on security-relevant paths).
  • Cap at 5–8 cases per behavior; each must be able to fail for a distinct reason.
  • Trivial change (< 10 lines, pure refactor, no new behavior) → skip the funnel; 1 happy + 1 boundary case suffices.

Each case from the list then gets its own RED → GREEN cycle (steps 3–6). A behavior whose only test is the happy path is not done — it is the first item of an unfinished case list.

3. Write the failing test first

Write the smallest test that expresses the sentence from step 1.

  • One assertion per behavior (multiple assertions are OK only when they describe the same single behavior).
  • Real code paths, not mocks — mock only at I/O boundaries (HTTP, DB, time).
  • Use a descriptive name: it_rejects_empty_email, not test_email_1.

4. Run the test and watch it fail

Execute the single test (targeted, not the full suite):

# PHP/Pest
./vendor/bin/pest --filter=it_rejects_empty_email

# JS/Vitest
npx vitest run --testNamePattern "rejects empty email"

Required observations before proceeding:

  • The test fails (not errors).
  • The failure message matches what you expected (missing behavior, not typo).
  • If the test passes immediately → it does not test what you think. Fix the test, do not start writing production code.

5. Write minimum code to pass

Add just enough production code to make the test green. No extra features, no unrelated refactoring, no "while I'm here" cleanups.

If you feel the urge to add a parameter, edge case, or helper not covered by the current test — stop. That belongs in the next RED step, not this GREEN step.

6. Run again and watch it pass

Re-run the same targeted command. Required:

  • The new test passes.
  • No previously green tests have turned red.
  • Test output is clean (no new warnings, deprecations, or noise).

7. Refactor (only if green)

With all tests green, you may:

  • Rename variables, methods, files
  • Extract duplication into helpers
  • Tighten types

Do not add new behavior during refactor — that needs its own failing test first. Re-run tests after the refactor to confirm still-green.

8. Repeat for the next behavior

Back to step 1 with the next single-sentence behavior.

Output format

  1. The failing test (file + test name) with captured failure output
  2. The minimum-code diff that makes it pass
  3. Captured green-run output
  4. Any refactor diff (optional)

Anti-rationalizations

Twelve common rationalizations that fire before the test is written — plus the delete-and-restart Iron Law — live in testing-anti-patterns/process-anti-patterns.md. Read the table when:

  • You catch yourself thinking "I'll add the test after" — row 2.
  • You want to keep the code "as reference" while writing the test — row 5.
  • "CI is red, patch first, test later" — row 9.
  • "Follow-up PR will add the test" — row 12.

For mock-isolation failure modes (separate concern), see testing-anti-patterns.

Examples

Example A — PHP / Pest

// tests/Unit/EmailValidatorTest.php — RED
it('rejects empty email', function () {
    $result = (new EmailValidator())->validate('');
    expect($result->isValid())->toBeFalse();
    expect($result->error())->toBe('Email required');
});

Run: ./vendor/bin/pest --filter='rejects empty email' → fails (EmailValidator does not exist yet, or returns isValid()=true).

// app/Validators/EmailValidator.php — GREEN (minimum)
final class EmailValidator
{
    public function validate(string $email): EmailResult
    {
        if (trim($email) === '') {
            return EmailResult::invalid('Email required');
        }
        return EmailResult::valid();
    }
}

Run the filter again → passes. No additional rules (format, MX, length) until a next failing test drives them.

Example B — TypeScript / Vitest

// src/retry.test.ts — RED
import { retry } from './retry';

it('retries a failing operation up to 3 times', async () => {
  let attempts = 0;
  const op = async () => {
    attempts += 1;
    if (attempts < 3) throw new Error('transient');
    return 'ok';
  };
  await expect(retry(op)).resolves.toBe('ok');
  expect(attempts).toBe(3);
});

Run: npx vitest run --testNamePattern "retries a failing" → fails (retry is undefined).

// src/retry.ts — GREEN (minimum)
export async function retry<T>(op: () => Promise<T>): Promise<T> {
  let lastError: unknown;
  for (let i = 0; i < 3; i += 1) {
    try { return await op(); } catch (e) { lastError = e; }
  }
  throw lastError;
}

Run again → passes. Configurable attempt count, backoff, and jitter all wait for their own failing tests.

Gotchas

  • Running the full suite instead of a filtered test hides the RED→GREEN signal in noise. Always target first.
  • A test that passes on the very first run is not TDD — it was written against code that already exists.
  • expect() with three or four assertions on unrelated fields describes multiple behaviors. Split them.
  • Snapshot tests invert the discipline — they generate the expected value from the code. Only use snapshots where human-readable output is the contract (CLI output, SQL strings).
  • Mocking the thing under test (instead of its I/O) tests the mock, not the code.

Do NOT

  • Do NOT write or modify production code before the failing test exists and has been observed to fail
  • Do NOT stop after the happy-path test — work through the enumerated case list from step 2 (boundary, error, abuse where relevant)
  • Do NOT accept a test that never failed as evidence the code works
  • Do NOT bundle refactors into the GREEN step
  • Do NOT silence a flaky test — diagnose it, or delete it
  • Do NOT skip the targeted RED-run because "I just wrote it, I know it fails"

Anti-patterns

  • it('works') — no behavior described
  • One test covering "and/and/and" — split per behavior
  • Test that reaches into private state instead of testing observable behavior
  • Test that duplicates the production code's algorithm (tautology)

When to hand over to another skill

  • Enumerating what to test before writing (case matrix, dimension scan, subagent cross-check) → test-case-discovery
  • Project type-checker / linter / formatter (PHPStan, ECS, Rector for PHP — tsc / eslint / prettier for TS — ruff / mypy for Python) → quality-tools
  • Full Pest conventions and Laravel test helpers → pest-testing
  • Running tests inside Docker → tests-execute
  • Investigating why a test is failing for non-obvious reasons → systematic-debugging

Validation checklist

Before marking TDD work complete:

  • Every new behavior has a test
  • The case list from step 2 exists and meets the floor (1 happy + 1 boundary + 1 error per behavior; abuse case on security paths)
  • Every enumerated case is either tested or recorded as dropped with a one-line reason — no behavior ships happy-path-only
  • Each test was observed to fail first, with a matching failure message
  • The minimum code was written to turn each RED into GREEN
  • All targeted tests pass
  • No adjacent test has turned red
  • Test output is clean (no new warnings or deprecations)

See also developer-like-execution for the broader think → analyze → verify loop this skill plugs into.

Skill path
src/skills/test-driven-development/SKILL.md
Commit SHA
0adf49a8ae84
Repository license
MIT
Data collected