| description | Use Bun instead of Node.js, npm, pnpm, or vite. |
|---|---|
| globs | *.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json |
| alwaysApply | false |
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.
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.
Use bun test to run tests.
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});TypeScript (strict). Runtimes: Bun 1.3+, Node 23. Prettier: 2 spaces, no semicolons, double quotes, width 100. Import order: @ianvs/prettier-plugin-sort-imports + Tailwind plugin. ESLint (flat): any disallowed; unused vars warned (prefix _ to ignore). Naming: React components PascalCase; files kebab-case (e.g., user-profile.ts); packages @repo/. Comments: avoid writing inline comments everywhere, unless absolutely necessary for a todo or an important thing to take note of. Instead write tsdoc style block level comments at the method/class/function/route level. Focus on comments that provide value in regards to better type inference and clarity of usage. Using things like params/returns/etc..
Always default to useing named paramaters in functions eg myFunction({ ctx, other }) vs myFunction(ctx, other) Never use any types and always default to leveraging generics and smart types to sensure the best possible tpye inference across the project.
This project uses hack tickets (extension: dance.hack.tickets).
Common commands:
- Create:
hack x tickets create --title "..." --body-stdin(pipe long context) - List:
hack x tickets list - Show:
hack x tickets show T-00001 - Status:
hack x tickets status T-00001 in_progress - Sync:
hack x tickets sync
Data lives in .hack/tickets/ and syncs to branch hack/tickets by default.
When ending a work session, you MUST complete ALL steps below.
MANDATORY WORKFLOW:
- File tickets for remaining work - Create tickets for anything that needs follow-up
- Run quality gates (if code changed) - Tests, linters, builds
- Update issue status - Close finished work, update in-progress items
- Hand off - Provide context for next session
Use hack as the single interface for local runtime orchestration (compose, DNS/TLS, logs, sessions).
Operating rules:
- Prefer
hackover rawdocker/docker composefor project workflows. - Do not start/stop services from Docker Desktop UI for
hack-managed projects. - Treat
.hack/.internaland.hack/.branchas hack-managed artifacts; do not hand-edit generated files there. - Use MCP only when shell access is unavailable.
- If runtime state looks wrong, run
hack doctor, thenhack doctor --fixbefore manual repair.
Core objects:
- Project: a repo with
.hack/config + compose file. - Service: a compose service (e.g. api, web, worker).
- Instance: a running project; branch instances are separate copies started with
--branch.
Config + schema:
- Project config:
.hack/hack.config.json - Global config:
~/.hack/hack.config.json - Schema URL:
https://schemas.hack/hack.config.schema.json - Prefer CLI writes:
hack config get <path>,hack config set <path> <value>,hack config set --global <path> <value>
Hostname routing + Caddy labels:
- Primary host comes from
dev_host(default:<project>.hack). - Subdomain pattern is
<sub>.<dev_host>(for example:api.myapp.hack). - OAuth alias (when enabled) also routes
<dev_host>.<tld>and<sub>.<dev_host>.<tld>(default tld:gy). - Not every compose service is routable: only services with Caddy labels and on
hack-devare exposed. - Required labels for HTTP services:
caddy,caddy.reverse_proxy,caddy.tls=internal. - Quick checks:
hack open,hack open <sub>,hack open --json.
TLS + valid-hostname constraints:
hackuses Caddy internal PKI for HTTPS on routed hosts; trust CA withhack global trust..hackis local-first and great for dev, but it is not a public suffix.- Use OAuth alias hosts (for example
*.hack.gy) when providers require public-suffix-style callback domains. - Alias hosts are still local-dev routes unless you add an external tunnel/remote ingress path.
Project files (managed vs generated):
- Source-of-truth files:
.hack/docker-compose.yml,.hack/hack.config.json,.hack/hack.env.json(if env contract is used). - Local-only files:
.hack/.envand.hack/.internal/(runtime/local machine state; keep gitignored). - Generated (do not hand-edit):
.hack/.internal/compose.override.yml,.hack/.internal/compose.env.override.yml,.hack/.branch/compose.<branch>.override.yml. - Managed via CLI:
.hack/.internal/extra-hosts.json(usehack internal extra-hosts ...commands). - Lifecycle runtime files:
.hack/.internal/lifecycle/state.json,.hack/.internal/lifecycle/*.log.
Advanced networking (extra_hosts + local proxies/tunnels):
- Static host mappings: set
internal.extra_hostsin.hack/hack.config.json. - Dynamic host mappings:
hack internal extra-hosts set <hostname> <target>/unset/list. - For host-local proxies/tunnels, prefer
host-gatewayas target when possible. - After mapping changes or proxy IP churn:
hack restartand thenhack doctor.
Standard workflow:
- If
.hack/is missing:hack init - Start services:
hack up --detach(orhack up -d) - Check status:
hack psorhack status - Open app URL:
hack open --json - Restart:
hack restart - Stop services:
hack down
Logs (default is compose):
- Fast tail:
hack logs --pretty - Per-service tail:
hack logs <service> - Machine snapshot:
hack logs --json --no-follow - Loki history/query:
hack logs --loki --since 2h --prettyorhack logs --loki --query '{project="<name>"}' - Force compose backend:
hack logs --compose - Global infra logs:
hack global logs caddy --no-follow --tail 200
Lifecycle + startup:
- Put host setup in
.hack/hack.config.jsonunderstartup/lifecycle(not ad-hoc terminal tabs). - Use
lifecycle.up.beforefor pre-start hooks andlifecycle.processesfor long-running host tasks. - Inspect lifecycle status via
hack projects --detailsand stream viahack logs <service-or-process>.
Sessions (mux-managed):
- Picker:
hack session - Start/attach:
hack session start <project> - Force isolated agent session:
hack session start <project> --new --name agent-1 - Execute in session:
hack session exec <session> "<command>" - Stop session:
hack session stop <session>
Tickets (git-backed):
- Create:
hack tickets create --title "..." --body-stdin - List/show:
hack tickets list,hack tickets show T-00001 - Status/sync:
hack tickets status T-00001 in_progress,hack tickets sync
Global infra:
- Bootstrap once:
hack global install - Start/stop/status:
hack global up,hack global down,hack global status - Use
hack global upbefore Loki/Grafana queries if global logging is offline.
When to use a branch instance:
- You need two versions running at once (PR review, experiments, migrations).
- You want to keep a stable environment while testing another branch.
- Use
--branch <name>onhack up/open/logs/downto target it.
Run commands inside services:
- One-off:
hack run <service> <cmd...>(usesdocker compose run --rm) - Example:
hack run api bun test - Use
--workdir <path>to change working dir inside the container. - Use
hack ps --jsonto list services and status.
Project targeting:
- From repo root, commands use that project automatically.
- Else use
--project <name>(registry) or--path <repo-root>. - List projects:
hack projects --json
Daemon (optional):
- Start for faster JSON status/ps:
hack daemon start - Check status:
hack daemon status
Docker compose notes:
- Prefer
hackcommands; they include the right files/networks. - Use
docker compose -f .hack/docker-compose.yml exec <service> <cmd>only if you need exec into a running container.
Agent integration maintenance:
- Project-level hack commands auto-check integration drift and attempt auto-sync (docs/skills/MCP).
- Set
HACK_SETUP_SYNC_MODE=warnto only warn, orHACK_SETUP_SYNC_MODE=offto disable. - Refresh project + user integrations:
hack setup sync --all-scopes - Audit integration state only:
hack setup sync --all-scopes --check - Remove generated integration artifacts:
hack setup sync --all-scopes --remove - After upgrading CLI:
hack updatethenhack setup sync --all-scopes
Agent setup (CLI-first):
- Cursor rules:
hack setup cursor - Claude hooks:
hack setup claude - Codex skill:
hack setup codex - Tickets skill:
hack setup tickets - Refresh all local agent integrations:
hack setup sync --all-scopes - Init prompt:
hack agent init(use --client cursor|claude|codex to open) - Init patterns:
hack agent patterns - MCP (no-shell only):
hack setup mcp - MCP install (explicit):
hack mcp install --all --scope project
This project uses Ultracite, a zero-config preset that enforces strict code quality standards through automated formatting and linting.
- Format code:
bun x ultracite fix - Check for issues:
bun x ultracite check - Diagnose setup:
bun x ultracite doctor
Biome (the underlying engine) provides robust linting and formatting. Most issues are automatically fixable.
Write code that is accessible, performant, type-safe, and maintainable. Focus on clarity and explicit intent over brevity.
- Use explicit types for function parameters and return values when they enhance clarity
- Prefer
unknownoveranywhen the type is genuinely unknown - Use const assertions (
as const) for immutable values and literal types - Leverage TypeScript's type narrowing instead of type assertions
- Use meaningful variable names instead of magic numbers - extract constants with descriptive names
- Use arrow functions for callbacks and short functions
- Prefer
for...ofloops over.forEach()and indexedforloops - Use optional chaining (
?.) and nullish coalescing (??) for safer property access - Prefer template literals over string concatenation
- Use destructuring for object and array assignments
- Use
constby default,letonly when reassignment is needed, nevervar
- Always
awaitpromises in async functions - don't forget to use the return value - Use
async/awaitsyntax instead of promise chains for better readability - Handle errors appropriately in async code with try-catch blocks
- Don't use async functions as Promise executors
- Use function components over class components
- Call hooks at the top level only, never conditionally
- Specify all dependencies in hook dependency arrays correctly
- Use the
keyprop for elements in iterables (prefer unique IDs over array indices) - Nest children between opening and closing tags instead of passing as props
- Don't define components inside other components
- Use semantic HTML and ARIA attributes for accessibility:
- Provide meaningful alt text for images
- Use proper heading hierarchy
- Add labels for form inputs
- Include keyboard event handlers alongside mouse events
- Use semantic elements (
<button>,<nav>, etc.) instead of divs with roles
- Remove
console.log,debugger, andalertstatements from production code - Throw
Errorobjects with descriptive messages, not strings or other values - Use
try-catchblocks meaningfully - don't catch errors just to rethrow them - Prefer early returns over nested conditionals for error cases
- Keep functions focused and under reasonable cognitive complexity limits
- Extract complex conditions into well-named boolean variables
- Use early returns to reduce nesting
- Prefer simple conditionals over nested ternary operators
- Group related code together and separate concerns
- Add
rel="noopener"when usingtarget="_blank"on links - Avoid
dangerouslySetInnerHTMLunless absolutely necessary - Don't use
eval()or assign directly todocument.cookie - Validate and sanitize user input
- Avoid spread syntax in accumulators within loops
- Use top-level regex literals instead of creating them in loops
- Prefer specific imports over namespace imports
- Avoid barrel files (index files that re-export everything)
- Use proper image components (e.g., Next.js
<Image>) over<img>tags
Next.js:
- Use Next.js
<Image>component for images - Use
next/heador App Router metadata API for head elements - Use Server Components for async data fetching instead of async Client Components
React 19+:
- Use ref as a prop instead of
React.forwardRef
Solid/Svelte/Vue/Qwik:
- Use
classandforattributes (notclassNameorhtmlFor)
- Write assertions inside
it()ortest()blocks - Avoid done callbacks in async tests - use async/await instead
- Don't use
.onlyor.skipin committed code - Keep test suites reasonably flat - avoid excessive
describenesting
Biome's linter will catch most issues automatically. Focus your attention on:
- Business logic correctness - Biome can't validate your algorithms
- Meaningful naming - Use descriptive names for functions, variables, and types
- Architecture decisions - Component structure, data flow, and API design
- Edge cases - Handle boundary conditions and error states
- User experience - Accessibility, performance, and usability considerations
- Documentation - Add comments for complex logic, but prefer self-documenting code
Most formatting and common issues are automatically fixed by Biome. Run bun x ultracite fix before committing to ensure compliance.