Skip to content

Latest commit

 

History

History
256 lines (210 loc) · 18.1 KB

File metadata and controls

256 lines (210 loc) · 18.1 KB

Default to using Bun instead of Node.js.

  • Use bun <file> instead of node <file> or ts-node <file>
  • Use bun test instead of jest or vitest
  • Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild
  • Use bun install instead of npm install or yarn install or pnpm install
  • Use bun run <script> instead of npm run <script> or yarn run <script> or pnpm run <script>
  • Use bunx <package> <command> instead of npx <package> <command>
  • Bun automatically loads .env, so don't use dotenv.

APIs

  • Bun.serve() supports WebSockets, HTTPS, and routes. Don't use express.
  • bun:sqlite for SQLite. Don't use better-sqlite3.
  • Bun.redis for Redis. Don't use ioredis.
  • Bun.sql for Postgres. Don't use pg or postgres.js.
  • WebSocket is built-in. Don't use ws.
  • Prefer Bun.file over node:fs's readFile/writeFile
  • Bun.$ls instead of execa.

Testing

Use bun test to run tests.

import { test, expect } from "bun:test";

test("hello world", () => {
  expect(1).toBe(1);
});

Coding Style & Naming Conventions

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.

Docs Currency (non-negotiable)

  • ANY interface or behavior change (commands, flags, config keys, file layouts, env vars, defaults) must update the affected docs/ pages in the same patch.
  • The CLI reference is generated: after changing the CLI surface, run bun run docs:cli-reference and commit docs/reference/cli.md (a drift test fails otherwise).
  • Agent-facing behavior phrasing lives in src/agents/instruction-source.ts; update it (not the generated surfaces) and run hack setup sync --all-scopes.
  • Real end-to-end coverage lives in tests/e2e/ (bun run test:e2e:local) — extend it when adding user-facing workflows.

Project Notes (Obsidian)

This project uses Obsidian for project context, specs, research, and progress tracking.

Vault: ~/.vaults/core Project folder: 30 Projects/Hack CLI/Notes/

When to write notes:

  • Project analysis and architecture reviews
  • Research findings (e.g., Ghostty VT, libraries, APIs)
  • Specs and design decisions
  • Session summaries with progress updates
  • Any context that should persist across sessions

How to write notes:

  • Use the obsidian skill to interact with the vault
  • Name notes with date prefix: YYYY-MM-DD — Title.md
  • Include tags: #hack-cli #substrate

Landing the Plane (Session Completion)

When ending a work session, you MUST complete ALL steps below.

MANDATORY WORKFLOW:

  1. File tickets for remaining work - Create tickets for anything that needs follow-up
  2. Run quality gates (if code changed) - Tests, linters, builds
  3. Update issue status - Close finished work, update in-progress items
  4. Write session summary to Obsidian - Key decisions, progress, blockers
  5. Hand off - Provide context for next session

hack CLI (local dev + MCP)

Use hack as the single interface for local-first runtime orchestration (compose, DNS/TLS, logs, env, and persistent project workspaces).

Integration freshness:

  • These instructions were generated by hack CLI v3.4.1; treat cached rules from another version as potentially stale.
  • At session start, audit project and global integrations with hack setup sync --all-scopes --check.
  • If anything is stale, missing, or deprecated, run hack setup sync --all-scopes, then reload the agent session so cached instructions are replaced.
  • Never copy or hand-edit generated Hack rules to refresh them; update the CLI and run the sync command.
  • Content revision: 902b04fe1469 (version alone is not a freshness guarantee).

Product boundary:

  • Supported v3 surface: project init, up/down/restart, open, logs, env, host exec/shell, sessions, doctor, and daemon.
  • Removed surfaces: hosted auth/account/org/team flows, web dashboard, built-in GitHub workflows, and built-in Linear sync.
  • Experimental and unsupported: remote/gateway/node/dispatch commands. They are hidden from default help (list with hack help --all) and warn on use; do not use them unless explicitly requested.

Operating rules:

  • Prefer hack over raw docker / docker compose for project workflows.
  • Do not start/stop services from Docker Desktop UI for hack-managed projects.
  • Treat .hack/.internal and .hack/.branch as hack-managed artifacts; do not hand-edit generated files there.
  • Use --json for machine-readable output when available; hack up/down/restart/doctor --json emit an {ok, data | error: {code, message}} envelope with stable E_* error codes.
  • Scripted/agent runs: pass --no-interactive (or set HACK_NO_INTERACTIVE=1) so commands never block on prompts — they apply documented defaults or fail fast with E_INTERACTIVE_REQUIRED.
  • Use MCP only when shell access is unavailable.
  • If runtime state looks wrong, run hack doctor, then hack doctor --fix before 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).
  • Browser launches automatically prefer a routed OAuth alias when enabled; custom development hosts outside .hack stay on the primary host. Set open.prefer or pass hack open --prefer <auto|alias|dev> to override.
  • Not every compose service is routable: only services with Caddy labels and on hack-dev are 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:

  • hack uses Caddy internal PKI for HTTPS on routed hosts; trust CA with hack global trust.
  • Containers get a combined public+local trust bundle (SSL_CERT_FILE etc.) once hack global trust has run; public TLS (package registries, external APIs) keeps working alongside *.hack trust.
  • If the combined bundle is missing, only Node gets *.hack trust (NODE_EXTRA_CA_CERTS); OpenSSL-based tools keep public roots — run hack global trust to enable both.
  • .hack is 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.default.yaml, and optional .hack/hack.env.<overlay>.yaml.
  • Worktree-local env override files: .hack/hack.env.local.yaml and .hack/hack.env.<overlay>.local.yaml.
  • Local-only files: .hack.secret.key, optional .hack/.env compatibility output, .hack/.env.state.json, and .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/.internal/compose.runtime.override.yml, .hack/.branch/compose.<branch>.override.yml, .hack/.branch/compose.<branch>.runtime.override.yml.
  • Managed via CLI: .hack/.internal/extra-hosts.json (use hack internal extra-hosts ... commands).
  • Lifecycle runtime files: .hack/.internal/lifecycle/state.json, .hack/.internal/lifecycle/*.log.
  • Ignore rules: hack owns a committed .hack/.gitignore (self-healing on init/up) covering machine-local generated files (.internal/, .branch/, .env, .env.state.json, hack.env*.local.yaml); keep it committed, and if generated files leaked into git, hack doctor --fix untracks them (files stay on disk).

Linked git worktrees:

  • Secret key inherits from the primary checkout automatically through the shared git common dir; set HACK_ENV_SECRET_KEY for CI or detached environments.
  • hack up in a linked worktree defaults to a branch instance named after the worktree's git branch; a detached linked worktree requires an explicit --branch, unless config worktree.auto_branch=false explicitly opts into the base instance.
  • Before hack up or hack restart auto-targets a new branch instance, Hack warns when the same worktree already owns a non-terminal instance; pass --branch <name> to make the target explicit.
  • hack doctor flags divergent secret keys and dev_host collisions across checkouts.

Advanced networking (extra_hosts + local proxies/tunnels):

  • Static host mappings: set internal.extra_hosts in .hack/hack.config.json.
  • Dynamic host mappings: hack internal extra-hosts set <hostname> <target> / unset / list.
  • For host-local proxies/tunnels, prefer host-gateway as target when possible.
  • After mapping changes or proxy IP churn: hack restart and then hack doctor.

Standard workflow:

  • If .hack/ is missing: hack init
  • Start services: hack up --detach (or hack up -d)
  • Check status: hack ps or hack status
  • Open app URL: hack open --json
  • Restart: hack restart
  • Stop services: hack down

Running things (decision guide):

  • One-off command in a fresh service container (deps started as needed): hack run <service> <cmd...>.
  • Command inside an already-running service container: hack exec <service> -- <cmd...>.
  • Host script that needs hack-stored env: hack host exec --env <overlay> --scope <service> -- <cmd...> — this is THE way to run repo scripts; never read .env files directly.
  • Interactive host shell with injected env: hack host shell --env <overlay> --scope <service>.
  • Browser/host URL: use hack open <service> --json (OAuth aliases are preferred when enabled). Container-to-container traffic should use Compose DNS.

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 --pretty or hack 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.json under startup/lifecycle (not ad-hoc terminal tabs).
  • Use lifecycle.up.before for pre-start hooks and lifecycle.processes for long-running host tasks.
  • Lifecycle hooks and processes receive the selected overlay's global values plus host overrides; service-scoped values remain container-specific unless a host command explicitly selects that scope.
  • For fixed-port host helpers such as SSM tunnels or local proxies, set singleton.ports and usually onConflict: "adopt" so Hack reuses a healthy existing listener instead of starting duplicate tunnel stacks.
  • singleton is a listener guard, not process ownership transfer; adopted external processes are left running on hack down.
  • Inspect lifecycle status via hack projects --details and stream via hack logs <service-or-process>.
  • Lifecycle session recovery is ownership-proven: Hack adopts healthy token-, definition-, and environment-matched sessions, replaces owned stale sessions, and refuses to kill same-name sessions without deterministic ownership proof.
  • hack doctor --fix reaps an orphan lifecycle session only when mux ownership is proven and its Compose instance is absent; unverified same-name sessions are never modified.
  • After hack up or hack restart, running services and successful one-shot services (exited with code 0) count as successful. Hack recognizes dependency installers, hack.service.one-shot=true, and services referenced by Compose condition: service_completed_successfully; other states return E_STARTUP_INCOMPLETE, and hack doctor warns about containers stuck in Created.
  • Detached startup is bounded; a hung Compose operation returns E_STARTUP_TIMEOUT, terminates its process group, and hack doctor --fix can start exact containers left in Created.
  • Target only affected services with hack up <service...> --detach, hack restart <service...>, or hack env apply --service <service>; scoped operations skip project lifecycle hooks and implicit dependency startup.
  • Use hack env explain <KEY> --env <overlay> --service <service> --target <host|compose> for redacted source, precedence, availability, and delivery diagnostics.
  • Dependency installer services are detected generically by command or hack.dependencies.bootstrap=true; registry env references are preflighted before container mutation. Optional hack.dependencies.cache-volume, hack.dependencies.lockfiles, and hack.dependencies.runtime-files labels enable lockfile/runtime-keyed volumes shared across compatible worktrees.

Workspaces (mux-managed, tmux-first by default):

  • Picker: hack session for persistent project workspaces.
  • Reuse/create: hack session start <project>
  • Env-scoped workspace: hack session start <project> --env qa --service api --detach
  • Force isolated agent workspace: hack session start <project> --new --name agent-1 (<project>--agent-1).
  • Execute in workspace: hack session exec <workspace> "<command>"
  • Execute in workspace with injected env: hack session exec <workspace> --env qa --service api "bun db:migrate"
  • Stop workspace: hack session stop <workspace>

Host-side env helpers:

  • One-off host command with injected env: hack host exec --env qa --scope api -- bun db:migrate
  • Host commands default to a host-local env view; use --target compose when you explicitly want container-oriented addresses.
  • --scope selects which env scope to inject; it does not move execution into that service container.
  • Interactive host shell with injected env: hack host shell --env qa --scope api
  • Run inside an already-running service container: hack exec api -- bun test

Branch instances (parallel envs):

  • Use a branch instance when you need two versions running at once (PR review, experiments, migrations) or want to keep a stable environment while testing another branch.
  • Target one with --branch <name> on up/open/logs/down (for example: hack up --branch <name> --detach).
  • Linked worktrees pick a branch instance automatically (see Linked git worktrees).
  • Containers receive HACK_RUNTIME_METADATA plus HACK_DEV_URL, HACK_ALIAS_URL, and current-service URL fields derived from effective Caddy routes. Use Compose DNS for internal traffic and this metadata for browser-facing links, OAuth callbacks, and webhooks.

Run commands inside services:

  • One-off: hack run <service> <cmd...> (uses docker compose run --rm)
  • Example: hack run api bun test
  • Use --workdir <path> to change working dir inside the container.
  • Use hack ps --json to 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

Global infra:

  • Bootstrap once: hack global install
  • Start/stop/status: hack global up, hack global down, hack global status
  • Use hack global up before Loki/Grafana queries if global logging is offline.

Daemon (optional):

  • Start for faster JSON status/ps: hack daemon start
  • Check status: hack daemon status

Docker compose notes:

  • Prefer hack commands; 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 (project docs, client skills/rules, shared global skills, and MCP).
  • When drift is detected, Hack reports it before repair and tells the agent to reload after repair; it never silently leaves the session using cached rules.
  • Set HACK_SETUP_SYNC_MODE=warn to only warn, or HACK_SETUP_SYNC_MODE=off to 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 update then hack setup sync --all-scopes
  • When changing hack itself: interface or behavior changes must update docs/ in the same change (regenerate the CLI reference with bun run docs:cli-reference).

Agent setup (CLI-first):

  • Cursor rules: hack setup cursor
  • Claude hooks: hack setup claude
  • Codex skill: hack setup codex
  • Refresh all local agent integrations: hack setup sync --all-scopes
  • Agent-assisted onboarding: hack init --with claude|codex|both (new repos) or hack agent onboard (existing projects) print/hand off the full setup prompt; the /hack-init skill and the hack-init MCP prompt return the same content.
  • 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