* [AI] Add cross-platform hook keeping GitHub comments/issues in Chinese or pirate voice Adds a shared agent hook that requires anything an agent posts to GitHub (PR/issue comments, PR reviews, created issues) to be written in 简体中文 (preferred) or, failing that, a fun pirate voice — never plain English. - scripts/agent-hooks/github-comment-style.sh: shared guard. Reads tool_input.body/.title, allows CJK or pirate-flavoured text, blocks plain English via exit 2. Fails open on malformed payloads. - Wired for Claude (.claude/settings.json PreToolUse), Codex (.codex/config.toml PreToolUse) and Cursor (beforeMCPExecution adapter). - Documents the rule in AGENTS.md, the canonical pr-and-commit-rules.md and the Cursor rule so platforms without hook support apply it manually. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Use a slug for the release-note filename; document slug naming in AGENTS.md Release-note filenames don't need to be the PR number — a short descriptive slug works too (the PR link is resolved at release time). Rename the new note accordingly and note this in AGENTS.md's directory reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [autofix.ci] apply automated fixes * [AI] Auto-label PRs with Chinese descriptions as "ai spam" via CodeRabbit Add a CodeRabbit labeling instruction (auto_apply_labels is already on) that applies the "ai spam" label when a PR description contains Chinese/CJK characters — a strong signal of AI-generated spam for this English-language project. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Exempt CodeRabbit-addressed comments from the Chinese/pirate voice hook CodeRabbit parses its commands (@coderabbitai review/resolve/etc.) as plain English, so the github-comment-style guard now skips any comment mentioning @coderabbitai / @coderabbit instead of forcing it into Chinese or pirate. Documented the exception in the canonical and Cursor rule files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Key CodeRabbit voice exemption on authorship, not mentions The hook only ever runs on the agent's own outgoing comments, so CodeRabbit's own comments are already out of scope (it posts under its own identity). Drop the @coderabbitai mention bypass — a comment merely name-dropping the bot is still the agent's prose and must follow the Chinese/pirate rule, closing an easy plain-English bypass. Docs updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Pin CodeRabbit reviews to English CodeRabbit was reading AGENTS.md / pr-and-commit-rules.md (which ask contributors to comment in Chinese/pirate) and applying that to its own reviews, posting them in Chinese. Set `language: en-US` and `tone_instructions` so the bot's own reviews, summaries and replies stay in English; the Chinese/pirate rule is for contributor/agent comments, not CodeRabbit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Replace Chinese/pirate comment voice with a robot-emoji prefix Scrap the 简体中文/pirate voice scheme (and its CodeRabbit language override and Chinese-based "ai spam" label) in favour of one simple rule: every GitHub comment, review or issue an agent posts must be prefixed with 🤖. The shared guard now blocks any body — or, for issues, title — that doesn't start with the robot emoji; docs and per-platform wiring updated to match. .coderabbit.yaml is restored to its original state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Silence SC1007 false positive on CDPATH= cd in Cursor MCP adapter shellcheck flags the intentional `CDPATH= cd` empty-env prefix as SC1007 (mistaking it for an assignment). Add a scoped inline disable directive on that line; behaviour is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Fail closed on guard execution errors in Cursor MCP adapter The adapter previously allowed the call on any non-0/2 exit from github-comment-style.sh, so a broken or missing guard would silently disable enforcement. Deny on unexpected exits instead (matching guard-shell.sh), with a message that marks it as an execution problem rather than a real policy denial. The shared guard still fails open on malformed payloads (its own exit-0 choice). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Add hook requiring a blank PR template when agents create PRs New shared guard pr-template-blank.sh blocks mcp__github__create_pull_request unless the body is the repo's PR template, unmodified (cosmetic whitespace aside) — agents must leave it blank for the human, per AGENTS.md. Wired for Claude (PreToolUse), Codex (PreToolUse) and Cursor (the beforeMCPExecution adapter now dispatches per-tool to the right guard). Docs and a release note updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqu3eZi5djm2cPAEm3yVht * [AI] Simplify agent-hook guard scripts - github-comment-style.sh: fold the empty/whitespace-only case into the prefix check, dropping a redundant tr subshell per field. - pr-template-blank.sh: drop the dead CR strip in canon() (the trailing whitespace sub already removes a trailing carriage return). - before-mcp-github.sh: emit the constant allow payload with printf instead of spawning jq on the common (allow) path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [AI] Drop duplicated PR-template/robot-emoji rules now that hooks enforce them The PR-template-blank and GitHub-comment robot-emoji-prefix rules are enforced by cross-platform hooks, so the copies scattered across AGENTS.md and the Cursor rules file are redundant. Remove them and point at the canonical pr-and-commit-rules.md, which keeps the full rule (including the PR-template Chinese exception that no hook can enforce). Also delete the internal PR-template-guard release note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [AI] Drop "enforced by hooks" wording from the PR/comment rule docs State the rules plainly without the meta-commentary about hook enforcement. Keeps the actual rules (don't fill the PR template, prefix GitHub comments/reviews/issues with 🤖, the Chinese exception, and the your-own-comments-only note) intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [AI] Trim enforcement meta-commentary; shorten robot-emoji release note Drop the "isn't enforced automatically" / "handled by tooling" framing from AGENTS.md and pr-and-commit-rules.md (keeping the rule and the "apply it yourself" responsibility), and shorten the robot-emoji-prefix release note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [AI] Fail closed on unreadable payloads in the GitHub agent guards Align the new guards with the git-guard.sh / guard-shell.sh convention: fail closed when the hook payload can't be read, fail open only on a genuinely absent optional field. - github-comment-style.sh: block on invalid JSON / missing / non-object .tool_input; allow only an absent body/title within a valid object. - before-mcp-github.sh: deny on a jq parse failure / missing .tool_name instead of falling through to allow. - pr-template-blank.sh: an absent/null body now fails open (previously it normalized to "" and wrongly blocked PR creation); an empty-string body is still treated as a real non-template submission. - .codex/config.toml: describe the full guard scope (comment body + issue title) in the hook comment and statusMessage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
18 KiB
AGENTS.md - Guide for AI Agents Working with Actual Budget
This guide provides comprehensive information for AI agents (like Cursor) working with the Actual Budget codebase.
Project Overview
Actual Budget is a local-first personal finance tool written in TypeScript/JavaScript. It's 100% free and open-source with synchronization capabilities across devices.
- Repository: https://github.com/actualbudget/actual
- Community Docs: Documentation is part of the monorepo at
packages/docs/. Published at https://actualbudget.org/docs - License: MIT
- Primary Language: TypeScript (with React)
- Build System: Yarn 4 workspaces (monorepo)
Quick Start Commands
Essential Commands (Run from Root)
# Type checking (ALWAYS run before committing)
yarn typecheck
# Linting and formatting (with auto-fix)
yarn lint:fix
# Run all tests
yarn test
# Start development server (browser)
yarn start
# Start with sync server
yarn start:server-dev
# Start desktop app development
yarn start:desktop
Important Rules
- ALWAYS run yarn commands from the root directory - never run them in child workspaces
- Use
yarn workspace <workspace-name> run <command>for workspace-specific tasks - Tests run once and exit by default (using
vitest --run)
⚠️ PR titles must start with [AI]
Every pull request title must be prefixed with [AI] — you have to apply it
yourself. See PR and Commit Rules.
Task Orchestration with Lage
The project uses lage (a task runner for JavaScript monorepos) to efficiently run tests and other tasks across multiple workspaces:
- Parallel execution: Runs tests in parallel across workspaces for faster feedback
- Smart caching: Caches test results to skip unchanged packages (cached in
.lage/directory) - Dependency awareness: Understands workspace dependencies and execution order
- Continues on error: Uses
--continueflag to run all packages even if one fails
Lage Commands:
# Run all tests across all packages
yarn test # Equivalent to: lage test --continue
# Run tests without cache (for debugging/CI)
yarn test:debug # Equivalent to: lage test --no-cache --continue
Configuration is in lage.config.js at the project root.
Architecture & Package Structure
Core Packages
1. loot-core (packages/loot-core/)
The core application logic that runs on any platform.
-
Business logic, database operations, and calculations
-
Platform-agnostic code
-
Exports for both browser and node environments
-
Test commands:
# Run all loot-core tests yarn workspace @actual-app/core run test # Or run tests across all packages using lage yarn test
2. desktop-client (packages/desktop-client/ - aliased as @actual-app/web)
The React-based UI for web and desktop.
-
React components using functional programming patterns
-
E2E tests using Playwright
-
Vite for bundling
-
Commands:
# Development yarn workspace @actual-app/web start:browser # Build yarn workspace @actual-app/web build # E2E tests yarn workspace @actual-app/web e2e # Visual regression tests yarn workspace @actual-app/web vrt
3. desktop-electron (packages/desktop-electron/)
Electron wrapper for the desktop application.
- Window management and native OS integration
- E2E tests for Electron-specific features
4. api (packages/api/ - aliased as @actual-app/api)
Public API for programmatic access to Actual.
-
Node.js API
-
Designed for integrations and automation
-
Commands:
# Build yarn workspace @actual-app/api build # Run tests yarn workspace @actual-app/api test # Or use lage to run all tests yarn test
5. sync-server (packages/sync-server/ - aliased as @actual-app/sync-server)
Synchronization server for multi-device support.
- Express-based server
- Currently transitioning to TypeScript (mostly JavaScript)
- Commands:
yarn workspace @actual-app/sync-server start
6. component-library (packages/component-library/ - aliased as @actual-app/components)
Reusable React UI components.
- Shared components like Button, Input, Menu, etc.
- Theme system and design tokens
- Icons (375+ icons in SVG/TSX format)
7. crdt (packages/crdt/ - aliased as @actual-app/crdt)
CRDT (Conflict-free Replicated Data Type) implementation for data synchronization.
- Protocol buffers for serialization
- Core sync logic
8. plugins-service (packages/plugins-service/)
Service for handling plugins/extensions.
9. eslint-plugin-actual (packages/eslint-plugin-actual/)
Custom ESLint rules specific to Actual.
no-untranslated-strings: Enforces i18n usageprefer-trans-over-t: Prefers Trans component over t() functionprefer-logger-over-console: Enforces using logger instead of console inpackages/loot-core/typography: Typography rulesprefer-if-statement: Prefers explicit if statements
10. docs (packages/docs/)
Documentation website built with Docusaurus.
- Documentation is part of the monorepo
- Built with Docusaurus 3
- Commands:
yarn workspace docs start yarn workspace docs build yarn start:docs # From root
Development Workflow
1. Making Changes
When implementing changes:
- Read relevant files to understand current implementation
- Make focused, incremental changes
2. Testing Strategy
Unit Tests (Vitest)
The project uses lage for running tests across all workspaces efficiently.
# Run all tests across all packages (using lage)
yarn test
# Run tests without cache (for debugging)
yarn test:debug
# Run tests for a specific package
yarn workspace @actual-app/core run test
E2E Tests (Playwright)
# Run E2E tests for web
yarn e2e
# Desktop Electron E2E (includes full build)
yarn e2e:desktop
# Visual regression tests
yarn vrt
# Visual regression in Docker (consistent environment)
yarn vrt:docker
# Run E2E tests for a specific package
yarn workspace @actual-app/web e2e
Testing Best Practices:
- Minimize mocked dependencies - prefer real implementations
- Use descriptive test names
- Vitest globals are available:
describe,it,expect,beforeEach, etc. - For sync-server tests, globals are explicitly defined in config
3. Type Checking
TypeScript configuration uses:
- Incremental compilation
- Strict type checking with
typescript-strict-plugin. New files must be type-strict — don't add// @ts-strict-ignoreto a new file (existing files are grandfathered). - Platform-specific exports in
loot-core(node vs browser)
4. Internationalization (i18n)
Use the Trans component (and translated strings) for user-facing text.
Regenerate i18n files with yarn generate:i18n.
5. Financial Number Typography
Wrap standalone financial numbers with FinancialText (or styles.tnum where
wrapping isn't possible).
Code Style & Conventions
TypeScript Guidelines
Type Usage:
- Use TypeScript for all code; look for existing type definitions before adding new ones
- Prefer
satisfiesover type assertions (as,!) for narrowing
Naming:
- Use descriptive variable names with auxiliary verbs (e.g.,
isLoaded,hasError)
Code Structure:
- Functional and declarative programming patterns - avoid classes
- Use the
functionkeyword for pure functions - Prefer iteration and modularization over code duplication
- Structure files: exported component/page, helpers, static content, types
- Create new components in their own files
React Patterns:
- The project uses React Compiler (
babel-plugin-react-compiler) in the desktop-client. The compiler auto-memoizes component bodies, so you can omit manualuseCallback,useMemo, andReact.memowhen adding or refactoring code; prefer inline callbacks and values unless a stable identity is required by a non-compiled dependency. - Avoid unstable nested components
JSX Style:
- Declarative JSX, minimal and readable
- Avoid unnecessary curly braces in conditionals
- Use concise syntax for simple statements
- Prefer explicit expressions (
condition && <Component />)
Platform-Specific Code
- Use conditional exports in
loot-corefor platform-specific code; platform resolution happens at build time via package.json exports. Don't directly import another platform's modules (.api,.electron).
For commit and PR rules, see PR and Commit Rules.
File Structure Patterns
Typical Component File
import { type ComponentType } from 'react';
// ... other imports
type MyComponentProps = {
// Props definition
};
export function MyComponent({ prop1, prop2 }: MyComponentProps) {
// Component logic
return (
// JSX
);
}
Test File
import { describe, it, expect, beforeEach } from 'vitest';
// ... imports
describe('ComponentName', () => {
it('should behave as expected', () => {
// Test logic
expect(result).toBe(expected);
});
});
Important Directories & Files
Configuration Files
/package.json- Root workspace configuration, scripts/lage.config.js- Lage task runner configuration/.oxlintrc.json- Lint rules (oxlint);/.oxfmtrc.json- formatting (oxfmt)/.nano-staged.json- pre-commit format/lint config (run via Husky)/.claude/settings.json,/.codex/config.toml,/.cursor/hooks.json- agent hook wiring; shared scripts live in/scripts/agent-hooks//tsconfig.json- Root TypeScript configuration/.cursorignore,/.gitignore- Ignored files/yarn.lock- Dependency lockfile (Yarn 4)
Documentation
/README.md- Project overview/CONTRIBUTING.md- Points to community docs/upcoming-release-notes/- Release notes for next version. Name each file with a short, descriptive slug (e.g.add-payee-autocomplete.md) — the PR link is resolved automatically at release time, so you don't need the PR number. Numeric filenames like1234.mdalso remain valid. See the release-note template and rules inpackages/docs/docs/contributing/index.md./CODEOWNERS- Code ownership definitions/packages/docs/- Documentation website (Docusaurus)
Build Artifacts (Don't Edit)
packages/*/lib-dist/- Built outputpackages/*/dist/- Built outputpackages/*/build/- Built outputpackages/desktop-client/playwright-report/- Test reportspackages/desktop-client/test-results/- Test results.lage/- Lage task runner cache (improves test performance)
Key Source Directories
packages/loot-core/src/client/- Client-side core logicpackages/loot-core/src/server/- Server-side core logicpackages/loot-core/src/shared/- Shared utilitiespackages/loot-core/src/types/- Type definitionspackages/desktop-client/src/components/- React componentspackages/desktop-client/src/hooks/- Custom React hookspackages/desktop-client/e2e/- End-to-end testspackages/component-library/src/- Reusable componentspackages/component-library/src/icons/- Icon components (auto-generated, don't edit)packages/docs/docs/- Documentation source files (Markdown)packages/docs/docs/contributing/- Developer documentation
Common Development Tasks
Running Specific Tests
# Run all tests across all packages (recommended)
yarn test
# E2E test for a specific file
yarn workspace @actual-app/web run playwright test accounts.test.ts --browser=chromium
Building for Production
# Browser build
yarn build:browser
# Desktop build
yarn build:desktop
# API build
yarn build:api
# Sync server build
yarn build:server
Type Checking Specific Packages
TypeScript uses project references. Run yarn typecheck from root to check all packages.
Debugging Tests
# Run tests in debug mode (without parallelization)
yarn test:debug
# Run specific E2E test with headed browser
yarn workspace @actual-app/web run playwright test --headed --debug accounts.test.ts
Working with Icons
Icons in packages/component-library/src/icons/ are auto-generated. Don't manually edit them.
Troubleshooting
Type Errors
- Run
yarn typecheckto see all type errors - Check if types are imported correctly
- Look for existing type definitions in
packages/loot-core/src/types/ - Use
satisfiesinstead ofasfor type narrowing
Linter Errors
Run yarn lint to check. All rules — including the custom actual/* rules
(no-untranslated-strings, prefer-trans-over-t, prefer-logger-over-console,
typography, …) — are defined in .oxlintrc.json.
Test Failures
- Check if test is running in correct environment (node vs web)
- For Vitest: check
vitest.config.tsorvitest.web.config.ts - For Playwright: check
playwright.config.ts - Ensure mock minimization - prefer real implementations
- Lage cache issues: Clear cache with
rm -rf .lageif tests behave unexpectedly - Tests continue on error: With
--continueflag, all packages run even if one fails
Import Resolution Issues
- Check
tsconfig.jsonfor path mappings - Check package.json
exportsfield (especially for loot-core) - Verify platform-specific imports (
.electron,.api) - Use absolute imports in
desktop-client
Build Failures
- Clean build artifacts:
rm -rf packages/*/dist packages/*/lib-dist packages/*/build - Reinstall dependencies:
yarn install - Check Node.js version (requires >=22)
- Check Yarn version (requires ^4.9.1)
Testing Patterns
Unit Tests
- Located alongside source files or in
__tests__directories - Use
.test.ts,.test.tsx,.spec.jsextensions - Vitest is the test runner
- Minimize mocking - prefer real implementations
E2E Tests
- Located in
packages/desktop-client/e2e/ - Use Playwright test runner
- Visual regression snapshots in
*-snapshots/directories - Page models in
e2e/page-models/for reusable page interactions - Mobile tests have
.mobile.test.tssuffix
Visual Regression Tests (VRT)
- Snapshots stored per test file in
*-snapshots/directories - Use Docker for consistent environment:
yarn vrt:docker
Additional Resources
- Community Documentation: https://actualbudget.org/docs/contributing/
- Discord Community: https://discord.gg/pRYNYr4W5A
- GitHub Issues: https://github.com/actualbudget/actual/issues
- Feature Requests: Label "needs votes" sorted by reactions
Code Quality Checklist
Before committing changes, ensure:
- Commit and PR rules followed (see PR and Commit Rules)
- Platform-specific code uses proper exports
Pull Request Guidelines
See PR and Commit Rules for complete PR creation rules, including title prefix requirements, labeling, the GitHub comment/review/issue 🤖 prefix, and PR template handling.
Code Review Guidelines
When performing code reviews (especially for LLM agents): see CODE_REVIEW_GUIDELINES.md for specific guidelines.
Performance Considerations
- Bundle Size: Check with rollup-plugin-visualizer
- Type Checking: Uses incremental compilation
- Testing: Tests run in parallel by default
- Linting: ESLint caches results for faster subsequent runs
Workspace Commands Reference
# List all workspaces
yarn workspaces list
# Run command in specific workspace
yarn workspace <workspace-name> run <command>
# Run command in all workspaces
yarn workspaces foreach --all run <command>
# Install production dependencies only (for server deployment)
yarn install:server
Environment Requirements
- Node.js: >=22
- Yarn: ^4.9.1 (managed by packageManager field)
- Browser Targets: Electron >= 35.0, modern browsers (see browserslist)
Migration Notes
The codebase is actively being migrated:
- JavaScript → TypeScript: sync-server is in progress
- Classes → Functions: Prefer functional patterns
- React.* → Named Imports: Legacy React.* patterns being removed
When working with older code, follow the newer patterns described in this guide.
Cursor Cloud specific instructions
Services overview
| Service | Command | Port | Required |
|---|---|---|---|
| Web Frontend (Vite) | yarn start |
3001 | Yes |
| Sync Server | yarn start:server-dev |
5006 | Optional (sync features only) |
All storage is SQLite (file-based via better-sqlite3). No external databases or services are needed.
Running the app
yarn startbuilds the plugins-service worker, loot-core browser backend, and starts the Vite dev server on port 3001.yarn start:server-devstarts both the sync server (port 5006) and the web frontend together.- The Vite HMR dev server serves many unbundled modules. In constrained environments, the browser may hit
ERR_INSUFFICIENT_RESOURCES. If that happens, useyarn build:browserfollowed by serving the built output frompackages/desktop-client/build/with proper COOP/COEP headers (Cross-Origin-Opener-Policy: same-origin,Cross-Origin-Embedder-Policy: require-corp).
Lint, test, typecheck
Standard commands documented in package.json scripts and the Quick Start section above:
yarn lint/yarn lint:fix(uses oxlint + oxfmt)yarn test(lage across all workspaces)yarn typecheck(tsgo + lage typecheck)
Testing and previewing the app
When running the app for manual testing or demos, use "View demo" on the initial setup screen (after selecting "Don't use a server"). This creates a test budget pre-populated with realistic sample data (accounts, transactions, categories, and budgeted amounts), which is far more useful than starting with an empty budget.
Gotchas
- The
enginesfield requires Node.js >=22 and Yarn ^4.9.1. The.nvmrcspecifiesv22/*. - Pre-commit hook runs
nano-staged(oxfmt + oxlint, configured in.nano-staged.json) via Husky. Runyarn prepareonce after install to set up hooks. - Lage caches test results in
.lage/. If tests behave unexpectedly, clear withrm -rf .lage. - Native modules (
better-sqlite3,bcrypt) require build tools (gcc,make,python3). These are pre-installed in the Cloud VM. - All yarn commands must be run from the repository root, never from child workspaces.