ADR 0001: Runtime and Language — Node.js 20+ with TypeScript
| Field | Value |
|---|---|
| Status | Accepted |
| Date | Pre-alpha |
| Deciders | Project maintainers |
| Supersedes | — |
| Superseded by | — |
| Related ADRs | 0002 (Monorepo tooling), 0004 (Distribution) |
Summary
aidokit is built in TypeScript targeting Node.js 20 LTS or later. All packages publish as ESM with .d.ts types. There is no plan to support older Node versions, alternative runtimes (Deno, Bun), or compiled-binary distribution within v1.0.
Context
aidokit is a scaffolding CLI with a specific operational shape:
- Writes files into a project directory (engine directory, agent rules, docs, scripts)
- Reads project manifests (
package.json,pyproject.toml,Cargo.toml,go.mod) - Runs shell commands as part of postinstall (
chmod,bd init,claude mcp add, etc.) - Prompts interactively for adapter, conformance, stack, MCP selections
- Validates JSON schemas for task briefs, change summaries, reports
- Composes plugin packages (adapters, stack packs) from
node_modules - Makes zero LLM API calls — all model communication happens in the user's chosen AI coding CLI
Distribution must work cross-platform (macOS, Linux, Windows) with minimal friction. The target user already has Node.js installed in many cases (they use AI coding CLIs, which themselves often run on Node) and would invoke aidokit via npx.
There is also strong existing alignment:
- The v4 kit's hook scripts (already a major input to
aidokit) are written in Node.js - The emitted
.mjswatchdog scripts that adapters output continue to run in Node - The Beads CLI is Go but distributed via package managers including npm scripts
- The MCP server ecosystem is primarily Node.js + TypeScript
- The AI coding CLI ecosystem (Claude Code, Codex CLI, Aider, Cursor) is largely Node or has Node bindings
We need a runtime that:
- Aligns with the existing emitted-code stack (Node-based hooks)
- Has a mature ecosystem for CLI conveniences (arg parsing, prompts, formatting, validation)
- Distributes universally with minimal friction
- Has a large enough contributor pool to support an open-source project
- Supports a strongly-typed adapter contract (third-party adapters need real type checking)
- Works cross-platform without weeks of Windows-specific debugging
Decision
Runtime
- Node.js 20.x or later is the minimum supported version
- Node 22 (next LTS) MUST work without modification
- Node 18 is NOT supported — it reaches end-of-maintenance in April 2025 and lacks features we want (notably stable
node:test, fetch, web streams)
Language
- TypeScript 5.4 or later for all source code
- All packages compile to TypeScript declarations (
.d.ts) for downstream type checking - Test files use TypeScript directly via Vitest
Module format
- All packages publish as ESM (
"type": "module"inpackage.json) - Imports use the
.jsextension form (TypeScript'snodenextresolution) - No CommonJS dual-publishing — the ecosystem has converged on ESM and dual-publishing doubles build complexity for marginal benefit
Distribution
- Primary: npm, invoked via
npx aidokit(no global install required) - Secondary (post-v1.0, not committed): single-binary builds via Node SEA or
pkg
Type sharing
@aidokit/coreexports theAdapter,StackPack, and supporting types- Third-party adapter and stack-pack authors install
@aidokit/coreas a peer dependency and implement the interfaces in TypeScript - Adapter/stack-pack authors may write in plain JavaScript; the harness verifies runtime behavior regardless of source language
Consequences
Positive
- Zero-friction install:
npx aidokit initis the smallest possible barrier to entry - Ecosystem alignment: matches the runtime of every emitted hook script, every adapter we'll build, and most MCP servers
- Type safety across the contract surface: adapter and stack-pack authors get IDE autocomplete, type checking, and refactoring tools when implementing our interfaces
- Large contributor pool: JS/TS is the most-used language stack on GitHub; community contributions are easier
- Mature CLI tooling: commander/oclif,
@inquirer/prompts,picocolors,ora,ajv/zod,mathjs(we won't need this),fs-extra, etc. — everything we need exists and is stable - Cross-platform mostly-free: Node handles macOS/Linux/Windows path semantics, line endings, child process spawning, and TTY detection
- ESM-first is future-proof: the JavaScript ecosystem has converged; CommonJS-only is now the unusual choice
- Versioned via the package manifest:
engines: { node: ">=20" }inpackage.jsonproduces a clean error for users on older Node - Compilation cached by Turborepo: repeated builds are fast even with the TS compile step
Negative
- Startup time: Node CLIs start in ~100–300ms vs ~10–30ms for Go/Rust binaries. Acceptable for an interactive setup tool; would be unacceptable for a hot-loop tool.
- Node.js is a prereq: users who don't have Node installed must install it first. Mitigated by the prereq-check displaying install commands per OS.
- Bundle size: the npm install of
aidokitand its deps is ~30–60 MB. Acceptable for a developer tool; mitigated bynpxnot requiring permanent install. - ESM-only: consumers of our published packages MUST use ESM. Acceptable; CommonJS holdouts can use dynamic
import(). - Cross-language adapter authors must context-switch: a Python or Go developer wanting to write an adapter has to work in TypeScript. Mitigated by adapters being a thin shim — the heavy lifting (target-CLI integration) is mostly file emission, not language-dependent algorithms.
- TypeScript build step: adds latency to local dev. Mitigated by Turborepo caching and
tsx/tsc --watchfor development.
Neutral
- Single-binary distribution is deferred.
npxis sufficient for v1.0. If demand emerges, post-1.0 we revisit Node SEA, Bun-compiled binaries, or a Go rewrite of the CLI shell only (with@aidokit/corestill in TS as the spec engine). - Version pinning strategy: we pin to
>=20, not to a specific minor. Node minor versions of the same major are intended to be drop-in compatible; if a regression occurs we'll narrow the range. - TypeScript 5.x assumes a 1–2 year stable window. TS 6.0 may require minor adjustments; that's an acceptable cost.
Alternatives considered
Alternative 1: Go
Pros
- Single static binary, no runtime prerequisite
- Faster startup (~10ms vs ~150ms)
- Better cross-compilation story
- The Beads CLI (a key dependency of the emitted projects) is Go, so there's some alignment
Cons
- The MCP server ecosystem, the AI coding CLI ecosystem, the v4 hook scripts, and most existing tooling we'd integrate with are all Node-based
- Adapter authors would have to write Go to extend the system, OR we'd need a JSON-RPC bridge — adding significant complexity
- Smaller community of developers wanting to write CLI scaffolders in Go vs TS
- Go's type system doesn't lend itself to the kind of expressive interface contracts (
Adapter,StackPack) we need — generics in Go are deliberately limited - Cross-language type-sharing is harder; we'd need JSON Schema generated from Go structs to give third-party authors type definitions
Reason rejected: ecosystem alignment outweighs performance for an interactive setup tool. The 100ms startup difference is invisible to users running a one-shot bootstrap command; the ecosystem cost of going against the grain is significant and permanent.
Alternative 2: Rust
Pros
- Excellent performance and safety guarantees
- Single binary, fast startup
- Growing CLI ecosystem (
clap,dialoguer, etc.) - Modern type system
Cons
- High contributor barrier — far fewer Rust developers than JS/TS
- Even smaller alignment with the surrounding ecosystem than Go
- Build complexity (compile times) much higher
- Same adapter-author barrier as Go
- We don't need Rust's performance or safety guarantees for what
aidokitactually does
Reason rejected: the contributor barrier is too high for an open-source project that depends on community adapters and stack packs. Rust's strengths (memory safety, parallelism) are not what this tool needs.
Alternative 3: Python
Pros
- Familiar to the AI developer audience
- Strong scripting ergonomics
- Decent CLI library ecosystem (
click,typer)
Cons
- Cross-platform distribution is painful —
pipworks differently on every OS, virtual environments add a layer - Type system (mypy/pyright) is good but not enforced at the language level
- Startup time worse than Node (~200–500ms with imports)
- Most of our integrated tooling (MCP servers, AI coding CLIs, kit hooks) is in JS
- Windows support is historically rough
Reason rejected: distribution complexity is the deal-breaker. npx is universal in a way pip is not. Users would hit "wrong Python version" errors immediately.
Alternative 4: Bun
Pros
- Node-compatible API (drop-in for most cases)
- Single-binary builds out of the box
- Significantly faster startup than Node
Cons
- Smaller user base — not universally installed
- Still maturing (1.0 was 2023; rough edges remain in 2024–2026)
- Doesn't replace npm as the universal package manager for distribution
- Locking ourselves to Bun cuts contributors who only have Node
Reason rejected for v1.0: Bun is too new to be the primary target. We may add Bun as a supported runtime later (the code should mostly work) but Node is the primary. Revisit at v2.0.
Alternative 5: Deno
Pros
- TypeScript-native (no compile step)
- Modern, security-conscious design
- Standard library is well-curated
Cons
- Different package ecosystem (URL imports / JSR) — would isolate us from the npm ecosystem we need
- Far smaller user base than Node
- Distribution friction (Deno isn't preinstalled anywhere
npxworks)
Reason rejected: Deno is technically excellent but ecosystem-isolated. The cost of being outside npm is too high.
Alternative 6: Shell scripts (Bash + Node mix)
Like the v4 kit's hook scripts in some places.
Pros
- Zero build step
- Minimum dependencies
Cons
- Cross-platform is a nightmare (Windows has no Bash by default; PowerShell is different enough to require parallel scripts)
- No type safety
- No real ecosystem for the things we need (prompts, validation, manifest parsing)
- Doesn't scale beyond ~500 lines of code
Reason rejected: unmaintainable at the scale of aidokit. Bash is fine for the emitted hook scripts; not for the CLI itself.
Implementation notes
Required tooling
pnpmworkspace (locked in by ADR 0002)tscfor type checking and.d.tsgenerationtsuportshyfor ESM bundling (TBD via a follow-up implementation decision; not architectural)vitestas the test runnertsxfor running TypeScript directly during development
package.json baseline for every package
{
"name": "@aidokit/<package>",
"version": "0.1.0",
"type": "module",
"engines": {
"node": ">=20.0.0"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist", "README.md", "CHANGELOG.md"]
}
Imports
TypeScript with NodeNext resolution requires .js extension on imports even for .ts files:
// Correct
import { Adapter } from './contract.js';
// Incorrect (will break at runtime in ESM)
import { Adapter } from './contract';
Engines field enforcement
Every published package MUST include engines.node: ">=20.0.0". npm/pnpm emit a warning on install if the user's Node is older. CI runs against Node 20 and Node 22 to catch regressions.
Validation
This decision is validated when:
- [x] The team agrees that Node + TS alignment outweighs single-binary distribution at v1.0
- [ ] First successful
pnpm install && pnpm testagainst a Node 20 environment in CI - [ ] First successful
npx aidokit initfrom an empty directory (Phase 7 dogfood gate) - [ ] Adapter author can write a working adapter in TypeScript using the published
@aidokit/coretypes (Phase 4)
References
- Node.js LTS schedule: https://github.com/nodejs/release#release-schedule
- TypeScript release notes: https://devblogs.microsoft.com/typescript/
- ESM-only publishing guide (Sindre Sorhus): https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
- ADR 0002: Monorepo tooling (pnpm + Turborepo)
- ADR 0004: Distribution strategy (npm + npx)
ARCHITECTURE.md§2.2 — design principle: contracts as TypeScript interfaces