- Added upfront diff parsing and filtering in code review command with automatic reviewer agent count recommendation. - Added fsync call before closing session writer to ensure data durability. - Replaced Bun.Glob-based gitignore scanning with fd command execution in find tool. - Fixed hasOverrides logic to only set true when parsed.servers has entries in LSP config. - Replaced console.log/error/warn calls with structured logger across session manager and LSP modules.
8.8 KiB
Development Rules
Default Context
This repo contains multiple packages, but packages/coding-agent/ is the primary focus. Unless otherwise specified, assume work refers to this package.
Terminology: When the user says "agent" or asks "why is agent doing X", they mean the coding-agent package implementation, not you (the assistant). The coding-agent is a CLI tool that uses Claude—questions about its behavior refer to the code in packages/coding-agent/, not your current session.
Code Quality
- No
anytypes unless absolutely necessary - Check node_modules for external API type definitions instead of guessing
- NEVER use inline imports - no
await import("./foo.js"), noimport("pkg").Typein type positions, no dynamic imports for types. Always use standard top-level imports. - NEVER remove or downgrade code to fix type errors from outdated dependencies; upgrade the dependency instead
- Always ask before removing functionality or code that appears to be intentional
Bun Over Node
This project uses Bun as its runtime. Always prefer Bun APIs over Node.js equivalents.
Process Spawning
// GOOD: Bun.spawn with ReadableStream API
import type { Subprocess } from "bun";
const proc: Subprocess = Bun.spawn(["cmd", ...args], {
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
});
// Read stdout
const reader = (proc.stdout as ReadableStream<Uint8Array>).getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// process Buffer.from(value)
}
const exitCode = await proc.exited;
// BAD: Node child_process
import { spawn } from "node:child_process";
const child = spawn("cmd", args);
child.stdout.on("data", (chunk) => { ... });
child.on("close", (code) => { ... });
Sync Process Execution
// GOOD: Bun.spawnSync
const result = Bun.spawnSync(["cmd", ...args], {
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
});
if (result.exitCode === 0) { ... }
// BAD: Node execSync/spawnSync
import { spawnSync } from "node:child_process";
File I/O
// GOOD: Bun.file and Bun.write
const content = await Bun.file("path.txt").text();
const binary = await Bun.file("image.png").arrayBuffer();
const exists = await Bun.file("maybe.txt").exists();
await Bun.write("path.txt", content);
// BAD: Node fs
import { readFileSync, writeFileSync, existsSync } from "node:fs";
Scripts and package.json
// GOOD: Use bun in scripts
{
"scripts": {
"start": "bun run src/index.ts",
"test": "bun test",
"check": "bunx tsc --noEmit"
}
}
// BAD: Use node/npm/npx
{
"scripts": {
"start": "node dist/index.js",
"test": "npm test",
"check": "npx tsc --noEmit"
}
}
Running Commands
# GOOD
bun run script.ts
bun test
bunx tsc --noEmit
bun install
# BAD
node script.js
npm test
npx tsc --noEmit
npm install
Type Imports for Bun APIs
// Import Bun types when needed
import type { Subprocess } from "bun";
// Bun globals are available without import
Bun.spawn(...)
Bun.file(...)
Bun.write(...)
Bun.stdin.stream()
Casting Bun Subprocess Streams
Bun's Subprocess.stdout/stderr types can be number | ReadableStream | undefined. Cast when using pipe mode:
const child = Bun.spawn(["cmd"], { stdout: "pipe", stderr: "pipe" });
const stdoutReader = (child.stdout as ReadableStream<Uint8Array>).getReader();
const stderrReader = (child.stderr as ReadableStream<Uint8Array>).getReader();
Binary Existence Checks
// GOOD: Bun.which (cross-platform)
const gitPath = Bun.which("git");
if (!gitPath) throw new Error("git not found");
// BAD: Spawning which/where (platform-specific)
Bun.spawnSync(["which", "git"]); // Unix only
Bun.spawnSync(["where", "git"]); // Windows only
Path Resolution
// GOOD: import.meta (Bun ESM)
const thisDir = import.meta.dir; // equivalent to __dirname
const thisFile = import.meta.path; // equivalent to __filename
// BAD: fileURLToPath dance
import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Crypto
// GOOD: Web Crypto (standard) or Bun.hash
const uuid = crypto.randomUUID();
const bytes = crypto.getRandomValues(new Uint8Array(32));
const hash = Bun.hash("sha256", data);
// BAD: Node crypto
import { randomBytes, randomUUID } from "node:crypto";
Environment Variables
Bun auto-loads .env files. Do NOT import dotenv.
// GOOD: Direct access
const apiKey = process.env.API_KEY;
// BAD: dotenv
import dotenv from "dotenv";
dotenv.config();
HTTP Servers
// GOOD: Bun.serve
const server = Bun.serve({
port: 3000,
fetch(req) {
return new Response("OK");
},
});
// later: server.stop();
// BAD: Node http
import http from "node:http";
const server = http.createServer((req, res) => { ... });
HTTP Requests
// GOOD: Native fetch (built into Bun)
const response = await fetch("https://api.example.com/data");
const json = await response.json();
// BAD: External packages
import fetch from "node-fetch";
import axios from "axios";
Password Hashing
// GOOD: Bun.password (bcrypt/argon2 built-in)
const hash = await Bun.password.hash("password", "bcrypt");
const valid = await Bun.password.verify("password", hash);
// BAD: External packages
import bcrypt from "bcrypt";
import argon2 from "argon2";
Watch Mode
# GOOD: Bun built-in watch/hot reload
bun --watch src/index.ts # Restart on changes
bun --hot src/index.ts # Hot reload without restart
# BAD: External tools
npx nodemon src/index.ts
npx ts-node-dev src/index.ts
SQLite
// GOOD: Bun built-in SQLite
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");
db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)");
// BAD: External packages
import Database from "better-sqlite3";
Testing
# GOOD: Bun built-in test runner
bun test
# BAD: External test runners
npx jest
npx vitest
npx mocha
Logging
NEVER use console.log, console.error, or console.warn in the coding-agent package. Console output corrupts the TUI rendering.
Use the centralized logger instead:
import { logger } from "../core/logger";
logger.error("MCP request failed", { url, method });
logger.warn("Theme file invalid, using fallback", { path });
logger.debug("LSP fallback triggered", { reason });
Logs go to ~/.omp/logs/omp.YYYY-MM-DD.log with automatic rotation.
Commands
- After code changes:
bun run check(runs biome + tsc, get full output, no tail) - For auto-fixable lint issues:
bun run fix(includes unsafe fixes) - NEVER run:
bun run dev,bun testunless user instructs - Only run specific tests if user instructs:
bun test test/specific.test.ts - NEVER commit unless user asks
- Do NOT use
tscornpx tsc- always usebun run check
GitHub Issues
When reading issues:
- Always read all comments on the issue
When creating issues:
- Use standard GitHub labels (bug, enhancement, documentation, etc.)
- If an issue affects a specific package, mention it in the issue title or description
When closing issues via commit:
- Include
fixes #<number>orcloses #<number>in the commit message - This automatically closes the issue when the commit is merged
Tools
- GitHub CLI for issues/PRs
- TUI interaction: use tmux
Style
- Keep answers short and concise
- No emojis in commits, issues, PR comments, or code
- No fluff or cheerful filler text
- Technical prose only, be kind but direct (e.g., "Thanks @user" not "Thanks so much @user!")
Changelog
Location: packages/*/CHANGELOG.md (each package has its own)
Format
Use these sections under ## [Unreleased]:
### Added- New features### Changed- Changes to existing functionality### Fixed- Bug fixes### Removed- Removed features### Breaking Changes- API changes requiring migration (appears first if present)
Rules
- New entries ALWAYS go under
## [Unreleased]section - NEVER modify already-released version sections (e.g.,
## [0.12.2]) - Each version section is immutable once released
Attribution
- Internal changes (from issues):
Fixed foo bar ([#123](https://github.com/can1357/oh-my-pi/issues/123)) - External contributions:
Added feature X ([#456](https://github.com/can1357/oh-my-pi/pull/456) by [@username](https://github.com/username))
Releasing
-
Update CHANGELOGs: Ensure all changes since last release are documented in the
[Unreleased]section of each affected package's CHANGELOG.md -
Run release script:
bun run release
The script handles: version bump, CHANGELOG finalization, commit, tag, publish, and adding new [Unreleased] sections.