chore: add ESLint, Prettier, CI workflow, and comprehensive CLAUDE.md conventions

- ESLint flat config with typescript-eslint strict rules
- Prettier config (double quotes, semicolons, 2-space indent)
- GitHub Actions CI: lint + typecheck + test on PRs
- Cursor BugBot config (.cursor/BUGBOT.md)
- Comprehensive CLAUDE.md with code conventions, security rules,
  plugin patterns, and common mistakes to avoid
- Fix unused param lint error in plugin-registry.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Prateek 2026-02-13 18:01:52 +05:30
parent 5058c409d5
commit c8061ce03f
10 changed files with 1140 additions and 29 deletions

29
.cursor/BUGBOT.md Normal file
View File

@ -0,0 +1,29 @@
# BugBot Configuration
## Project Context
Agent Orchestrator is a TypeScript monorepo for managing parallel AI coding agents. It uses pnpm workspaces with packages under `packages/`.
## Tech Stack
- TypeScript (strict mode, ESM with `.js` extensions in imports)
- Node.js 20+ (use `node:` prefix for built-in modules)
- pnpm workspaces
- Next.js 15 (App Router) for web dashboard
- Commander.js for CLI
- vitest for testing
## Review Focus
- **Security**: Watch for command injection (especially in shell/tmux/git commands), AppleScript injection, GraphQL injection, unsanitized user input in API routes
- **Shell execution**: Prefer `execFile` over `exec` to avoid shell injection. Flag any use of `exec` or string concatenation in shell commands
- **Plugin pattern**: Plugins must export `{ manifest, create } satisfies PluginModule<T>` with types from `@agent-orchestrator/core`
- **Type safety**: Flag `as unknown as T` casts, unguarded `JSON.parse`, and type re-declarations that should import from core
- **Resource leaks**: Check for uncleared intervals/timeouts, uncleaned event listeners, missing `cancel()` on streams
- **ESM compliance**: Imports must use `.js` extension for local files, `node:` prefix for builtins
## Ignore
- `packages/web/src/lib/mock-data.ts` — temporary mock data, will be replaced
- `scripts/` — legacy bash scripts, not part of the TypeScript codebase
- `artifacts/` — design documents, not code

53
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,53 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm typecheck
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm test

3
.gitignore vendored
View File

@ -4,3 +4,6 @@ dist/
*.tsbuildinfo
.env
.env.local
coverage/
*.patch
*-context.md

6
.prettierignore Normal file
View File

@ -0,0 +1,6 @@
dist/
node_modules/
.next/
coverage/
*.tsbuildinfo
pnpm-lock.yaml

9
.prettierrc Normal file
View File

@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always"
}

137
CLAUDE.md
View File

@ -8,13 +8,15 @@ An open-source, agent-agnostic system for orchestrating parallel AI coding agent
## Tech Stack
- **Language**: TypeScript throughout (ESM, Node 20+)
- **Language**: TypeScript throughout (ESM, Node 20+, strict mode)
- **Monorepo**: pnpm workspaces
- **Web**: Next.js 15 (App Router) + Tailwind CSS
- **CLI**: Commander.js
- **Config**: YAML + Zod validation
- **Real-time**: Server-Sent Events
- **State**: Flat metadata files + JSONL event log
- **Linting**: ESLint (flat config) + Prettier
- **Testing**: vitest
## Architecture
@ -58,50 +60,132 @@ packages/
terminal-web/ — xterm.js web terminal
```
## Conventions
## Code Conventions
### TypeScript
- ESM modules (`"type": "module"`)
- Use `.js` extensions in imports (TypeScript ESM requirement): `import { foo } from "./bar.js"`
- Strict mode enabled
- Use `node:` prefix for Node.js builtins: `import { readFileSync } from "node:fs"`
### TypeScript (MUST follow)
- **ESM modules** — all packages use `"type": "module"`
- **`.js` extensions in imports** — required for ESM: `import { foo } from "./bar.js"`
- **`node:` prefix for builtins** — `import { readFileSync } from "node:fs"`, never bare `"fs"`
- **Strict mode**`"strict": true` in all tsconfig
- **`type` imports** — use `import type { Foo }` for type-only imports (enforced by ESLint)
- **No `any`** — use `unknown` and narrow with type guards. `any` is an ESLint error
- **No `as unknown as T` casts** — validate data instead of unsafe casting
- **Prefer `const`**`let` only when reassignment is needed, never `var`
- **Semicolons** — always use them
- **Double quotes** — for strings (enforced by Prettier)
- **2-space indentation** — (enforced by Prettier)
### Plugin Pattern (MUST follow)
Every plugin exports a `PluginModule` with type-safe `satisfies`:
### Plugin Structure
Every plugin exports a `PluginModule`:
```typescript
import type { PluginModule, Runtime } from "@agent-orchestrator/core";
export const manifest = {
const manifest = {
name: "tmux",
slot: "runtime" as const,
description: "Runtime plugin: tmux sessions",
version: "0.1.0",
};
export function create(): Runtime {
function create(): Runtime {
return {
name: "tmux",
// ... implement interface
// ... implement interface methods
};
}
export default { manifest, create } satisfies PluginModule<Runtime>;
```
### Shell Commands
When a plugin needs to run shell commands (git, tmux, gh, etc.), use `child_process.execFile` or `child_process.spawn` from `node:child_process`. Wrap them in async helpers.
**Do NOT** use `const plugin = { ... }; export default plugin;` — always use inline `satisfies` for compile-time type checking.
### Shell Command Execution (MUST follow)
- **Always use `execFile`** (or `spawn`) from `node:child_process` — NEVER `exec`
- `exec` runs through a shell and is vulnerable to injection
- `execFile` passes args as an array, bypassing shell interpretation
- **Always add timeouts**`{ timeout: 30_000 }` for external commands
- **Escape user-provided values** — never interpolate into command strings
- **Shell escaping**: Do NOT use `JSON.stringify` for shell escaping — it is not a shell escaping function
```typescript
// GOOD
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], { timeout: 30_000 });
// BAD — shell injection risk
import { exec } from "node:child_process";
exec(`git checkout ${branchName}`); // branchName could contain ; rm -rf /
```
### Error Handling
- Throw typed errors, don't return error codes
- Plugin methods should throw if they can't do their job
- The core services catch and handle plugin errors
- **Always wrap `JSON.parse`** in try/catch — corrupted metadata should not crash the system
- **Guard external data** — validate types from API/CLI/file inputs before using them
### Naming
- Files: `kebab-case.ts`
- Types/Interfaces: `PascalCase`
- Functions/variables: `camelCase`
- Constants: `UPPER_SNAKE_CASE` only for true constants (env vars, regex patterns)
- Plugin names: `kebab-case` matching directory name
- Test files: `*.test.ts` co-located with source, or in `__tests__/` directory
### Imports Order
1. Node builtins (`node:fs`, `node:path`, etc.)
2. External packages (`commander`, `chalk`, `yaml`, etc.)
3. Workspace packages (`@agent-orchestrator/core`)
4. Relative imports (`./foo.js`)
### Testing
- Use **vitest** for all tests
- Mock external dependencies (`child_process`, `fs`, HTTP calls)
- Co-locate test files: `src/foo.test.ts` or `src/__tests__/foo.test.ts`
- Test edge cases: corrupted data, missing files, timeout, concurrent access
## Building & Development
```bash
pnpm install # install all deps
pnpm build # build all packages
pnpm typecheck # typecheck all packages
pnpm lint # ESLint check
pnpm lint:fix # ESLint auto-fix
pnpm format # Prettier format
pnpm format:check # Prettier check (CI)
pnpm test # run all tests
```
### Before Committing
Always run lint and typecheck:
```bash
pnpm lint && pnpm typecheck
```
Fix any issues before pushing. CI will reject PRs that fail lint or typecheck.
## Config
### Config
- Config is loaded from `agent-orchestrator.yaml` (see `.yaml.example`)
- All paths support `~` expansion
- Per-project overrides for plugins and reactions
- Validated with Zod schema at load time
## Reference Implementation
The `scripts/` directory contains the original bash scripts that this TypeScript codebase replaces. Use them as specifications:
The `scripts/` directory contains the original bash scripts that this TypeScript codebase replaces. Use them as behavioral specifications:
| Script | What It Specifies |
|--------|------------------|
@ -115,14 +199,6 @@ The `scripts/` directory contains the original bash scripts that this TypeScript
| `claude-session-status` | Activity classification (working/idle/blocked) |
| `get-claude-session-info` | Agent introspection (session ID, summary extraction) |
## Building
```bash
pnpm install
pnpm build # build all packages
pnpm typecheck # typecheck all packages
```
## Key Design Decisions
1. **Stateless orchestrator** — no database, just flat metadata files + event log
@ -130,3 +206,16 @@ pnpm typecheck # typecheck all packages
3. **Push notifications** — the Notifier is the primary human interface, not the dashboard
4. **Two-tier event handling** — auto-handle routine issues (CI, reviews), notify human only when judgment is needed
5. **Backwards-compatible metadata** — flat key=value files matching the existing bash script format
6. **Security first**`execFile` not `exec`, validate all external input, escape strings for target context
## Common Mistakes to Avoid
- Using `exec` instead of `execFile` — security vulnerability
- Using `JSON.stringify` for shell escaping — does not escape `$`, backticks, `$()`
- Missing `.js` extension in local imports — will fail at runtime with ESM
- Using bare `"fs"` instead of `"node:fs"` — inconsistent, may break in edge cases
- Casting with `as unknown as T` — bypasses type safety, crashes on bad data
- `export default plugin` without `satisfies PluginModule<T>` — loses type checking
- Interpolating user input into shell commands, AppleScript, or GraphQL queries
- Forgetting to clean up setInterval/setTimeout on disconnect/destroy
- Using `on("exit")` instead of `once("exit")` for one-time handlers

80
eslint.config.js Normal file
View File

@ -0,0 +1,80 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier";
export default tseslint.config(
// Global ignores
{
ignores: [
"**/dist/**",
"**/node_modules/**",
"**/.next/**",
"**/coverage/**",
"packages/web/next.config.js",
"packages/web/postcss.config.mjs",
],
},
// Base JS rules
eslint.configs.recommended,
// TypeScript strict rules
...tseslint.configs.strict,
// Prettier compat (disables formatting rules)
eslintConfigPrettier,
// Project-wide rules
{
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
// Security: prevent shell injection patterns
"no-eval": "error",
"no-implied-eval": "error",
"no-new-func": "error",
// Code quality
"no-console": "warn",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-template-curly-in-string": "warn",
"prefer-const": "error",
"no-var": "error",
"eqeqeq": ["error", "always"],
// TypeScript
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/consistent-type-imports": [
"error",
{ prefer: "type-imports" },
],
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-require-imports": "error",
},
},
// Relaxed rules for test files
{
files: ["**/*.test.ts", "**/__tests__/**"],
rules: {
"no-console": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
},
},
// Relaxed rules for Next.js pages/components
{
files: ["packages/web/**/*.tsx", "packages/web/**/*.ts"],
rules: {
"no-console": "off", // Next.js uses console for server logs
},
}
);

View File

@ -8,11 +8,24 @@
"node": ">=20.0.0"
},
"packageManager": "pnpm@9.15.4",
"type": "module",
"scripts": {
"build": "pnpm -r build",
"dev": "pnpm --filter @agent-orchestrator/web dev",
"lint": "pnpm -r lint",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "pnpm -r typecheck",
"test": "pnpm -r --filter '!@agent-orchestrator/web' test",
"clean": "pnpm -r clean"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^25.2.3",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.1",
"typescript-eslint": "^8.55.0"
}
}

View File

@ -87,7 +87,7 @@ export function createPluginRegistry(): PluginRegistry {
}
},
async loadFromConfig(config: OrchestratorConfig): Promise<void> {
async loadFromConfig(_config: OrchestratorConfig): Promise<void> {
// First, load all built-ins
await this.loadBuiltins();

File diff suppressed because it is too large Load Diff