Thank you for your interest in contributing to Peak Code. This document defines the standards, processes, and expectations for all contributions. Please read it carefully before opening an issue or pull request.
Peak Code is in an early stage. The architecture and APIs are still evolving. Proposing sweeping changes that improve long-term maintainability is not just welcome — it is encouraged. That said, every change must be justified, focused, and aligned with the project's core priorities.
- Code of Conduct
- Core Priorities
- Ways to Contribute
- Development Setup
- Project Architecture
- Coding Standards
- Pull Request Process
- Transcript Performance Guardrails
- Local Dev Instance Isolation
- Package Roles & Subpath Exports
- Contributor License Agreement
This project adheres to the MIT License terms. In all interactions, treat maintainers and fellow contributors with respect. Be constructive, assume good intent, and keep discussions focused on technical merit.
We reserve the right to block or ban any participant whose behavior undermines a healthy community.
Every contribution is evaluated against these priorities, in order:
- Performance first. Peak Code wraps resource-intensive AI agent runtimes. The GUI must never be the bottleneck. Every render, every store update, every WebSocket message must justify its cost.
- Reliability first. Sessions survive restarts. Streaming must not drop frames. State must be deterministic. Partial failures must not corrupt the session.
- Predictability under load and during failures. Session restarts, reconnections, and partial streams must behave consistently. Graceful degradation is mandatory.
When tradeoffs are unavoidable, choose correctness and robustness over short-term convenience.
If you find a bug, create an issue. A great bug report includes:
- A clear description of the bug and its impact
- Steps to reproduce, with the exact environment (OS, browser, agent provider)
- What you expected to happen vs. what actually happened
- Screenshots, videos, or logs where applicable
- Whether the issue is reproducible across providers (Codex, Claude, etc.) or provider-specific
Before proposing a feature:
- Search existing issues and discussions to avoid duplicates.
- Describe the problem you are solving, not just the solution you want.
- Explain why the enhancement aligns with the project's core priorities.
- If the change touches the transcript, scroll, or streaming paths, read the Transcript Performance Guardrails first.
Enhancements that trade performance or reliability for cosmetic convenience will be rejected.
Documentation is essential. If you find something missing, unclear, or outdated:
- For architecture docs: submit a PR to
.docs/ - For user-facing docs: submit a PR to
docs/ - For internal guidance:
AGENTS.mddocuments the conventions used by automated agents
When adding documentation, prefer clarity over comprehensiveness. A short, correct document is better than a long, outdated one.
| Dependency | Minimum Version | Notes |
|---|---|---|
| Bun | ^1.3.9 | Package manager and runtime |
| Node.js | ^24.13.1 | Also required for some tooling |
| Git | 2.30+ | For version control integration |
| Codex CLI | latest | Required for Codex provider support |
The project uses Bun as its package manager with the isolated linker. Do not use npm or yarn.
git clone https://github.com/PeakCode-AI/PeakCode.git
cd PeakCode
bun installNote:
bun installinstalls all workspace dependencies from the root. Do not runbun installin individual workspace packages.
# Full dev environment (web UI + server)
bun run dev
# Individual services
bun run dev:server # Server only
bun run dev:web # Web UI only
bun run dev:desktop # Desktop (Electron) app
# Quality checks (run before committing)
bun run test # Vitest test suite
bun run lint # oxlint
bun run fmt # oxfmt
bun run typecheck # TypeScript type checking
# Desktop distribution builds
bun run dist:desktop:dmg # macOS DMG
bun run dist:desktop:linux # Linux AppImage
bun run dist:desktop:win # Windows installerImportant: Run all four quality checks (
test,lint,fmtcheck,typecheck) in a single pass before opening a PR. Avoid repeatedly running individual checks during iteration — batch them at the end.
When running alongside an existing Peak Code instance, avoid port and state conflicts:
env -u PEAKCODE_AUTH_TOKEN \
PEAKCODE_PORT_OFFSET=3158 \
PEAKCODE_NO_BROWSER=1 \
bun run dev -- --home-dir ./.peakcode-dev --port 58090Always dry-run first:
env -u PEAKCODE_AUTH_TOKEN PEAKCODE_PORT_OFFSET=3158 \
bun run dev -- --home-dir ./.peakcode-pr84 --port 58090 --dry-runIf the UI shows no threads after connecting, the issue is likely a WebSocket auth mismatch or port collision — check your PEAKCODE_AUTH_TOKEN and port bindings before debugging SQL.
| Path | Role |
|---|---|
apps/server |
Node.js WebSocket server. Wraps Codex app-server (JSON-RPC over stdio), serves the React web app, manages provider sessions. |
apps/web |
React/Vite UI. Session UX, conversation rendering, client-side state. Connects via WebSocket. |
apps/desktop |
Electron desktop shell. Wraps the web app as a native desktop application. |
apps/marketing |
Marketing / landing page site. |
packages/contracts |
Effect-TS Schema definitions and TypeScript contracts. Schema-only — no runtime logic. |
packages/shared |
Runtime utilities consumed by server and web. Uses explicit subpath exports — no barrel index. |
packages/effect-acp |
Effect-TS ACP (Agents, Context, Policies) integration. |
The data flow follows a strict layered path:
Browser → WebSocket → wsServer → ProviderService → codex app-server
↓
Browser ← ServerPushBus ← OrchestrationEngine ← ProviderRuntimeIngestion
- User action in the browser becomes a typed WebSocket request via
WsTransport wsServerdecodes and routes the request using shared contracts frompackages/contracts/src/ws.tsProviderServicestarts/resumes a session and communicates withcodex app-servervia JSON-RPC over stdioProviderRuntimeIngestioningests provider-native events, normalizing them into orchestration eventsOrchestrationEnginepersists events, updates the read model, and exposes domain eventsServerPushBuspushes typed updates to the browser on channels defined inorchestration.ts
Long-running async flows run as queue-backed workers to keep side effects ordered and test synchronization deterministic:
- ProviderRuntimeIngestion — ingests provider runtime events
- ProviderCommandReactor — reacts to provider commands asynchronously
- CheckpointReactor — processes session checkpoint tasks
When a milestone completes, the server emits a typed receipt on RuntimeReceiptBus (e.g., checkpoint completion, turn quiescence). Tests wait on these receipts instead of polling.
- Checkpoint completion
- Turn quiescence
- Diff finalization
These signals are the only approved mechanism for async coordination. Do not poll internal state.
- No comments in code. Code should be self-documenting through clear naming, small functions, and Effect-TS typed pipelines. If something is hard to understand without a comment, refactor it.
- Prefer extraction over duplication. When adding functionality, check if shared logic can be extracted to a new module. Duplicate logic across multiple files is a code smell.
- No barrel index files. The
packages/sharedpackage uses explicit subpath exports (e.g.,@t3tools/shared/git). Do not addindex.tsbarrel files. - Don't be afraid to refactor. This is an early project. If existing code stands in the way of a clean solution, change it — but justify the refactoring in your PR.
- No emojis in code or UI unless the user explicitly requests them.
- Strict TypeScript. The project uses strict TypeScript with Effect-TS heavily. All new code must use Effect-TS patterns (Schema, Effect, Layer) where applicable.
- Effect-TS Schema (
packages/contracts) is the source of truth for all protocol types, WebSocket message formats, and domain events. Never duplicate type definitions. - Effect Layers are used for dependency injection. All services should be expressed as
Layers composed in the runtime graph. - No
any. If you need escape hatch, preferunknownwith proper type narrowing. If you must useany, explain why in the PR. - Avoid classes. Prefer Effect-TS functional patterns (pipes, generators, Layers) over OOP patterns.
- Zustand stores are the primary state management pattern. Do not introduce Redux, MobX, or other state libraries.
- Tailwind CSS for styling, with theme colors defined in the theme system. Do not use inline styles or CSS modules unless there is a measured performance case.
- No explicit color classes outside the theme (e.g.,
text-yellow-400is not allowed). Use theme tokens. - UI changes must include before/after screenshots in the PR. For motion/interaction changes, include a video.
- Components should be small and focused. If a component exceeds 200 lines, consider splitting it.
The project uses oxlint for linting and oxfmt for formatting (not Prettier, not ESLint).
bun run lint # oxlint
bun run fmt # oxfmt (auto-fix)
bun run fmt:check # oxfmt (check only)All code must pass bun run lint and bun run fmt:check before opening a PR.
- Vitest is the test framework (run via
bun run test, neverbun test). - Tests live alongside source files (co-located).
- Write tests for new functionality. If your PR introduces a new module, it must include tests.
- Update existing tests if your change affects behavior.
- Functional testing is the primary focus — the project values integration-level tests over pure unit tests, especially around the orchestration engine, WebSocket protocol, and background worker flows.
- Use runtime signals (
RuntimeReceiptBus) for test synchronization instead of arbitrary timers or polling.
- Open an issue first (or comment on an existing one) to discuss your proposed changes. This ensures your effort aligns with the project direction and avoids wasted work.
- Keep scope tight. One concern per PR. If you have multiple unrelated changes, open separate PRs.
- Follow the coding standards above. Code that does not meet these standards will not be merged.
- Small and focused. Prefer PRs under 200 lines. Large PRs are difficult to review and more likely to be rejected.
- Clear motivation. Explain what changed and why. If the change is non-obvious, include the reasoning.
- Tests included. New functionality must include tests. Bug fixes must include a regression test.
- Quality checks pass. Run
bun run test,bun run lint,bun run fmt:check, andbun run typecheckbefore opening the PR. - UI changes include screenshots. Before/after images are required. Videos for animation/interaction changes.
- Branch from
main. Name your branch descriptively (e.g.,fix-ws-reconnect,feat-thread-persistence).
Use the PR template at .github/pull_request_template.md. It asks for:
- What Changed — describe the change clearly and keep scope tight
- Why — explain the problem being solved and why this approach is right
- UI Changes — before/after screenshots (or delete section if not applicable)
- Checklist — small and focused, explanation provided, screenshots included
- Initial Review — A maintainer will review your PR. Response time depends on availability.
- Feedback Loop — The reviewer may request changes. Address all feedback. If you disagree, explain your reasoning — but be prepared to accept the reviewer's judgment on matters of architecture and project direction.
- Automated Checks — CI runs lint, typecheck, and the full test suite. All must pass before merge.
- Approval & Merge — Once approved and passing CI, a maintainer will merge your PR.
Note: Peak Code is a small team project. PRs may sit for a while before review. Please be patient. If a PR has been open for more than two weeks without any response, feel free to ping the thread politely.
The transcript (chat message list) is the most performance-sensitive component in the UI. When making changes to transcript rendering, scrolling, or state, follow these rules:
- Treat auto-scroll as a live-output feature, not a generic "working" indicator. Buffering, reconnecting, pending approvals, and tool-only activity must not trigger auto-scroll as if assistant text is actively streaming.
- Count real transcript messages only. Tool and work rows must not retrigger the "new content arrived" auto-stick path.
- Prefer the simpler fork-style transcript path for the common case. Small and medium transcripts should avoid virtualization churn unless there is a clear measured need.
- If virtualization is used, never couple
rowVirtualizer.measure()directly to another bottom-stick or height-follow cycle. Height-follow for live output must stay one-way to avoid measure/scroll feedback loops. - Preserve these behaviors with dedicated tests when changing chat scrolling, timeline measurement, or sidebar-driven transcript updates.
Violating these guardrails will cause review rejection. They exist because every prior violation caused observable jank in real-world usage.
When running a development instance alongside a production or another dev instance:
- Never start
bun run devblindly while another Peak Code instance is running unless you explicitly intend shared ports and state. - Use an isolated home directory and non-default ports. Example:
env -u PEAKCODE_AUTH_TOKEN PEAKCODE_PORT_OFFSET=3158 \ bun run dev -- --home-dir ./.peakcode-dev --port 58090
- Always dry-run first to detect conflicts:
env -u PEAKCODE_AUTH_TOKEN PEAKCODE_PORT_OFFSET=3158 \ bun run dev -- --home-dir ./.peakcode-pr84 --port 58090 --dry-run
- Unset
PEAKCODE_AUTH_TOKENfor browser dev instances unless the web app is configured to connect with that token. An auth mismatch will cause silent WebSocket rejection, and the UI will show no threads even though SQLite has data. - Check both server and web ports with
lsof -nP -iTCP:<port> -sTCP:LISTEN. A desktop app can bind127.0.0.1:<port>while the dev server binds IPv6*:<port>—localhostmay still hit the wrong process. - If the UI shows no threads, first verify the server path by inspecting the isolated
state.sqlite, then probeorchestration.getSnapshotover WebSocket. A healthy snapshot with projects/threads means the issue is client connection or hydration, not empty history.
This package is the single source of truth for:
- Provider event schemas
- WebSocket protocol message formats
- Model and session types
No runtime logic belongs here. If you need a utility function, put it in packages/shared.
This package exports shared runtime utilities. It does not use barrel index files. Import like this:
import { DrainableWorker } from "@t3tools/shared/DrainableWorker";
// NOT: import { DrainableWorker } from "@t3tools/shared";When adding a new module to packages/shared, add a corresponding entry in the package's exports field in package.json.
By submitting a pull request, you agree that your contributions will be licensed under the MIT License that covers this project. You represent that you have the right to grant this license.
If this is your first contribution to the project, the CLA bot will ask you to confirm acceptance. Reply with:
I have read the CLA Document and I hereby sign the CLA
This is recorded once per repository and does not need to be repeated for subsequent contributions.
Thank you for helping make Peak Code better. Every thoughtful contribution — whether a bug report, a documentation fix, or a code change — moves the project forward.