Skip to content

Tuulbelt Design Principles

These principles guide what gets built, how it's built, and what doesn't belong.

1. Single Problem Per Tool

Each tool solves one problem and solves it well.

Good: "Detect unreliable tests by running them N times" Bad: "Complete test framework with mocking, fixtures, and reporting"

If you're adding features and the scope is growing, it's time to split into a new tool.

2. Zero External Dependencies (Standard Library Only)

No npm install, no cargo add, no runtime dependency management required.

Exception: Development dependencies (TypeScript compiler, test runners) are okay if they don't ship with the tool.

Rationale: Tools that don't require dependency resolution outlive language trends. They're more portable, more maintainable, and less fragile.

3. Portable Interface

Tools communicate via:

  • CLI with flags and stdin/stdout
  • Files (plain text, JSON, binary)
  • Environment variables
  • Sockets (TCP, UDP)

NOT via:

  • Proprietary package APIs
  • Internal state management
  • Plugin systems
  • Configuration frameworks

Rationale: Interfaces that work across languages, shells, and systems are reusable.

4. Composable (Pipes, Not Plugins)

Tools should chain together via pipes:

bash
tool-a | tool-b | tool-c

NOT via:

  • Plugin systems
  • Hook APIs
  • Event listeners

Rationale: Composition over extension keeps tools independent.

5. Independently Cloneable

Each tool is its own GitHub repository.

Users should be able to:

bash
git clone https://github.com/tuulbelt/<tool-name>.git
cd <tool-name>
npm test && npm run build

Without needing the meta repo or any other tool.

6. Proven Implementation, Not Moonshots

Before building:

  • Problem must be real (you hit it, or it's documented in production)
  • Solution must have a clear implementation path
  • No "it would be cool if..." without proof it works
  • No "works 80% of the time" solutions

Red flags:

  • "I think this would be useful"
  • "It's like X but better" (without explaining why X is broken)
  • Complex state management or learning curve
  • Vague scope ("improve developer experience")

7. Documentation Over Configuration

If a tool needs explanation, document it. Don't add config complexity.

Good:

-f, --format <json|text>  Output format (default: json)

Bad:

--config my-config.json   # 50 keys, most optional

8. Fail Loudly, Recover Gracefully

  • Errors should be explicit and helpful
  • Stack traces should include context (file, line, operation)
  • Degradation should be obvious, not silent

9. Test Everything, Ship Nothing Untested

  • Unit tests for core logic
  • Integration tests for CLI behavior
  • Edge case coverage (empty input, malformed input, concurrent access)
  • Tests run in CI on every commit

10. Version Independently

Each tool has its own version and changelog. The meta repo has no version.

Use semantic versioning:

  • 0.1.0 — First release, unstable API
  • 1.0.0 — Stable API, production-ready
  • 2.0.0 — Breaking change

What Doesn't Belong in Tuulbelt

  • Frameworks (anything that tries to be your whole app)
  • Heavy abstractions (frameworks disguised as utilities)
  • Language-specific hacks (unless the problem is language-specific)
  • Vaporware (things that work 80% of the time)
  • Opinionated workflows (unless they're optional)
  • Things that require significant setup (complexity = fragility)

Examples of Tools That Fit

"Run tests N times, report flaky ones" (single problem, clear output) "Convert YAML to JSON" (single problem, standard I/O) "Manage concurrent test port allocation" (single problem, solves real issue) "Generate FFI boilerplate from C headers" (single problem, code generation)

Examples of Tools That Don't Fit

"Complete testing framework" (too broad) "Multi-format data transformer with plugin system" (framework) "Universal configuration management" (scope creep) "AI-powered development assistant" (vaporware) "Framework for building frameworks" (meta-framework)


Before Building a New Tool, Answer:

  1. What single problem does it solve?
  2. How would I test it end-to-end?
  3. Can it run without installing dependencies?
  4. Can it be used from any programming language?
  5. Have I hit this problem in real code?
  6. Is there a proven implementation path, or am I inventing something new?

If you can't answer clearly, wait. Revisit the idea later.

Released under the MIT License.