Repo hygiene: dropped dead React-hooks dir, locked the package manager to npm, madenpm run buildrebuilddist/from scratch, added a command registry as the single source of truth for autocomplete +/help+ dispatch validation, lifted ACP test coverage from 0% to ~45% on the testable surface, removed allas anycasts from the settings screen, upgradedconfto v13, fixed the tsconfig sotsc --noEmitis now genuinely clean (was silently emitting 445+ errors), patched the high-severityviteadvisory, switched CI from bun to npm, addedCONTRIBUTING.md+SECURITY.md, extracted the synchronous ACP handlers + pure helpers out of thestartAcpServer()closure into a testableserverHandlers.tsmodule (89.7% line coverage on the extracted surface; 0 → 25.7% onserver.ts), migratedrelease-binaries.ymlfrom bun to npm (keeping bun only for thebun build --compilebinary step), removed the deprecated + vulnerablepkgdevDependency (0 high/critical advisories remaining), and hardened the build withnoEmitOnError: true.
Removed
src/hooks/deleted. This directory (index.ts+useAgent.ts) was a
leftover from the old Ink-based TUI: it imported react (which is not in package.json) and was kept out of tsc via an exclude in tsconfig.json, so the project type-checked only because the dead code was hidden. Nobody imported it (grep "from.hooks/" src/ returned zero hits), and it referenced a runAgent signature that no longer exists. The src/hooks/*/* exclude entry is removed from tsconfig.json — tsc --noEmit is now clean with nothing hidden.
Changed
- Single package manager: npm.
bun.lockis removed andpackage-lock.json
is now the canonical lockfile (previously both were git-ignored and drifted). .gitignore no longer ignores package-lock.json; other lockfiles (yarn.lock, pnpm-lock.yaml, bun.lock*) stay ignored.
/autocomplete trimmed. Five rarely-tab-completed utility/legacy
commands were dropped from the / dropdown — /clear, /exit, /context-save, /context-load, /context-clear — to cut clutter. They remain fully dispatchable and listed in /help. (/sessions was kept in the dropdown as a primary navigation command.)
npm run buildnow wipesdist/first (rm -rf dist && tsc …). The
bun build --compile binary step reads from dist/, so a stale dist/ (e.g. leftover output from the now-deleted src/hooks/ dir, or a renamed module) could ship outdated JS. A clean rebuild on every build closes that hole.
src/acp//.ts added to the Vitest coverage scope* so the editor-
integration layer is measured alongside utils/, api/, and config/.
confupgraded 12 → 13 (^13.1.0). BringsrootSchema/ajvOptions
options, a .delete() dot-notation typing fix, and updated transitive deps (dot-prop 8→9, ajv patch bumps). Held below v14 pending a separate engine/compat review — the v13 bump was all this release needed.
tsconfig.jsonnow declares"types": ["node"]. Without it, `tsc
--noEmit was silently emitting 445+ TS2591: Cannot find name 'fs'/'path'/ 'process'/… errors across 86 files — npm run build appeared to succeed only because the config doesn't set noEmitOnError, so the broken JS was written to dist/ alongside the errors. With types: ["node"]` the type check is genuinely clean (0 errors). This was a long-standing latent issue masked by the emit-on-error behaviour.
npm audit fixpatched the high-severityviteadvisory
(GHSA-v6wh-96g9-6wx3 / GHSA-fx2h-pf6j-xcff — launch-editor UNC path + server.fs.deny bypass on Windows) by bumping the transitive vite brought in by vitest. One advisory remains: a single low in esbuild (GHSA-g7r4-m6w7-qqqr — dev-server-only arbitrary file read on Windows), pulled in transitively by vitest/tsx, with no fix short of a major bump of those dev tools. Removing pkg (below) cleared the prior moderate advisory, so npm audit now reports 0 high/critical (1 low total).
Added
- Command registry — single source of truth for slash-command metadata
(src/renderer/commands/registry.ts). Previously three places carried the same data and drifted: the COMMAND_DESCRIPTIONS record in App.ts (123 hand-typed rows driving / autocomplete), the helpCategories array in components/Help.ts (hand-typed /help rows), and the case labels in renderer/commands.ts + acp/commands.ts. Now App.ts and Help.ts both derive from the registry, and two new tests (registry.test.ts) enforce the invariant at build time: every top-level case label in either dispatcher must exist in the registry — adding a case 'foo': without a registry entry now fails CI. The registry also exposes resolveCommand(), ALL_COMMAND_NAMES, and ALL_ALIASES so the dispatchers themselves can move to registry lookups incrementally.
- ACP layer test coverage. Two new test files lift the previously
untested ACP adapter from 0% to meaningful coverage: - src/acp/session.test.ts (35 tests) covers buildProjectContext, toolCallMeta (every tool → ACP kind mapping), and buildRawOutput (diff formatting for edits, stdout surfacing for commands, error paths). toolCallMeta and buildRawOutput were promoted from module-private to exported specifically so they can be tested in isolation. - src/acp/commands.test.ts (10 tests) covers initWorkspace — the filesystem bootstrap that runs on every ACP session/new: .codeep/ creation, project initialization, the read+write permission grant, and the informed-consent banners for custom slash commands and lifecycle hooks. Uses an isolated tmpdir per test (same pattern as checkpoints.test.ts). - Together they catch regressions in the data-shaping surface and the onboarding flow without mocking the agent loop, which is exercised end-to-end by toolExecution.test.ts.
- Settings screen: all 7
as anycasts removed. Previously every
config.set/config.get call in Settings.ts used setting.key as any because SettingItem.key was typed as string rather than keyof ConfigSchema. Now: - ConfigSchema is exported from config/index.ts so consumers can reference its keys. - SettingItem.key is typed as keyof ConfigSchema — a typo'd or unknown setting key is now a compile error, not a silent runtime no-op. - All writes go through a single typed writeSetting(setting, value) helper. The unavoidable as ConfigSchema[K] cast lives in exactly one audited spot instead of seven call sites, and config.get(setting.key) no longer needs any cast. - 13 new tests (Settings.test.ts) pin the write path: number editing + clamping, select cycling (forward/backward/wrap), escape-abort, and the updateRateLimits() side effect for the rate-limit settings.
CONTRIBUTING.md+SECURITY.mdadded. Previously the repo had
neither — the README's "Contributing" section was one sentence pointing at GitHub issues. The new files cover: - CONTRIBUTING.md: the npm-based setup, the test/build/type-check loop (including "what gets a test" guidance), code style, an architecture reading order for new contributors, the provider-integration flow, and the release pipeline. - SECURITY.md: how to report a vulnerability (GitHub Security Advisories preferred), what's in/out of scope for an agent that can edit files and run shell commands, and a list of the hardening features already in place (project permissions, confirmation modes, hook trust gate, keychain storage, telemetry opt-out) so contributors don't regress them. - The README's "Contributing" section now links to both.
- CI workflow switched from bun to npm (
.github/workflows/ci.yml).
The workflow used bun install --frozen-lockfile, but bun.lock was removed and .gitignored when npm became canonical (#2), so the gate would fail on a clean checkout. Now uses actions/setup-node@v5 with Node 20 + npm ci + npx tsc --noEmit + npm test, consistent with CONTRIBUTING.md. release-binaries.yml still uses bun (its bun build --compile step produces the cross-platform binaries — that's tracked as a separate migration, not safe to flip blindly).
server.tsrefactor: extracted handlers + pure helpers to module scope.
startAcpServer() was a ~1300-line closure whose 12+ request handlers (session/set_mode, session/set_config_option, session/list, session/delete, …) were unreachable by unit tests because they captured the live stdio transport. The refactor lifts them into two testable surfaces: - server.ts module-scope exports — the pure helpers (formatToolInputForPermission, resolveLocalPath, collectEmbeddedContext, providerHasKey, buildConfigOptions, AGENT_MODES) now carry export and are covered by server.test.ts. - New serverHandlers.ts module — the four synchronous session handlers (handleSetMode, handleSetConfigOption, handleSessionList, handleSessionDelete) plus a pure applyConfigOption helper, each taking an explicit (msg, deps) pair where deps = { transport, sessions }. The transport is stubbed in tests via a recorded-call array, which is what makes the assertions readable. - startAcpServer() now constructs one handlerDeps object and delegates to the extracted functions, keeping the dispatch loop intact. - The async handlers (handleSessionPrompt, handleSessionNew, handleSessionLoad, handleSessionResume, image/vision, agent loop) stay in the closure for now — they reach into MCP spawning, the keychain, and runAgentSession, each of which needs its own extraction pass. Tracked as a follow-up. - Coverage: serverHandlers.ts at 89.7% lines / 87% statements; server.ts up from 0% to 25.7%. 56 new tests across server.test.ts (34) and serverHandlers.test.ts (22).
release-binaries.ymlmigrated from bun to npm (build + publish-npm
jobs). bun install → npm ci, bun run → npm run, setup-bun → setup-node@v5 (Node 20 build, Node 24 publish). Only the bun build --compile step keeps bun — it produces the distributed standalone binaries and has no npm-side equivalent (the deprecated pkg was never wired into CI). This makes CI + release consistent with the canonical npm toolchain (#8 fixed the same drift in ci.yml).
- Removed deprecated
pkgdevDependency.pkgwas unmaintained since
2023 (archived by Vercel), carried a moderate-severity advisory, and its build:binary script was never invoked by any workflow — the release pipeline uses bun build --compile instead. Removed pkg, the build:binary script, and the top-level "pkg" config block from package.json. npm audit now reports 0 high/critical (was 1 high). @yao-pkg/pkg (the active fork) stays available if we ever need an npm-side path to standalone binaries.
- Added
noEmitOnError: truetotsconfig.json. Now thattsc --noEmit
is clean (0 errors), this flag hardens the build: tsc will refuse to emit dist/ if a type error slips in, so a broken build can no longer be masked by a stale dist/. Prevents regressions of the kind that #7 fixed (445+ errors silently emitted for months).
- **Lifted
handleListProviders+buildProviderListinto
serverHandlers.ts. The provider-catalog handler was the simplest remaining async-safe handler — pure shape mapping over PROVIDERS + a single transport.respond. Extracted as buildProviderList() (pure, testable shape) + handleListProviders(msg, deps) (thin wrapper). 5 new tests pin the shape including dynamicModels flagging for open-ended providers (OpenRouter, Ollama). serverHandlers.ts coverage now at 89.7% lines**.