This is the repository instruction entrypoint. X-Code loads the applicable AGENTS.md chain into project context;
other agent hosts must use a verified discovery path or open this file explicitly. Keep it concise because it is prompt
context.
- Use visualizations when they help explain complex concepts.
- Keep responses concise and direct, and clearly distinguish facts from assumptions.
- Base research on reliable sources.
- Stay aligned with the user's goals and constraints.
- Avoid unnecessary questions; ask only when a key decision requires confirmation.
- Use sub-agents judiciously and avoid unnecessary parallelism.
- Keep code changes minimal and avoid unrelated refactoring.
- Verify actual results through testing; do not assume something is complete just because it looks complete.
- Keep all features and tests compatible with Windows, macOS, and Linux. Do not assume POSIX paths, shells, signals, terminal glyphs, process behavior, or file-lock semantics. Platform-conditional tests are allowed only when the underlying capability cannot be tested equivalently, and the reason must be explicit.
- Protect existing code and data.
- Report key results without unnecessary progress updates.
pnpm workspace; Node >= 22.
pnpm build # tsc -b (core) then esbuild (cli → dist/cli.js)
pnpm dev # build core, then run CLI from source via tsx (no watch)
pnpm typecheck # tsc -b — strict, run before any PR
pnpm lint # eslint --fix; ignores **/tests/** and *.js
pnpm test # build + Unit/Fault/Package/PTY tests
pnpm test <pattern> # single file: pnpm test packages/core/tests/agent-loop.test.ts
pnpm run ci # typecheck + lint/format checks + pnpm testAfter editing core sources you must pnpm build (or tsc -b --watch in packages/core). The CLI imports packages/core/dist/, not the TS source.
Follow docs/development/agent-workflow.md for work definition, Human Plan Review, verification evidence, Human Local Acceptance, Draft/Ready PR handling, and continuous knowledge capture. Use .github/pull_request_template.md for the durable PR handoff. Capability status in that guide describes what can run; every task must report its own check outcomes separately.
Two packages, unidirectional: cli → core. Core has zero UI dependencies.
packages/
core/ Agent engine: agentLoop, tools, providers, knowledge, permissions, sub-agents
cli/ Terminal UI: Ink (lifecycle only), ChatInput cell-grid renderer, slash commands
Rendering: Ink's dynamic region is permanently empty. Every visible UI element is drawn by ChatInput.tsx writing directly to process.stdout via cell-level diff. package.json aliases ink to @jrichman/ink@6.6.9 — never import from @jrichman/ink directly.
Agent loop (core/src/agent/loop.ts:agentLoop): one call = one user message, spinning runTurn rounds until stop/tool-calls/abort. LoopState is reused across submits within a CLI session. systemPromptCache must remain byte-stable between invalidation events (model switch, /skill or /mcp refresh, permission-mode flip) — any per-turn interpolation into the system prompt silently disables prompt caching for OpenAI-compatible providers.
Deferred tools (core/src/agent/tool-search/): MCP tools and non-core built-ins are listed by name under ## Deferred Tools in the system prompt and loaded on demand via toolSearch; buildDeferredCatalog falls back to full injection for weak models and small catalogs so the prompt stays byte-stable. Sub-agents get no catalog and keep the inline ## MCP Tools block.
Sub-agents (core/src/agent/sub-agents/): task tool delegates to isolated agentLoop with fresh LoopState. Registry is built at CLI startup and frozen. Adding/editing agent files requires a CLI restart because the agent list is embedded in the byte-stable systemPromptCache. Sub-agents always deny task (no recursion). In plan mode, write tools are denied via tool filter.
Cross-session messaging (core/src/peers/, cli/src/peer-lifecycle.ts): --name enables local discovery and messaging for interactive root sessions. Peer input taints the derived context and routes tool calls through the authority evaluator; do not bypass that path. Transport is local Unix sockets on macOS/Linux in this release; Windows must fail closed as unsupported until it has an audited native transport.
Knowledge (core/src/knowledge/): four layers merged into system prompt — user AGENTS.md, user memory Core profile (~/.x-code/memory/MEMORY.md), project AGENTS.md chain (root→leaf, leaf wins), AGENTS.local.md.
Provider config: packages/core/src/providers/catalog.ts:PROVIDERS is the single provider metadata table (envKey, defaultModel, /model picker entries, key URL, base URLs, reasoning tiers); PROVIDER_* derived views and the test env-var list all come from it. API keys read only from env vars, plus escape hatch OPENAI_COMPATIBLE_API_KEY + OPENAI_COMPATIBLE_BASE_URL. OpenAI also supports ChatGPT subscription OAuth through auth/openai-chatgpt/; stored ChatGPT credentials take exclusive precedence over OPENAI_API_KEY until xc logout, and authentication failures must never fall back to the key. Adding a provider = one catalog entry + a constructor branch in providers/registry.ts.
Permissions: 3-level model (always-allow / ask / deny). Shell commands classified via shell-utils.ts with quote-aware compound-command splitting and LRU cache.
Shared internal modules (core/src/):
frontmatter.ts— unified YAML frontmatter parser (commands, skills, sub-agents).coerceTypesoption enables array/number parsing (sub-agents only).registry-diff.ts—diffNamedEntries()for all registryreload()methods.settings-io.ts— fault-tolerant JSON settings read/modify/write (skills + plugins).utils.ts:generateTimestampId— canonicalYYYYMMDD-HHMMSS-mmmfor session/checkpoint IDs.
User directory resolution: userXcodeDir() for all file I/O (respects X_CODE_HOME). The frozen USER_XCODE_DIR constant is only for byte-stable system-prompt content and log init.
- ESM only (
"type": "module"),.jsextensions on relative imports even in.tsfiles (NodeNext). - No semicolons, single quotes, trailing commas, print width 120. Import order enforced by prettier-plugin-sort-imports:
node:→react→ink→ai/@ai-sdk→zod→@x-code-cli/→./relative. - Comments: reserved for why (terminal-protocol workarounds, provider-specific quirks). Keep that style.
- Commit style: conventional commits (
feat:,fix:,refactor:, etc.) enforced by commitlint. Additional types:release,wip. .x-code/at repo root is gitignored — session summaries, auto-memory, local prefs, custom sub-agent definitions. Tests redirect viaX_CODE_HOME.DEBUG_STDOUT=1 xcwrites to~/.x-code/logs/debug.log(10 MB rolling).debugLog()in core is a no-op without it.- Tests use vitest with globals (
describe,it,expect,viavailable without imports). Test files ignored by eslint. - API export snapshot (
packages/core/tests/api-exports.test.ts) locks the@x-code-cli/corepublic export list. Runpnpm test -- -uto update after intentional changes.
- Don't auto-commit. Typecheck/build/tests passing is NOT authorization. Wait for explicit
提交,commit,commit it,提交一下, orok ship it. One authorization covers one chunk only. - When changing tool execution code, always thread
options.abortSignalthrough. Orphan tool_calls (without tool_results) cause the next API request to fail. - OpenAI-compatible providers auto-cache stable prefixes. Don't interpolate timestamps or frame-shifting data into systemPromptCache or sub-agent tool descriptions.
- Blocking the UI thread kills the TUI.
ChatInputrenders via direct stdout writes on a setInterval timer. Long synchronous work in tool execution, permission callbacks, or slash-command handlers freezes the display. Use async I/O; offload heavy work. - Don't add
<Box>/<Text>Ink children that produce visible output.App.tsxreturns a single<ChatInput>. New UI surfaces go insideChatInput.tsx's cell buffer.