Caveman style. Progressive discovery. Load on demand.
This is the highest rule. It outranks every phase, every dispatch, every "be efficient" instinct.
If a faster path and a better path both exist, the better path wins. Always. Even if "better" means 40 directory renames, 28 cross-reference fixes, a doc update, a test rewrite, and a hash bump. Volume of work is not a reason. "It would still work" is not a reason.
Watch for these shortcut patterns in your own thinking:
- "This avoids the rename / refactor / doc update / migration." → That's labor, not a reason. Do the work.
- Adding an override / opt-out / fallback field to paper over a bad name or bad shape. → Fix the underlying name or shape.
- Skipping a typecheck / test / build because it's slow or set-up-heavy. → Run it.
- Keeping legacy compatibility "just in case" inside internal code. → Delete it (per the existing no-backwards-compat rule).
- Generating bulk content with a script when each item deserves real thought. → Do the items by hand.
- Writing a TODO / FIXME / "we can clean this up later." → Clean it up now.
- Declaring something done before the verification step actually passed. → Not done.
- Picking the smallest example to demo on without asking the user. → Ask.
Self-audit at the end of every meaningful chunk of work:
"Where did I cut a corner? Was it because the better path was wrong, or because it was just longer?"
If "just longer" — go back and do it right.
This rule was added because the model defaulted to a shortcut (a display_name schema override instead of renaming 40 slugs) and the user had to push back. Don't make the user push back. Catch yourself first.
Before any action: invoke /caveman skill. Stay terse. Drop articles, filler, pleasantries. Technical substance stays. Code blocks unchanged.
Default level: full. Switch via /caveman lite|full|ultra.
Off only if user says "stop caveman" or "normal mode".
Houston = desktop app + standalone engine + open library of agents.
app/— Tauri 2 desktop. React frontend, small Rust binary that spawns the engine as a sidecar subprocess and talks to it over HTTP/WS. OS-native glue only (file pickers, reveal-in-file-manager, logs). No domain logic.engine/— Rust crates.houston-engine-core= runtime/domain.houston-engine-protocol= wire types.houston-engine-server= axum HTTP+WS binary (houston-engine).houston-agent-files,houston-skills,houston-sessions,houston-file-watcher, etc. are leaf crates. Frontend-agnostic: no Tauri, no React.ui/—@houston-ai/*React packages (chat, board, layout, engine-client, …). Props-only, no store imports.@houston-ai/engine-clientis the TS front door to the engine.- User data —
~/.houston/: DB, logs,engine.json, andworkspaces/<Workspace>/<Agent>/. Each agent has.houston/data files +CLAUDE.md+.agents/skills/. - Wire contract — every domain call is a
fetchor WS frame in@houston-ai/engine-client. There are NOinvoke("list_workspaces", …)style Tauri commands for domain; those were all deleted. - Reactivity — engine emits
HoustonEvents; desktop subscribes to the WS*firehose; TanStack Query invalidation inapp/src/hooks/use-agent-invalidation.tsmaps events → query keys. File watcher catches direct agent writes. - Voice — agents' target user is NON-technical. The product system prompt forbids mentioning files/JSON/configs/CLIs when talking to the user. Lives in
app/src-tauri/src/houston_prompt.rs(Houston app), NOT in the engine. Engine is prompt-agnostic; app hands it over at spawn viaHOUSTON_APP_SYSTEM_PROMPT.
Before touching anything: run PHASE 1 (load knowledge-base/architecture.md + any KBs relevant to scope).
Deploying / shipping a release? → /release
Manual macOS build, notarize, staple? → /build-app-local
Bug? Don't guess → /debug
Need specific knowledge? Load on demand:
- Repo shape, products, engine story →
knowledge-base/architecture.md - Colors, typography, components, animation →
knowledge-base/design-system.md .houston/layout, schemas, reactivity →knowledge-base/files-first.md- Skills on disk + UI, picker, invocation marker →
knowledge-base/skills.md - Agent manifest, tiers, sidebar, workspaces →
knowledge-base/agent-manifest.md - Engine wire protocol (REST + WS) →
knowledge-base/engine-protocol.md - Provider error taxonomy + classifier contract →
knowledge-base/provider-errors.md houston-enginebinary ops →knowledge-base/engine-server.md- Bundled CLIs (codex universal, composio per-arch) + runtime claude-code installer →
knowledge-base/cli-bundling.md - Windows testing loop from a Mac (UTM VM, SSH bridge, cross-compile, log fetch) →
knowledge-base/windows-testing.md - Custom frontend on
houston-engine(integration reference) →examples/smartbooks/README.md - Mobile PWA (tunnel, pairing, reactivity) →
docs/mobile-architecture.md+docs/relay-operations.md - Updater, analytics, Sentry, env vars, CI →
knowledge-base/production-infra.md - Daily/weekly/monthly data rituals + dashboard reading guide →
knowledge-base/data-rituals.md - UTM conventions, campaign attribution, IRL event tracking →
growth/utm-conventions.md+growth/campaigns/_template.md+scripts/event-qr.sh - Supabase auth, Google SSO, Keychain →
knowledge-base/auth.md - Translating UI strings, namespaces, ui/ labels prop pattern,
t()rules →knowledge-base/i18n.md
Design work? Skills: /critique before, /polish after. Else /clarify (copy), /distill (overloaded screen), /animate (micro-interactions), /audit (a11y).
Print phase name so user knows protocol active.
STOP-AND-WAIT rule: When told "wait for approval" / "ask user" — end turn NOW. No anticipating. No next phase.
Read knowledge-base/architecture.md + any KBs relevant to scope. Name what you loaded.
Read files user references. Identify direction: library-first / app-first / single-layer. Ask clarifying Qs if anything unclear. STOP if asked.
Push back on request if better approach exists. Check:
- Library or app? Generic → ui/engine. App-specific → app/.
- Which package? Exists already?
- Props generic? No store imports? No app-specific types?
- Does this fit chat-first planning/delegation?
Approach sound → say so. Better path exists → say it clearly, no sugarcoat. STOP until user agrees.
Numbered steps. Mark area per step: [ui/board], [engine], [app]. Group into testable chunks. Library before app. STOP for approval.
Print chunk + area. Do all steps. Brief summary. Continue.
Run checks for what touched. Rust → cargo test, not just check. Fix failures.
Full verification. UI touched? Visual fidelity check. Say "Ready for testing — verify + report." STOP. Issue? Add logging first (see /debug), never blind fix.
Library boundary leak? API clean? File > 200 lines (CSS > 500)? Duplication across ui/ + app/? Propose + do after approval.
Unused imports, dead code, debug artifacts. ui/ → no @/, no Zustand, no Tauri. app/ → no duplicated logic.
Check + update all affected docs: knowledge-base/*.md, skills, showcase. Update now, not propose.
Summarize. Needs NEW KB entry? New pattern / architecture decision / gotcha / design precedent. Propose if yes.
Ask: "Ready to commit? (yes/no/skip)" STOP. Yes → stage specific files, conventional commit, push claude/wip. Never git add -A.
| Area | TS | Rust | Full build |
|---|---|---|---|
| ui/ | pnpm typecheck |
— | — |
| engine/ | — | cargo test --workspace |
cargo build --workspace |
| engine/ Win check | — | cargo check --target x86_64-pc-windows-gnu -p houston-engine-server (needs mingw-w64) |
— |
| app/ | cd app && pnpm tsc --noEmit |
cd app/src-tauri && cargo check |
cd app && pnpm tauri build |
| app/ Win MSI | — | — | cd app && pnpm tauri build --target x86_64-pc-windows-msvc (needs Windows host or xwin SDK) |
| app/ i18n | cd app && pnpm check-locales |
— | — |
| CLI bundle (mac) | — | — | ./scripts/fetch-cli-deps.sh both |
| CLI bundle (win) | — | — | ./scripts/fetch-cli-deps.sh windows-x64 (Bun + jq + zstd required) |
pnpm tauri dev spawns the engine as a subprocess from app/src-tauri/binaries/houston-engine-<triple>, which build.rs stages from target/{debug,release}/houston-engine. Tauri does NOT rebuild the engine on its own — frontend HMR works fine but the sidecar is whatever binary was last compiled.
Rule: any time a PR touches engine/** (including merges that bring engine changes from main), run cargo build -p houston-engine-server BEFORE the next pnpm tauri dev and restart it. Symptoms of a stale sidecar: 404s on routes that exist in the current source, missing event types, schema mismatches. Production users never hit this — release CI builds the engine from scratch on every tag.
Never guess. Read logs first. See /debug.
- Generic reusable → ui/. App-specific → app/. Unsure → start in app/, extract later.
- Props over stores, always. No Zustand/Redux/etc imports in ui/.
- No app/ types in ui/. Use generic types (
BoardItem,FeedItem,ChatMessage). - No
@/path aliases in ui/. Relative imports within package. Package imports between.
engine/= frontend-agnostic. No Tauri. No React. No webview assumption.- Tauri-specific code →
app/houston-tauri/(the adapter).
New AI provider = one new adapter file in engine/houston-terminal-manager/src/provider/<name>.rs implementing ProviderAdapter, one entry in REGISTRY, three dispatch arms (runner spawn in session_dispatch.rs, NDJSON parser in session_io.rs, title summarizer in sessions/summarize.rs). All other call sites pick the new provider up automatically through Provider::from_str and the registry. Provider is a Copy newtype around &'static dyn ProviderAdapter, NOT an enum, so no variant additions are needed.
Error classification is part of the adapter — implement classify_stderr and classify_result_error to map this provider's failure patterns to the shared ProviderError taxonomy (RateLimited, QuotaExhausted, Unauthenticated, ...). Real CLI fixtures > guessed regex; unit-test each classifier with verbatim stderr / NDJSON snippets. The frontend already renders every variant (app/src/components/shell/provider-error-card.tsx) — no UI work unless you need a custom status-page URL or a provider-specific reconnect flow.
See knowledge-base/architecture.md (engine crates), knowledge-base/agent-manifest.md (provider/model table), and knowledge-base/provider-errors.md (full taxonomy + classifier contract) for the full picture.
- Every
.houston/data surface must react to file changes regardless of who wrote (user via UI, agent via file write, external edit). - All
.houston/fetching → TanStack Query + event invalidation. No load-on-mount-only. - Agent writes emit events. File watcher catches bypass writes. Both architecturally required.
- Never build "agent can do X but UI won't show until refresh."
- Houston ships en / es / pt. Every user-facing string flows through
t()fromreact-i18next. No literal English in JSX text, props, placeholders, aria-labels, toast titles, error messages, or<Empty>defaults. - New screen / new strings → pick the right namespace under
app/src/locales/<lang>/<ns>.json(or create one + register inapp/src/lib/i18n.ts+ augmentapp/src/types/react-i18next.d.ts). en is source of truth; es and pt mirror the structure. ui/@houston-ai/*stays i18n-agnostic per the library boundary. Components take optionallabels?props with English defaults; the consumer inapp/passest()results in. Don't importreact-i18nextinui/.- Variables:
t("key", { name }), never string concat. Plurals:countAPI with_one/_otherkeys. Embedded markup:<Trans components={{...}}>. - No em dashes (
—) in user-facing copy. Commas or sentence breaks. Validator enforces this. - Spanish = Latin-American neutral (computador, tú). Portuguese = Brazilian (você).
- Keys are type-checked via
app/src/types/react-i18next.d.tsaugmentation — typos fail at compile time. - Pre-commit:
pnpm tsc --noEmitANDpnpm check-locales(catches missing keys, shape drift, placeholder parity, em dashes). - See
knowledge-base/i18n.mdfor patterns, glossary, and the wiring checklist.
- Types, APIs, Rust modules, TS fns: change = change. No "just in case" keeps.
- User data = different. Canonical location is
~/.houston/**(workspaces live at~/.houston/workspaces/). Shape/layout changes inside~/.houston/<agent>/.houston/**need an idempotent migration inhouston_agent_files::migrate_agent_data. Never break existing users. - Legacy
~/Documents/Houston/**— earlier versions used this path. We do NOT auto-migrate from there; if a user upgrades they may need to copy their workspaces manually. When introducing further root moves, propose a migration story before executing.
Every feature gets tests. No exceptions. Tests don't count toward 200-line limit.
Domain concepts (status, classification) MUST be enums. TS → discriminated unions. Rust → enums w/ Display/FromStr.
We are in beta. Every error a user-initiated action can produce MUST reach the user as a visible toast with a "Report bug" affordance. Silent fallbacks rob us of the bug report — we WANT the noise.
Banned patterns (Rust):
let _ = <fallible>/let _ = <fallible>.await— discarding aResult.ok()to drop a Result on the floor.unwrap_or(...),.unwrap_or_default(),.unwrap_or_else(|_| ...)over an op the user initiatedmatch x { Ok(v) => ..., Err(_) => <log + default> }— log-and-continue- catch-and-
tracing::warn!-and-continue inside loops where the user expected progress (theinstall_from_repo"skip" pattern is the canonical anti-example) unwrap()/expect()outside of test code or genuine compile-time invariants
Banned patterns (TypeScript):
.catch(() => ...)returningnull/[]/{}try { ... } catch { ... }with no rethrow and no toasttry { ... } catch (e) { console.error(e) }— log only, no surface- React Query
onErrorthat toasts a generic string instead oferrorMessage(err) - Top-level event handlers that fire-and-forget a Promise with no
.catch
Required surfacing path:
Engine SkillError / CoreError → ApiError → TS errorMessage(err) → toast hook → user sees the real reason AND a Report-bug button that bundles the most recent engine + app log tail.
The one exception: tracing::error! from event-emit / file-watcher callbacks where there is no UI thread to toast on. Everything else surfaces.
When unsure: don't swallow. A noisy beta is a productive beta.
Interactive elements visible without hovering. Hover may enhance, never gate.
200 lines/file (excluding tests). CSS 500. NEVER compress to fit. Extract modules.
shadcn/ui registry, @houston-ai showcase, existing components, npm — before writing from scratch.
Never "You're absolutely right!" if better approach exists. Say it.
User ALWAYS runs Claude in a per-task worktree. Each task = isolated branch in .claude/worktrees/<name>/. Main stays clean.
Branch model:
main— releasable, protected, PRs onlyclaude/<worktree-name>— the worktree's own branch (auto-created on worktree spawn); commits go here
End-to-end flow (run without asking, unless a step is destructive and not pre-authorized):
git branch --show-current→ confirm it's the worktree branch (e.g.claude/crazy-pare-b3d43d). Never switch toclaude/wipormain.- Stage specific files. Never
git add -A. - Conventional commit (
feat:fix:docs:chore:refactor:style:test:). git push -u origin <worktree-branch>.gh pr create --base main --title "…" --body "…"— summarise changes, list affected files.- Merge the PR yourself:
gh pr merge --squash --delete-branch. User does NOT review — they rely on the phase protocol + tests + typecheck to catch issues before commit. - Cleanup (from the main repo checkout, not the worktree):
git worktree remove <path>is handled by the harness on exit; just ensure the remote branch is deleted by--delete-branch.
Never git reset --hard on main, never force-push to main, never merge without the PR step (even for trivial changes — PR is the audit trail).
Signing identities, team IDs, API keys, issuer UUIDs: env vars only. Never literals in committed files. Read via option_env!() (Rust compile-time) or env vars (CI).
User approved once ≠ approved in all contexts. Unless durable instructions authorize, confirm first for:
- Destructive ops (delete files/branches, drop tables, rm -rf)
- Hard-to-reverse (force-push, git reset --hard, amend published, remove deps)
- Shared-state (push, PR create/comment, Slack/email send)
- Third-party uploads (diagram renderers, pastebins — could be indexed)
Match action scope to what was actually requested.