Development
See AGENTS.md for additional guidelines.Setup
scripts/*.ts. Avoid yarn and pnpm. Run package scripts from the monorepo root or a package directory, for example:
Forking / Rebranding
Configure viapackage.json:
name, configDir, and the bin field for your fork. The app-specific <appName>Config key is preferred; legacy piConfig remains a backwards-compatible shim. Atomic sets these to atomic, .atomic, and the atomic executable. Affects CLI banner, config paths, and environment variable names.
Path Resolution
Three execution modes: package-manager install, standalone binary, and source checkout. Always usesrc/config.ts for package assets:
__dirname directly for package assets.
Debug Command
/debug (hidden) writes to ~/.atomic/agent/atomic-debug.log:
- Rendered TUI lines with ANSI codes
- Last messages sent to the LLM
Startup timing probes
Usescripts/perf/windows-startup/benchmark.ts for Windows startup claims. It launches the ordinary bare atomic command through a real 120x40 ConPTY, feeds ordered output into @xterm/headless, and timestamps each receive with process.hrtime.bigint(). Complete first paint requires the final Atomic v<version> identity, the focused ❯ editor, and two identical settled frames at least one 80 ms animation interval apart. dispatchMs runs from the Enter write to the first byte observed by a raw TCP loopback provider. The headline spawnToDispatchMs is exactly startupCompleteMs + dispatchMs; launchToProviderFirstByteMs separately retains the contiguous launch-to-provider interval that also contains nonce typing and editor-echo wait. The provider request must contain the nonce and the normal tool schemas, and every accepted sample must pass /workflow list after the timed response. See the benchmark README for artifact preparation, cache profiles, raw records, and summary commands.
Do not use time-to-first-frame as settled-paint evidence, and do not substitute launchToProviderFirstByteMs for the contract sum. The first-frame mark records the host’s first requested identity frame after the header is mounted; it does not prove terminal receipt, animation settlement, engine resource readiness, or provider readiness. ATOMIC_STARTUP_BENCHMARK=1, --no-extensions, and --no-tools are attribution controls only. They do not run the accepted full CLI path with bundled workflows, normal extensions, tools, and provider dispatch.
The process-local lifecycle timing seams use monotonic nanoseconds and remain disabled until an internal diagnostic adapter installs a synchronous sink. With no sink, they do not read the clock, write output, or schedule work. External ConPTY and TCP marks remain authoritative. Startup now has several deliberate partial orders rather than one cross-process sequence:
engine-bound; the child can therefore report binding while the host is applying its theme or requesting that frame. engine-bound means RPC control plus the mandatory minimal session are available. engine-resources-ready is a separate, generation-scoped child message sent only after a staged optional snapshot containing bundled extensions, tools, providers, skills, prompts, and themes commits. User prompts, extension commands, model/resource commands, session replacement, and tool-dispatching RPC calls wait for that gate. After a readiness failure, /reload bypasses the rejected gate to start a fresh transactional attempt; successful retry replaces the gate before later work proceeds. Escape cancels a first submission that is still waiting, so that continuation cannot dispatch later. session_start messages are queued against the candidate snapshot and released only after publication. Mandatory Intercom remains in the minimal runtime. A failed transactional candidate leaves host-managed settings, providers, tools, resources, event subscriptions, and system-prompt state unchanged, restores an unadmitted prompt draft exactly, and reports the generation failure once. Extension-owned objects stored with sessionScopedExtensionState are deliberately shared across reloads and are not cloned or rolled back.
engine-resources-ready is recorded in the isolated engine process; host marks are recorded in the interactive process. The two process clocks are monotonic but must not be subtracted without an external synchronization protocol. startup-coherent and startup-complete describe internal render composition, not terminal receipt. The external VT predicates decide the reported first-paint marks.
For package-manager installs under Node 22, Atomic enables Node’s persistent module compile cache in both the host and isolated child and flushes the host cache before spawning the child. Explicit NODE_COMPILE_CACHE and NODE_DISABLE_COMPILE_CACHE settings pass through unchanged. This preserves Node’s coverage opt-out and avoids forcing a new cache directory or a first-run-only precompile step. SEA, V8 snapshots, and package-install precompilation were not adopted because Atomic’s dynamic ESM, native modules, workers, and first-run requirements do not provide a safe portable boundary.
Compiled releases syntax-minify the shared CJS app.js sidecar without identifier minification and compile launchers with bytecode on all eight supported targets, including Windows x64 and ARM64. bun run scripts/probe-windows-bytecode.ts pins Bun 1.4.0, cross-compiles bun-windows-x64-baseline and bun-windows-arm64 launchers, and verifies their PE machine types. Atomic observed a Bun 1.3.14 Windows startup crash in llint_entry, but Bun 1.4.0 already contains the embedded-bytecode alignment fix (#26299) and integrity fallback (#31961); the separate Bun 1.4.0 Windows report (#40302) concerns a general standalone/JIT segfault and has not been shown to be bytecode-specific. Cross-compilation is not runtime validation: release candidates still require full-archive target-machine coverage of the TUI, workflows, tools, extensions, workers, and native add-ons, with Windows ARM64 validated on ARM64 hardware.
Set ATOMIC_TIMING=1 only for the older human-readable phase diagnostics. Normal interactive launches print that initial timing group before interactiveMode.run() starts the TUI loop, so later marks are not printed during ordinary sessions.
Testing
Deterministic installs
@bastani/atomic ships packages/coding-agent/npm-shrinkwrap.json so package-manager installs resolve the same dependency tree every time. Contributors working from a source checkout can validate that the checked-in shrinkwrap is up to date with:
Release security boundary
Atomic’s release bases remain at the0.0.0 placeholder. scripts/cut-release.ts stamps the real version only on a detached tagged release commit. Tag creation runs an inert signal workflow; a separate workflow_run publisher loaded from protected main validates the exact upstream repository, source workflow/event/run, tag/SHA, immutable release-base trailers, and deterministic release tree. The privileged trigger checks out only protected workflow code: it treats the tag tree as data, exports it only after deterministic verification, and makes every read-only build verify the protected job’s source checksum instead of checking out tag-selected code. Same-run artifact transport failures receive at most one retry after partial-download cleanup and still fail explicitly on the second error; verified source archives are streamed to tar over stdin for portable Windows drive-letter handling. Preparation restores the digest-verified source after documentation validation before producing artifacts. Release-source jobs configure no dependency cache, npm publication has OIDC without repository write, and GitHub Release creation has repository write without OIDC. Never move or recreate a failed release tag or dispatch the privileged publisher. See the repository’s CI/CD pipeline for trusted-publisher configuration.