The Problem With Tool Fatigue
Every year brings a flood of "must-have" developer tools. Most of them solve problems we don't have, or solve real problems in ways that create new ones. At TwilightCore, we've learned to be skeptical — but a handful of tools genuinely reshaped how we build software in 2024. This isn't a listicle. These are the tools we actually kept after the honeymoon phase wore off.
Cursor: The Editor That Finally Delivered on AI Promises
We resisted AI-assisted editors for a long time. GitHub Copilot felt like autocomplete with delusions of grandeur — useful for boilerplate, but it interrupted flow more than it helped. Cursor changed that calculus.
The difference isn't the model — it's the interface. Cursor treats the entire codebase as context, and the Cmd+K inline editing workflow eliminated the copy-paste dance between editor and chat window.
Where It Actually Helps
The biggest wins weren't where we expected. Writing new features from scratch? Marginal improvement. But refactoring existing code, writing tests for legacy modules, and translating between languages — those tasks dropped from hours to minutes.
{
"rules": [
"Always use explicit return types on exported functions",
"Prefer const assertions over enum declarations",
"Use drizzle-orm patterns for database queries, never raw SQL strings",
"Error handling must use Result<T, E> pattern from @/lib/result",
"React components must use forwardRef when accepting className prop"
]
}Project-level rules matter more than prompts
We maintain a .cursorrules file in every repo. This single file improved Cursor's output quality more than any amount of prompt engineering. Be specific about your patterns — the model follows them religiously.
Team Adoption
We didn't mandate Cursor. We ran a two-week trial where three engineers used it on a real project. When they shipped a complex migration in half the estimated time, the rest of the team switched voluntarily. Forced adoption kills tools; demonstrated value sells them.
Turborepo: Monorepo Without the Monopain
We moved to a monorepo in Q1 and immediately regretted it. Build times ballooned, CI bills tripled, and developers spent more time waiting than coding. Turborepo fixed all three problems.
The remote cache alone justified the switch. When one engineer builds a package, every subsequent build across the team hits cache. Our average CI time dropped from 14 minutes to 3.
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"],
"cache": true
},
"lint": {
"dependsOn": ["^build"],
"cache": true
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"cache": false
},
"dev": {
"dependsOn": ["^build"],
"persistent": true,
"cache": false
}
}
}The Before and After
| Metric | Before Turborepo | After Turborepo |
|---|---|---|
| Average CI build | 14 min | 3.2 min |
| Local full build | 8 min | 45 sec (cached) |
| Monthly CI cost | ~$480 | ~$120 |
| Package dependency bugs | Weekly | Rare |
| New package scaffold time | 30 min | 2 min (generator) |
Biome: We Dropped ESLint and Prettier in the Same Week
This was the scariest change. ESLint and Prettier are load-bearing infrastructure in every JavaScript project. But our ESLint config had grown to 340 lines across 6 files, with 23 plugins and constant version conflicts. Biome replaced all of it with a single binary.
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExcessiveCognitiveComplexity": {
"level": "warn",
"options": { "maxAllowedComplexity": 15 }
}
},
"suspicious": {
"noExplicitAny": "error"
},
"style": {
"useConst": "error",
"noNonNullAssertion": "warn"
}
}
},
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
}
}The speed difference is absurd. Biome lints and formats our entire monorepo in under 200ms. The equivalent ESLint + Prettier pass took 18 seconds. That's not a benchmark — that's the difference between running on every keystroke and running only in CI.
Migration isn't painless
Biome doesn't support every ESLint rule. We lost a few niche rules around React hook dependency checking and import ordering preferences. For us, the tradeoff was worth it. Audit your ESLint rules before migrating — if you rely heavily on custom plugins, Biome might not be ready for you yet.
Linear: Project Management for People Who Build
We switched from Jira to Linear in March. The entire migration took a weekend. Every engineer on the team independently told us it was the single best process change we made all year.
Linear is opinionated and fast. There's no configuration rabbit hole, no marketplace of plugins, no Jira Query Language to learn. Cycles, projects, and issues — that's the model, and it maps cleanly to how we actually work.
The Integration That Sealed It
Linear's GitHub integration creates bidirectional links between issues and PRs. When we merge a PR that references FWD-142, the issue moves to "Done" automatically. No more status update theater in standups.
tRPC: End-to-End Type Safety Without the Ceremony
We were already a TypeScript shop, but the boundary between client and server was still a type-safety gap. tRPC closed it completely.
export const projectRouter = router({
list: publicProcedure
.input(z.object({
status: z.enum(["active", "archived", "draft"]).optional(),
limit: z.number().min(1).max(50).default(20),
cursor: z.string().uuid().optional(),
}))
.query(async ({ input, ctx }) => {
const projects = await ctx.db.query.projects.findMany({
where: input.status
? eq(schema.projects.status, input.status)
: undefined,
limit: input.limit + 1,
orderBy: [desc(schema.projects.updatedAt)],
...(input.cursor && {
where: lt(schema.projects.id, input.cursor),
}),
});
const hasMore = projects.length > input.limit;
return {
items: projects.slice(0, input.limit),
nextCursor: hasMore ? projects[input.limit - 1].id : null,
};
}),
});The client consumes this with full autocompletion and type checking — change a field name on the server, and the client shows errors immediately. No codegen step, no OpenAPI spec to maintain, no runtime validation mismatch.
What We Tried and Dropped
Honesty matters more than enthusiasm. Here's what didn't stick:
- Bun as a runtime — Fast, but the Node.js compatibility gaps bit us in production. We still use it as a package manager.
- Playwright for unit tests — Overkill. We kept it for E2E and went back to Vitest for everything else.
- Neon branching for preview deployments — Brilliant concept, but branch databases drifted from production schema in ways that masked real bugs.
The Meta-Lesson
The tools that stuck all share one trait: they reduced the gap between intention and execution. Not by adding features, but by removing friction. Every configuration file we deleted, every CI minute we saved, every type error we caught at compile time instead of production — that's compounding time returned to the team.
The best developer tools don't give you new things to do — they eliminate things you were already doing poorly. Evaluate tools by what they let you stop doing, not by their feature lists. If a tool requires more than a day of configuration to match your existing workflow, it's probably not the right tool.