From base
Scaffolds a new TypeScript project from scratch. Use when starting a new project, bootstrapping a codebase, or setting up a project from zero.
How this skill is triggered — by the user, by Claude, or both
Slash command
/base:baseThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Ask the user to describe what the project is about. Use their response to populate `<project-name>` and `<project-description>` in later steps.
Ask the user to describe what the project is about. Use their response to populate <project-name> and <project-description> in later steps.
bun add -d @biomejs/biome @types/bun @typescript/native-preview knip simple-git-hooks taze turbo ultracite vitest
{
"name": "<project-name>",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"lint": "biome check",
"types": "tsgo --build",
"test": "vitest run",
"unused": "knip",
"update": "taze --interactive"
},
"dependencies": {},
"devDependencies": {},
"simple-git-hooks": {
"pre-commit": "make validate"
},
"knip": {
"ignoreDependencies": [
"turbo"
],
"ignoreBinaries": [
"make"
]
},
"packageManager": "bun@<current-bun-version>"
}
Replace <current-bun-version> with the output of bun --version.
import { file, spawn } from "bun";
await installDependencies();
await installGitHooks();
await setupRemoteCache();
export async function installDependencies() {
await spawn(["bun", "install"]).exited;
console.log("Dependencies installed");
}
export async function installGitHooks() {
await spawn(["bunx", "simple-git-hooks"]).exited;
console.log("Git hooks installed");
}
export async function setupRemoteCache(isRetry?: boolean) {
const config = file(".turbo/config.json");
if (!((await config.exists()) && (await config.json()).teamId)) {
const stdio = isRetry ? "inherit" : "pipe";
const link = spawn(["turbo", "link"], { stdio: [stdio, stdio, stdio] });
if ((await link.exited) !== 0) {
const error = await new Response(link.stderr).text();
if (error.includes("User not found")) {
await spawn(["turbo", "login"]).exited;
await setupRemoteCache();
return;
}
if (error.includes("IO error")) {
await setupRemoteCache(true);
return;
}
}
}
console.log("Turbo remote cache configured");
}
import { beforeEach, describe, expect, test, vi } from "vitest";
const mockSpawn = vi.fn().mockReturnValue({
exited: Promise.resolve(0),
stderr: new Blob([""]),
});
const mockFile = vi.fn().mockReturnValue({
exists: () => Promise.resolve(false),
json: () => Promise.resolve({}),
});
vi.mock("bun", () => ({
spawn: (...args: unknown[]) => mockSpawn(...args),
file: (...args: unknown[]) => mockFile(...args),
}));
const { installDependencies, installGitHooks, setupRemoteCache } = await import("./setup");
function spawnReturns(exitCode: number, stderr = "") {
return mockSpawn.mockReturnValue({
exited: Promise.resolve(exitCode),
stderr: new Blob([stderr]),
});
}
function configReturns(exists: boolean, json: Record<string, unknown> = {}) {
mockFile.mockReturnValue({
exists: () => Promise.resolve(exists),
json: () => Promise.resolve(json),
});
}
beforeEach(() => {
mockSpawn.mockClear();
mockFile.mockClear();
spawnReturns(0);
configReturns(false);
});
describe("installDependencies", () => {
test("runs bun install", async () => {
await installDependencies();
expect(mockSpawn).toHaveBeenCalledWith(["bun", "install"]);
});
});
describe("installGitHooks", () => {
test("runs bunx simple-git-hooks", async () => {
await installGitHooks();
expect(mockSpawn).toHaveBeenCalledWith(["bunx", "simple-git-hooks"]);
});
});
describe("setupRemoteCache", () => {
test("skips linking when config already has teamId", async () => {
configReturns(true, { teamId: "team_123" });
mockSpawn.mockClear();
await setupRemoteCache();
expect(mockSpawn).not.toHaveBeenCalledWith(["turbo", "link"], expect.anything());
});
test("runs turbo link with piped stdio on first attempt", async () => {
await setupRemoteCache();
expect(mockSpawn).toHaveBeenCalledWith(["turbo", "link"], {
stdio: ["pipe", "pipe", "pipe"],
});
});
test("runs turbo login then retries on 'User not found' error", async () => {
mockSpawn
.mockReturnValueOnce({
exited: Promise.resolve(1),
stderr: new Blob(["User not found"]),
})
.mockReturnValueOnce({ exited: Promise.resolve(0) })
.mockReturnValueOnce({ exited: Promise.resolve(0) });
configReturns(false);
await setupRemoteCache();
expect(mockSpawn).toHaveBeenCalledWith(["turbo", "login"]);
});
test("retries with inherited stdio on 'IO error'", async () => {
mockSpawn
.mockReturnValueOnce({
exited: Promise.resolve(1),
stderr: new Blob(["IO error"]),
})
.mockReturnValueOnce({ exited: Promise.resolve(0) });
await setupRemoteCache();
expect(mockSpawn).toHaveBeenCalledWith(["turbo", "link"], {
stdio: ["inherit", "inherit", "inherit"],
});
});
});
setup:
bun run scripts/setup.ts
validate:
bun run turbo validate
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"lint": {},
"types": {},
"test": {},
"unused": {},
"validate": {
"dependsOn": ["lint", "types", "test", "unused"]
}
}
}
{
"compilerOptions": {
"allowImportingTsExtensions": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"isolatedModules": true,
"lib": ["esnext"],
"module": "esnext",
"moduleDetection": "force",
"moduleResolution": "bundler",
"noEmit": true,
"noUncheckedIndexedAccess": true,
"noUncheckedSideEffectImports": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext",
"verbatimModuleSyntax": false
},
"exclude": ["node_modules"],
"include": ["**/*.ts"]
}
{
"$schema": "node_modules/@biomejs/biome/configuration_schema.json",
"extends": ["ultracite/core"],
"formatter": {
"lineWidth": 100
},
"linter": {
"rules": {
"correctness": {
"noUnusedImports": "warn"
}
}
}
}
# base
*.local*
*.tsbuildinfo
.DS_Store
.turbo
node_modules
name: CI
on:
push:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
- name: Cache Bun dependencies
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install
- name: Validate
run: make validate
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
projects: [
{
extends: true,
test: {
name: "unit",
include: ["**/*.test.ts"],
environment: "node",
},
},
],
},
});
# <project-name>
<project-description>
## Tech Stack
- **Package manager:** Bun
- **Testing:** Vitest
## Conventions
<!-- Add project-specific conventions here as the codebase evolves -->
Then create a symlink so tools that look for CLAUDE.md find the same file:
ln -s AGENTS.md CLAUDE.md
# <project-name>
<project-description>
## Development
1. Clone this repo
2. Run `make setup`
## License
[MIT](LICENSE)
files:
- path: AGENTS.md
update_when:
- When changes in package.json alter the tech stack (not minor version bumps)
- When new learnings from a task would benefit future agents (conventions, corrections to avoid repeating mistakes)
package.json with correct name, scripts, simple-git-hooks, and knip config@biomejs/biome, @types/bun, @typescript/native-preview, knip, simple-git-hooks, taze, turbo, ultracite, vitest)Makefile with setup and validate commandsscripts/setup.ts with install, git hooks, and remote cache setupscripts/setup.test.ts with tests for setup functionsturbo.json with lint, types, test, unused, and validate taskstsconfig.jsonbiome.jsonc with ultracite preset.gitignore.github/workflows/ci.ymlvitest.config.tsAGENTS.md with tech stack, commands, and conventionsCLAUDE.md symlink to AGENTS.mdREADME.md.agents/commit.config.yml with AGENTS.md trackednpx claudepluginhub kvnwolf/devtools --plugin baseConfigures and runs TypeScript build tooling with Bun, tsgo, Vitest, Biome, and Turborepo. Covers package.json scripts, typechecking, testing, linting, formatting, and monorepo development.
Scaffolds projects with directory structures, configs, dependencies, linting, testing, CI/CD basics, and docs for frontend, backend, mobile, CLI, libraries, and monorepos.
Initializes new Python, Rust, or TypeScript projects interactively with git repo, GitHub workflows, pre-commit hooks, Makefile, and standard configs. Updates existing projects too.