description: Use Bun instead of Node.js, npm, pnpm, or vite. globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" alwaysApply: false#
Issue Tracking with bd (beads)#
IMPORTANT: This project uses bd (beads) for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods.
Why bd?#
- Dependency-aware: Track blockers and relationships between issues
- Git-friendly: Auto-syncs to JSONL for version control
- Agent-optimized: JSON output, ready work detection, discovered-from links
- Prevents duplicate tracking systems and confusion
Quick Start#
Check for ready work:
bd ready --json
Create new issues:
bd create "Issue title" -t bug|feature|task -p 0-4 --json
bd create "Issue title" -p 1 --deps discovered-from:bd-123 --json
bd create "Subtask" --parent <epic-id> --json # Hierarchical subtask (gets ID like epic-id.1)
Claim and update:
bd update bd-42 --status in_progress --json
bd update bd-42 --priority 1 --json
Complete work:
bd close bd-42 --reason "Completed" --json
Issue Types#
bug- Something brokenfeature- New functionalitytask- Work item (tests, docs, refactoring)epic- Large feature with subtaskschore- Maintenance (dependencies, tooling)
Priorities#
0- Critical (security, data loss, broken builds)1- High (major features, important bugs)2- Medium (default, nice-to-have)3- Low (polish, optimization)4- Backlog (future ideas)
Workflow for AI Agents#
- Check ready work:
bd readyshows unblocked issues - Claim your task:
bd update <id> --status in_progress - Work on it: Implement, test, document
- Discover new work? Create linked issue:
bd create "Found bug" -p 1 --deps discovered-from:<parent-id>
- Complete:
bd close <id> --reason "Done" - Commit together: Always commit the
.beads/issues.jsonlfile together with the code changes so issue state stays in sync with code state
Auto-Sync#
bd automatically syncs with git:
- Exports to
.beads/issues.jsonlafter changes (5s debounce) - Imports from JSONL when newer (e.g., after
git pull) - No manual export/import needed!
Managing AI-Generated Planning Documents#
AI assistants often create planning and design documents during development:
- PLAN.md, IMPLEMENTATION.md, ARCHITECTURE.md
- DESIGN.md, CODEBASE_SUMMARY.md, INTEGRATION_PLAN.md
- TESTING_GUIDE.md, TECHNICAL_DESIGN.md, and similar files
Best Practice: Use a dedicated directory for these ephemeral files
Recommended approach:
- Create a
history/directory in the project root - Store ALL AI-generated planning/design docs in
history/ - Keep the repository root clean and focused on permanent project files
- Only access
history/when explicitly asked to review past planning
CLI Help#
Run bd <command> --help to see all available flags for any command.
For example: bd create --help shows --parent, --deps, --assignee, etc.
Important Rules#
- Use bd for ALL task tracking
- Always use
--jsonflag for programmatic use - Link discovered work with
discovered-fromdependencies - Check
bd readybefore asking "what should I work on?" - Store AI planning docs in
history/directory - Run
bd <cmd> --helpto discover available flags - Do NOT create markdown TODO lists
- Do NOT use external issue trackers
- Do NOT duplicate tracking systems
- Do NOT clutter repo root with planning documents
Claude Models#
This project uses two Claude models:
Opus 4.5 (Main Agent)#
- Model ID:
claude-opus-4-5-20251101 - Letta handle:
openai/claude-opus-4-5-20251101(via LiteLLM proxy) - Used for: Main conversational agent, tool execution, user interactions
Haiku 4.5 (Detection)#
- Model ID:
claude-haiku-4-5-20251001 - Config:
HAIKU_MODELenvironment variable - Used for: Fast classification of user messages (overwhelm, brain dump, self-bullying)
- Called via LiteLLM at
LITELLM_URL
The detection flow in src/detect.ts:
- Regex pre-filter triggers Haiku only when patterns detected
- Haiku classifies:
overwhelm,brainDump,selfBullying,urgency - If brain dump detected, Haiku parses and saves tasks/ideas to DB
- Detection context prepended to message for Opus to respond appropriately
Do NOT use Sonnet unless explicitly requested.
Letta Agent Creation Workaround#
Due to a Letta bug with BYOK model handles, agents must be created in two steps:
- Create agent with
letta/letta-freemodel - Update agent's
llm_configto use Claude:await client.agents.update(agentId, { llm_config: { handle: 'openai/claude-opus-4-5-20251101', model: 'claude-opus-4-5-20251101', model_endpoint_type: 'openai', model_endpoint: 'http://litellm:4000', context_window: 200000, temperature: 0.7, }, });
Letta Tool Registration (Critical!)#
Custom tools require careful registration. Letta's "auto-extraction from Python source" is unreliable.
1. json_schema format - Use Letta's flat format, NOT OpenAI's nested format:
// WRONG (OpenAI format)
json_schema: {
type: 'function',
function: { name, description, parameters }
}
// CORRECT (Letta format)
json_schema: {
name: 'tool_name',
description: 'What it does',
parameters: {
type: 'object',
properties: { /* ... */ },
required: ['arg1']
}
}
2. Always pass json_schema explicitly - Both on create AND update:
// When creating
await client.tools.create({
source_code: pythonCode,
description: '...',
json_schema: { name, description, parameters }, // Required!
});
// When updating existing tools
await client.tools.update(toolId, {
source_code: pythonCode,
description: '...',
json_schema: { name, description, parameters }, // Also required!
});
3. Python function signatures - Use explicit typed parameters:
# WRONG - Letta won't know what args to pass
def my_tool(**kwargs):
pass
# CORRECT - Explicit parameters
def my_tool(text: str, priority: int = None):
pass
4. Tool attachment timing - Attach AFTER agent creation:
// Tools must be attached after agent is created
// (tool_ids in create() doesn't work with letta-free)
const agent = await client.agents.create({ /* ... */ });
for (const toolId of toolIds) {
await client.agents.tools.attach(toolId, { agent_id: agent.id });
}
5. Handle existing tools - Check by name and update instead of failing:
const existingTools = new Map<string, string>();
for await (const tool of client.tools.list()) {
existingTools.set(tool.name, tool.id);
}
// Then update if exists, create if not
LiteLLM tools=None Bug (Pinned Version)#
LiteLLM's main-latest image has a bug where filter_web_search_deployments throws a TypeError when Letta sends tools=null during summarization requests.
Symptom: Agent crashes during memory compaction/summarization with:
TypeError: argument of type 'NoneType' is not iterable
Fix: Pin to version v1.80.9.dev6 which includes the fix:
# docker-compose.yml
litellm:
# Using specific version that includes fix for tools=None bug
# https://github.com/BerriAI/litellm/commit/7c2e2111c0cc3372ca0ce911d0b6d45c22794d7f
image: ghcr.io/berriai/litellm:litellm_embedding_header_forwarding-v1.80.9.dev6
When to update: Once LiteLLM releases a stable version with this fix merged, update to that version.
Tiny Wins Tools#
The bot includes tools for tracking small accomplishments in src/tools/wins.ts:
| Tool | Purpose | Key Parameters |
|---|---|---|
record_tiny_win |
Log an accomplishment | content (required), category, magnitude |
delete_tiny_win |
Remove a mistaken entry | id (required) |
get_wins_by_day |
Query wins for a specific day | period ("today", "yesterday", "YYYY-MM-DD"), category |
get_wins_summary |
Get overview with streaks | days (1-30), category, limit |
Categories: task, habit, self_care, social, work, creative, other
Magnitudes: tiny, small, medium, big
Database: Wins are stored in the wins table with timestamps (createdAt).
Code Quality (MANDATORY)#
CRITICAL: All code changes MUST pass these checks before completion:
bun run check # Runs all checks: typecheck, lint, format, test
Individual Commands#
bun run typecheck # TypeScript strict checking with tsgo
bun run lint # ESLint with strict rules
bun run format:check # Prettier format verification
bun test # Run all tests
Auto-fix Commands#
bun run fix # Auto-fix lint and format issues
bun run lint:fix # Fix ESLint issues only
bun run format # Fix Prettier issues only
Subagent Handoff Protocol#
Before completing ANY task, subagents MUST:
- Run
bun run checkand ensure ALL checks pass - Fix any errors found (use
bun run fixfor auto-fixable issues) - Re-run
bun run checkto verify fixes - Only mark task complete when all checks pass
If checks fail and cannot be fixed:
- Document the specific errors in the task completion message
- Do NOT mark task as complete
- Escalate to orchestrator for resolution
Configuration Files#
tsconfig.json- Strict TypeScript (all strict flags enabled)eslint.config.js- ESLint flat config with typescript-eslint strict.prettierrc- Prettier (120 chars, semicolons, single quotes)
Bun Runtime#
Default to using Bun instead of Node.js.
- Use
bun <file>instead ofnode <file>orts-node <file> - Use
bun testinstead ofjestorvitest - Use
bun build <file.html|file.ts|file.css>instead ofwebpackoresbuild - Use
bun installinstead ofnpm installoryarn installorpnpm install - Use
bun run <script>instead ofnpm run <script>oryarn run <script>orpnpm run <script> - Use
bunx <package> <command>instead ofnpx <package> <command> - Bun automatically loads .env, so don't use dotenv.
APIs#
Bun.serve()supports WebSockets, HTTPS, and routes. Don't useexpress.bun:sqlitefor SQLite. Don't usebetter-sqlite3.Bun.redisfor Redis. Don't useioredis.Bun.sqlfor Postgres. Don't usepgorpostgres.js.WebSocketis built-in. Don't usews.- Prefer
Bun.fileovernode:fs's readFile/writeFile - Bun.$
lsinstead of execa.
Testing#
Use bun test to run tests.
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
Frontend#
Use HTML imports with Bun.serve(). Don't use vite. HTML imports fully support React, CSS, Tailwind.
Server:
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. <link> tags can point to stylesheets and Bun's CSS bundler will bundle.
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
With the following frontend.tsx:
import React from "react";
import { createRoot } from "react-dom/client";
// import .css files directly and it works
import './index.css';
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
Then, run index.ts
bun --hot ./index.ts
For more information, read the Bun API docs in node_modules/bun-types/docs/**.mdx.