This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
pnpm install # install dependencies
pnpm dev # development mode (tsx hot-reload)
pnpm dev:debug # DEV=true — enables React DevTools integration
pnpm build # TypeScript compile to dist/
pnpm start # run compiled output
pnpm tsc --noEmit # type-check onlyThis is a terminal AI coding assistant built on sema-core (event-driven engine) with Ink (React for terminal) as the TUI layer.
Three-layer data flow:
sema-core engine (events) → React Hooks (event→state) → Ink Components (render)
-
Engine (
sema-corenpm package) — providesSemaCore(process-level: models, sessions) andSemaSession(session-level:processUserInput(), event bus). All AI logic, tool execution, and permission handling is inside sema-core. -
Hooks (
src/hooks/) — bridge layer. Each hook subscribes to sema-core events and converts them to React state:useBootstrap— initialization state machine: create SemaCore → check models → create sessionuseSession—state:update,session:interrupted→ idling/processinguseMessages—input:processing,message:complete,tool:execution:*,task:agent:*→ message arrayuseStreaming—message:text:chunk,message:thinking:chunk(32ms throttled, ref+buffer) → streamingText + thinkingTextuseUsage—conversation:usage,session:ready→ token usageusePermissions—tool:permission:request→ pending state + respond callbackuseForms—pick:option:request,plan:exit:request→ inline form stateuseModels— calls SemaCore API directly for model CRUDuseTodos—todos:update→ todo listuseEvents—compact:exec,topic:update→ compact notification + session topic
-
Components (
src/components/) — Ink React components consuming hook state. No component directly calls sema-core APIs. Key patterns:MessageListuses a top<Box flexGrow={1} />spacer to push messages to the bottom — oldest content scrolls off the top naturallyPermissionConfirm,FormInline,PlanExitInlineare conditionally rendered inline (not modal overlays)
Screen routing: App.tsx manages chat vs models screen via a React state switch. /model command triggers the switch.
- Claude Code style REPL — no panel borders, no chat bubbles. Text flows naturally in terminal.
- Permission is inline — not a modal overlay.
(y) agree (a) always allow (n) refuserendered inside the conversation flow. - Forms are inline —
pick:option:requestrenders radio/checkbox/text directly in flow. - Streaming throttled —
useMessagesuses a 32ms ref+buffer pattern to avoid rendering 1000s of times per response. - Status bar — single line:
model · mode · tokens · state. No horizontal separator line. - Color — logo uses
#CD4B1A(Claude Code orange).
- Model config persists to
~/.sema/model.confautomatically afteraddModel() - First launch: if no models exist, auto-routes to ModelScreen for configuration
session:errorwithcontext_length_exceededtriggers auto-compact (handled by sema-core internally)- The session is created once on app start; ChatScreen receives the
SemaSessioninstance as a prop SemaSessionevents are session-scoped; usesession.on()/session.off()for lifecycle management
- Package has
"type": "module"in package.json — all files are ESM, imports must include.jsextension - Ink 7 + React 19 —
render()usesincrementalRendering: true(only redraws changed lines) andalternateScreen: true(alt screen buffer, like vim) for flicker-free terminal output MessageListuses a top<Box flexGrow={1} />spacer to push messages to bottom — overflow scrolls off the top naturally- Streaming throttle split —
useStreamingmanages text/thinking chunk buffering (32ms ref+buffer),useMessagesmanages only message array - ModelScreen views use separate
useInput({ isActive })— each sub-view has its own input handler, avoiding giant switch blocks useBootstraphook — encapsulates the init state machine, keepingApp.tsxlean (~70 lines)- Custom
useInput-based text input with slash command autocomplete inInputArea.tsx markedv15 for markdown rendering (custom renderer insrc/render/markdown.tsx)- sema-core source is at
../sema-code-core/— reference when debugging engine behavior - React DevTools: run
pnpm dev:debug+npx react-devtoolsto inspect component tree/props/state in real-time - No API keys or env vars needed at startup — user configures models inside the app on first launch
- pnpm is the required package manager (
packageManagerfield in package.json)
- Format:
<emoji> <description>— always prefix commit messages with an emoji, followed by a short description. - Examples:
🐛 fix: backspace key on macOS,✨ feat: add systemPrompt identity,📝 docs: update README.
The memory/ directory in this project contains design decisions:
bruce-code-ui-reference.md— UI must reference Claude Code style (REPL, inline permissions,>prompt)- See
ARCHITECTURE.mdfor architecture design, data flow, and component tree