A record of major design and implementation choices in {{project_name}} — what was considered, what was chosen, and why.
Each decision:
- Is atomic — focused on one clear choice.
- Is rationale-driven — the “why” matters more than the “what.”
- Should be written as if explaining it to your future self — concise, readable, and honest.
- Includes Context, Options Considered, Decision, and Consequences.
For formatting guidelines, see the DECISIONS.md Style Guide.
After stitching source files into a single script, {{project_title}} needs to ensure the output meets quality standards — static checking, formatting, and import organization. The tool should support a flexible, priority-based system where users can configure which tools run in which order, with fallbacks when preferred tools aren't available.
| Option | Pros | Cons |
|---|---|---|
| Ruff only | ✅ Single tool for all categories ✅ Extremely fast ✅ Unified configuration |
|
| Ruff + Black + isort | ✅ Ruff as primary (fast, modern) ✅ Black as formatting fallback (widely adopted) ✅ isort as import fallback (mature, configurable) ✅ Supports teams with existing toolchains |
|
| Black + isort only | ✅ Established, widely-used tools ✅ No newer dependencies |
❌ Slower than Ruff ❌ Two separate tools instead of one unified solution |
| No post-processing | ✅ Simplest implementation ✅ No external dependencies |
❌ Users must manually format/check output ❌ Inconsistent output quality |
Support Ruff as the primary tool for all three categories (static checking, formatting, and import sorting), with Black and isort as fallback options.
The default configuration prioritizes Ruff for its speed and unified approach, but allows teams with existing Black or isort workflows to use those tools instead. This provides:
- Fast defaults — Ruff handles all three categories efficiently
- Flexibility — Users can override priorities to match their existing toolchains
- Graceful degradation — If Ruff isn't available, Black and isort can step in
The three categories (static_checker, formatter, import_sorter) run in order, and within each category, tools are tried in priority order until one succeeds. This ensures consistent output while respecting user preferences and tool availability.
DEC 11 — 2025-10-15 → revised 2025-10-31
Early in development, the project required a consistent and colorized logging system that worked seamlessly in both modular and single-file builds.
At the time, the built-in Python logging module seemed overkill for such a small utility — especially since the tool needed lightweight log-level control and minimal setup.
We initially built a custom logger to provide:
- Compact, dependency-free logging.
- Inline color formatting for terminals.
- Simpler test injection and patching for trace output.
This approach fit the project's early ethos of “small, inspectable, and standalone.”
| Option | Pros | Cons |
|---|---|---|
| Custom lightweight logger | ✅ Fully under our control ✅ Compact and easily embedded ✅ Works identically in single-file builds |
|
Standard Library logging |
✅ Mature and battle-tested ✅ Configurable handlers, filters, and levels ✅ Works natively with external libraries ✅ Simple integration with pytest and CLI flags |
|
Third-party libraries (e.g. loguru, rich.logging) |
✅ Rich formatting and features out-of-the-box | ❌ Adds runtime dependencies ❌ Conflicts with minimalism goal |
Implement a custom, lightweight logger tailored for the project.
It would provide clear output, colorized levels, and simple hooks for tracing (TRACE) without bringing in external dependencies or complex handler hierarchies.
This custom module fit our goals of portability and transparency, keeping the tool’s behavior explicit and easy to inspect.
As the codebase grew, the in-house logger expanded significantly — gaining configuration flags, test-time injection, and shims for different runtime modes.
It became increasingly difficult to test, maintain, and integrate with third-party tooling.
We also realized (belatedly) that the standard logging module already supports most of what we built manually — including level control, handler injection, and structured message formatting — all without external dependencies.
The custom logger was therefore deprecated and removed, and the project migrated fully to Python’s built-in logging system.
As the early ad-hoc merger script evolved into a tested module, we want to ensure the project remains easy to distribute in forms that best suits different users.
| Option | Pros | Cons | Tools |
|---|---|---|---|
| PyPI module (default) | ✅ Easy to maintain and install ✅ Supports imports and APIs |
❌ Requires installation and internet | poetry, pip |
| Single-file script | ✅ No install step ✅ Human-readable source ✅ Ideal for quick CLI use |
❌ Not importable ❌ Harder to maintain merger logic |
{{project_name}} |
Zipped module (.pyz) |
✅ Bundled, portable archive ✅ Maintains import semantics |
zipapp, shiv, pex |
|
| Executable bundlers | ✅ Fully portable binaries ✅ No Python install required |
❌ Platform-specific ❌ Not source-transparent |
PyInstaller, shiv, pex |
Adopt a three-tier distribution model:
- PyPI package — the canonical importable module with semantic versioning guarantees.
- Single-file script — a CLI build based on
astimport parsing. - Zipped module (
.pyz) — optional for future releases and easy to produce.
Each tier serves different users while sharing the same tested, modular codebase.
This does not rule out an executable bundle in the future.
The project required a lightweight, expressive testing framework compatible with modern Python and CI environments.
Testing should be easy to write, discover, and extend — without verbose boilerplate or heavy configuration.
The priority was to keep tests readable while supporting fixtures, parametrization, and integration with tools like coverage and tox.
| Tool | Pros | Cons |
|---|---|---|
Pytest |
✅ Simple test discovery (test_*.py)✅ Rich fixtures and parametrization ✅ Integrates with CI and coverage tools ✅ Large ecosystem and community |
|
unittest (stdlib) |
✅ Built into Python ✅ Familiar xUnit style |
❌ Verbose boilerplate ❌ Weak fixture system ❌ Slower iteration and less readable output |
Adopt Pytest as the primary testing framework.
It provides clean syntax, automatic discovery, and a thriving ecosystem — making it ideal for both quick unit tests and full integration suites.
Pytest’s concise, declarative style aligns with the project’s principle of clarity over ceremony, enabling contributors to write and run tests effortlessly across all supported Python versions.
Static typing improves maintainability and clarity across the codebase, but Python’s ecosystem offers multiple overlapping tools.
The goal was to balance developer ergonomics in VS Code with strict, automated checks in CI.
We wanted instant feedback during development and deeper, slower analysis during builds — without fragmenting the configuration.
| Tool | Pros | Cons |
|---|---|---|
Pylance |
✅ Deep integration with VS Code ✅ Fast, incremental type checking ✅ Excellent in-editor inference and documentation ✅ Minimal configuration (uses pyrightconfig.json or pyproject.toml) |
❌ IDE-only — cannot run in CI ❌ Limited control over advanced typing rules |
Pyright |
✅ CLI equivalent of Pylance ✅ Fast and scriptable for CI |
|
MyPy |
✅ Mature, standards-based type checker ✅ Detects deeper type inconsistencies ✅ Integrates easily into CI workflows |
|
| No static checking | ✅ Simplifies setup | ❌ No type enforcement; increased maintenance burden |
Adopt Pylance as the default IDE type checker for developers using VS Code, and MyPy as the canonical CI type checker.
Pylance offers immediate, contextual feedback during development through its deep VS Code integration, while MyPy provides comprehensive type analysis in automated checks.
This dual setup ensures fast iteration locally and rigorous verification in CI — complementing Ruff’s linting and formatting without overlapping responsibilities.
Future builds may experiment with pyright CLI to align IDE and CI checks under a single configuration, but for now, Pylance in the editor and MyPy in CI provide the best balance of speed, coverage, and reliability.
DEC 07 — 2025-10-10 → revised 2025-10-30
The project needed a consistent, automated style and linting toolchain to enforce quality without slowing down iteration.
Python’s ecosystem offers several specialized tools (black, isort, flake8, mypy, etc.), but managing them separately increases setup friction and configuration sprawl.
The goal was to find a fast, unified tool that covers linting, formatting, and import management from a single configuration.
| Tool | Pros | Cons |
|---|---|---|
Ruff |
✅ Extremely fast (Rust-based) ✅ Replaces multiple tools (lint, format, import sort) ✅ Single configuration in pyproject.toml✅ Compatible with Black-style formatting |
|
Black |
✅ Widely adopted ✅ Consistent formatting standard |
❌ Format-only — requires separate tools for linting and imports |
isort |
✅ Excellent import sorter ✅ Highly configurable |
❌ Separate config and step ❌ Slower and redundant when used with Ruff |
.editorconfig |
✅ Supported by most editors ✅ Defines consistent indentation, EOLs, and encoding ✅ Works across languages |
❌ Limited to basic formatting rules |
Adopt Ruff as the unified linting and formatting tool, complemented by EditorConfig for cross-editor baseline consistency.
Ruff’s speed, all-in-one scope, and pyproject.toml integration reduce the need for multiple Python-specific tools, while EditorConfig ensures consistent indentation, encoding, and newline behavior in any environment.
Together, they provide a lightweight, editor-agnostic foundation that enforces uniform style without excess configuration — aligning with the project’s “minimal moving parts” principle.
For a brief period, isort was integrated alongside Ruff to handle complex import merging, as the team was unaware that Ruff’s configuration already supported equivalent sorting behavior.
After confirming Ruff’s import management features, isort was removed, consolidating all style and linting functions under Ruff alone.
The project needs a single-source, reproducible setup covering dependency management, packaging, and development workflows.
The goal is to reduce moving parts — one configuration, one lockfile, one entrypoint.
| Tool | Pros | Cons |
|---|---|---|
Poetry |
✅ Unified pyproject.toml for dependencies and metadata✅ Built-in lockfile for reproducible builds ✅ Manages virtual environments automatically ✅ Extensible with plugins (e.g. poethepoet) for task automation |
|
pip + requirements.txt |
✅ Ubiquitous and simple ✅ Works with system Python or virtualenv |
❌ No lockfile by default ❌ Fragmented setup (requires separate tools for packaging and scripts) ❌ Harder to track metadata and extras |
pip-tools |
✅ Adds lockfile support to pip |
|
Manual venv + Makefile |
✅ Transparent and minimal | ❌ Scattered configuration ❌ Manual sync and version drift |
Adopt Poetry as the project’s canonical environment and dependency manager.
It provides a batteries-included workflow — unified configuration (pyproject.toml), reproducible installs (poetry.lock), isolated environments, and task automation via the poethepoet plugin instead of maintaining Makefiles.
This mirrors the familiar ergonomics of package.json + pnpm for developers coming from JavaScript ecosystems while preserving full Python portability.
The project needed a clear, inclusive standard of behavior for contributors and maintainers.
As the {{project_author}} ecosystem grows, shared norms for collaboration, respect, and conflict resolution become essential — especially for open projects that welcome community participation.
Rather than inventing custom language, the team wanted a widely recognized, well-maintained template that could be easily understood, translated, and enforced.
| Option | Pros | Cons |
|---|---|---|
| Contributor Covenant 3.0 | ✅ Industry-standard and widely adopted ✅ Legally sound and CC BY-SA 4.0 licensed ✅ Clearly defines expectations, reporting, and enforcement ✅ Includes inclusive language and repair-focused approach |
|
| Custom in-house code | ✅ Tailored tone and structure | ❌ Risk of omissions or unclear enforcement ❌ Higher maintenance burden |
| No formal code | ✅ Less administrative work | ❌ Unclear expectations ❌ Difficult to moderate conflicts fairly |
Adopt the Contributor Covenant 3.0 as the foundation for the project’s CODE_OF_CONDUCT.md, adapted for the Apathetic Tools community.
This provides a consistent, transparent behavioral framework while avoiding the overhead of authoring and maintaining a custom code.
It defines reporting, enforcement, and repair processes clearly, reinforcing the community’s emphasis on accountability and respect.
This version is lightly customized with local contact details and references to community moderation procedures, maintaining alignment with upstream guidance.
Following the choice of Python (see DEC 03), this project must define a minimum supported version balancing modern features, CI stability, and broad usability. The goal is to stay current without excluding common environments.
The latest Python version is 3.14.
| Version | Pros | Cons |
|---|---|---|
| 3.8+ | ✅ Works on older systems | ❌ Lacks modern typing (|, match, typing.Self) and adds maintenance overhead |
| 3.10+ | ✅ Matches Ubuntu 22.04 LTS (baseline CI) ✅ Includes modern syntax and typing features |
|
| 3.12+ | ✅ Latest stdlib and type system | ❌ Too new; excludes many CI and production environments |
Windows WSL typically runs Ubuntu 22.04 or 24.04 LTS.
| Platform | Default Python | Notes |
|---|---|---|
| Ubuntu 22.04 LTS | 3.10 | Minimum baseline |
| Ubuntu 24.04 LTS | 3.12 | Current CI default |
| macOS / Windows | 3.12 | User-installed or Store LTS |
GitHub Actions ubuntu-latest |
3.10 → 3.12 | Transition period coverage |
| Version | Status | Released | EOL |
|---|---|---|---|
| 3.14 | bugfix | 2025-10 | 2030-10 |
| 3.13 | bugfix | 2024-10 | 2029-10 |
| 3.12 | security | 2023-10 | 2028-10 |
| 3.11 | security | 2022-10 | 2027-10 |
| 3.10 | security | 2021-10 | 2026-10 |
| 3.9 | security | 2020-10 | 2025-10 |
| 3.8 | end of life | 2019-10-14 | 2024-10-07 |
Target Python 3.10 and newer as the supported baseline.
This version provides modern typing and syntax while staying compatible with Ubuntu 22.04 LTS — the lowest common denominator across CI and production systems.
The project aims to be a lightweight, dependency-free build tool that runs anywhere — Linux, macOS, Windows, or CI — without setup or compilation.
Compiled languages (e.g. Go, Rust) would require distributing multiple binaries and would prevent in-place auditing and modification.
Python 3, by contrast, is preinstalled or easily available on all major platforms, balancing universality and maintainability.
| Language | Pros | Cons |
|---|---|---|
| Python | ✅ Widely available ✅ No compile step ✅ Readable and introspectable |
|
| JavaScript / Node.js | ✅ Familiar to web developers | ❌ Not standard on all OSes ❌ Frequent version churn |
| Bash | ✅ Ubiquitous | ❌ Fragile for complex logic |
Implement the project in Python 3, targeting Python 3.10+ (see DEC 04).
Python provides zero-dependency execution, cross-platform reach, and transparent, editable source code, aligning with the project’s principle of clarity over complexity.
It allows users to run the tool immediately and understand it fully.
The performance trade-off compared to compiled binaries is acceptable for small workloads.
Future distributions may include .pyz or bundled binary releases as the project evolves.
This project is meant to be open, modifiable, and educational — a tool for human developers.
The ethics and legality of AI dataset collection are still evolving, and no reliable system for consent or attribution yet exists.
The project uses AI tools but distinguishes between using AI and being used by AI without consent.
- MIT License (standard) — simple and permissive, but allows unrestricted AI scraping.
- MIT + “No-AI Use” rider (MIT-aNOAI) — preserves openness while prohibiting dataset inclusion or model training; untested legally and not OSI-certified.
Adopt the MIT-aNOAI license — the standard MIT license plus an explicit clause banning AI/ML training or dataset inclusion. This keeps the project open for human collaboration while defining clear ethical boundaries.
While this may deter adopters requiring OSI-certified licenses, it can later be dual-licensed if consent-based frameworks emerge.
AI helped create this project but does not own it.
The license asserts consent as a prerequisite for training use — a small boundary while the wider ecosystem matures.
This project started as a small internal tool. Expanding it for public release required more documentation, CLI scaffolding, and testing than available time allowed.
AI tools (notably ChatGPT) offered a practical way to draft and refine code and documentation quickly, allowing maintainers to focus on design and correctness instead of boilerplate.
- Manual authoring — complete control but slow and repetitive.
- Static generators (pdoc, Sphinx) — good for APIs, poor for narrative docs.
- AI-assisted drafting — fast, flexible, and guided by human review.
Use AI-assisted authoring (e.g. ChatGPT) for documentation and boilerplate generation, with final edits and review by maintainers.
This balances speed and quality with limited human resources. Effort can shift from writing boilerplate to improving design and clarity.
AI use is disclosed in headers and footers as appropriate.
AI acts as a paid assistant, not a data harvester.
Its role is pragmatic and transparent — used within clear limits while the ecosystem matures.
Written following the Apathetic Decisions Style v1 and ADR, optimized for small, evolving projects.
This document records why we build things the way we do — not just what we built.
✨ AI was used to help draft language, formatting, and code — plus we just love em dashes.