跪拜 Guibai
← Back to the summary

Scaffolding a Coding Agent Monorepo with npm Workspaces and Strict TypeScript

This article is the second in the "Developing a Coding Agent from Scratch" series, mainly introducing how to use npm to set up a monorepo project and manage multiple sub-projects. If you are also interested in Agent development, feel free to like, bookmark, and follow. Column: Developing a Coding Agent from Scratch - 东方小月's Column - Juejin

What is a monorepo project

A monorepo project manages multiple sub-projects within a single code repository. Each sub-project is an independent npm package, and they coordinate through npm's dependency management mechanism. In npm workspaces, local packages still declare dependencies via package.json; the difference is that during installation, npm links the dependency to the local workspace instead of downloading it from the npm registry. For example: the agent package needs to reference the ai package.

The root directory has declared:

{
  "workspaces": ["packages/*"]
}

packages/ai/package.json:

{
  "name": "@di-code/ai",
  "version": "0.0.0"
}

Declare in packages/agent/package.json:

{
  "name": "@di-code/agent",
  "version": "0.0.0",
  "dependencies": {
    "@di-code/ai": "0.0.0"
  }
}

Then install from the root: npm install

npm will create a local link similar to the following:

node_modules/@di-code/ai
    -> D:\pi\di-code\packages\ai

On Windows, this might actually be a junction (directory junction), but the effect is the same as a symbolic link, without copying the source code. Thus, in agent, you can import it like a regular npm package:

import type { Message } from "@di-code/ai";

Do not use cross-directory relative imports:

import type { Message } from "../../ai/src/types.js";

Environment and Directory

Next, we enter the actual development of the project. First, you need to check if your environment meets the requirements. The node version should be at least 22.19.0.

Create a new project directory. I chose the name di-code here, and initialize the root package.json:

{
	"name": "di-code",
	"version": "0.0.0",
	"private": true,
	"type": "module",
	"workspaces": ["packages/*"],
	"scripts": {
		"build": "npm run build --workspace @di-code/tui && npm run build --workspace @di-code/ai && npm run build --workspace @di-code/agent && npm run build --workspace @di-code/coding-agent",
		"check": "biome check . && tsc --noEmit -p tsconfig.json",
		"test": "npm run test --workspaces --if-present"
	},
	"devDependencies": {
		"@biomejs/biome": "2.3.5",
		"@types/node": "22.19.19",
		"typescript": "5.9.3",
		"vitest": "4.1.9"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

Key field descriptions:

Then create the packages directory to store all sub-projects. Inside this directory, create four sub-directories: ai, agent, coding-agent, tui. Create a src directory inside each sub-directory to store the source code.

TypeScript Configuration

Create a tsconfig.base.json file in the root directory:

{
	"compilerOptions": {
		"target": "ES2022",
		"lib": ["ES2022"],
		"module": "NodeNext",
		"moduleResolution": "NodeNext",
		"strict": true,
		"erasableSyntaxOnly": true,
		"verbatimModuleSyntax": true,
		"noEmitOnError": true,
		"declaration": true,
		"declarationMap": true,
		"sourceMap": true,
		"inlineSources": true,
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"skipLibCheck": true,
		"resolveJsonModule": true,
		"types": ["node"]
	}
}

Partial option descriptions:

Then write the TS configuration file tsconfig.json in the root directory:

{
	"extends": "./tsconfig.base.json",
	"compilerOptions": {
		"noEmit": true
	},
	"include": ["packages/*/src/**/*.ts", "packages/*/test/**/*.ts"],
	"exclude": ["**/node_modules/**", "**/dist/**"]
}

The responsibility of the root configuration is repository-wide checking, not building:

Writing the four workspace package.json files

Create package.json files in the four directories ai, agent, coding-agent, tui. Their scripts and export shapes remain consistent, with different names and descriptions.

packages/ai/package.json

{
	"name": "@di-code/ai",
	"version": "0.0.0",
	"description": "Provider-neutral AI contracts and adapters for di-code",
	"private": true,
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsc -p tsconfig.build.json",
		"test": "vitest run --passWithNoTests"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

packages/agent/package.json

{
	"name": "@di-code/agent",
	"version": "0.0.0",
	"description": "Core agent state and loop for di-code",
	"private": true,
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsc -p tsconfig.build.json",
		"test": "vitest run --passWithNoTests"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

packages/coding-agent/package.json

{
	"name": "@di-code/coding-agent",
	"version": "0.0.0",
	"description": "Coding agent product layer for di-code",
	"private": true,
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsc -p tsconfig.build.json",
		"test": "vitest run --passWithNoTests"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

packages/tui/package.json

{
	"name": "@di-code/tui",
	"version": "0.0.0",
	"description": "Reusable terminal UI library for di-code",
	"private": true,
	"type": "module",
	"main": "./dist/index.js",
	"types": "./dist/index.d.ts",
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"import": "./dist/index.js"
		}
	},
	"scripts": {
		"build": "tsc -p tsconfig.build.json",
		"test": "vitest run --passWithNoTests"
	},
	"engines": {
		"node": ">=22.19.0"
	}
}

Writing workspace build configurations

Create tsconfig.build.json with the same content in the following four locations:

packages\ai\tsconfig.build.json
packages\agent\tsconfig.build.json
packages\coding-agent\tsconfig.build.json
packages\tui\tsconfig.build.json

The content for each is:

{
	"extends": "../../tsconfig.base.json",
	"compilerOptions": {
		"rootDir": "./src",
		"outDir": "./dist"
	},
	"include": ["src/**/*.ts"],
	"exclude": ["node_modules", "dist", "test"]
}

Entry Creation

Write one line in each of the following four files:

packages\ai\src\index.ts
packages\agent\src\index.ts
packages\coding-agent\src\index.ts
packages\tui\src\index.ts

Content:

export {};

Configuring the Biome Formatting Tool

Biome is a code quality tool for JavaScript, TypeScript, JSON, and other files, simply put, it's ESLint + Prettier.

Write in di-code\biome.json:

{
	"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
	"files": {
		"includes": [
			"package.json",
			"biome.json",
			"tsconfig*.json",
			"packages/*/package.json",
			"packages/*/tsconfig*.json",
			"packages/*/src/**/*.ts",
			"packages/*/test/**/*.ts"
		]
	},
	"formatter": {
		"enabled": true,
		"formatWithErrors": false,
		"indentStyle": "tab",
		"lineWidth": 120
	},
	"linter": {
		"enabled": true,
		"rules": {
			"recommended": true
		}
	}
}

Writing .gitignore

Create a .gitignore in the root directory and write:

# Dependencies
node_modules/

# Build and test output
dist/
coverage/
*.tsbuildinfo

# Logs
*.log
npm-debug.log*

# Environment and credentials
.env
.env.*
!.env.example

# Editors and operating systems
.vscode/
.idea/
.DS_Store
Thumbs.db

None of these need to be committed to the code repository.

Installing Dependencies and Building

At this point, we have basically completed the basic monorepo configuration. Next, we can start installing dependencies and building the project.

npm install
npm run build

After the build is complete, we can see a dist directory under each package, containing the compiled code.

Finally, run a check to ensure code quality.

npm run check

If Biome only reports format differences, you can execute

npx biome check --write package.json tsconfig.base.json tsconfig.json biome.json packages

to fix them, then run npm run check again to ensure the format meets the requirements.

At this point, we have completed the basic monorepo configuration. The git branch for this chapter is Monorepo Initialization.