This file provides guidance to agents when working with code in this repository.
- Settings View Pattern: When working on
SettingsView, inputs must bind to the localcachedState, NOT the liveuseExtensionState(). ThecachedStateacts as a buffer for user edits, isolating them from theContextProxysource-of-truth until the user explicitly clicks "Save". Wiring inputs directly to the live state causes race conditions. - Changesets: Do NOT create
.changesetfiles for each commit or code change. Changesets are managed separately by maintainers and should not be generated by agents during normal development. - CHANGELOG: Do NOT update
CHANGELOG.mdorsrc/CHANGELOG.mdin individual PRs. CHANGELOG entries are added in bulk during release preparation PRs.
src/eslint-suppressions.json tracks per-file counts of suppressed lint rules. Suppression counts must never increase. When touching a file, prefer reducing its count when the fix is local and low-risk; avoid broad unrelated cleanup.
When writing new code:
- Fix lint violations in the new code rather than suppressing them.
- Avoid
as any; use typed APIs directly (e.g.RooCodeEventName.Xconstants with typedon()/listenerCount()), or bracket notation (obj["privateField"]) to access private members. Prefer precise test doubles orunknownwith a type guard over double assertions (as unknown as T); use double assertions only as a last resort, with a comment explaining why. - Avoid floating promises; add
void,await, or.catch()as appropriate. - After editing a file, run
pnpm --dir src exec eslint --prune-suppressions --max-warnings=0 <relative-file>and confirm the count for that file did not increase. - If a suppression is truly unavoidable (e.g.
vi.spyOn(Cls.prototype as any, "privateMethod")where no typed alternative exists), document why in a comment next to the cast.
New or substantially changed test files must not add @typescript-eslint/no-explicit-any suppression entries to src/eslint-suppressions.json:
- Prefer typed structural doubles: type mock context/proxy classes against the real public types they stand in for (e.g.
vscode.ExtensionContext), and useunknownfor storage values and mock callback parameters instead ofany. - Access private members with bracket notation (
provider["view"]) and call public members without casts so their generic signatures keep checking arguments. - When a partial double cannot be assigned to the real type, cast it once at the construction site (
as unknown as T) with a comment naming the members the double provides; do not repeatas anyat every use site. - Validate with
pnpm --dir src run check-types, the narrowest affected Vitest suite, andpnpm --dir src exec eslint --prune-suppressions --max-warnings=0 <relative-test-file>; the run must not add a new entry or raise an existing count. - An exception requires a comment next to the cast explaining why no typed alternative exists and maintainer approval in review.
When adding or changing a user setting, trace the complete round trip. A setting is not complete merely because its control renders or its value reaches storage.
- Define the setting, validation, and optionality in
packages/types/src/global-settings.ts(or the appropriate provider/settings schema). Define a shared default constant when multiple readers need the same default. - If the webview uses the setting, include it in
ExtensionStateinpackages/types/src/vscode-extension-host.tsand any relevant message types. - In
SettingsView, initialize and read the control from localcachedState, NOT directly from liveuseExtensionState(). The cache buffers edits until the user explicitly clicks Save; binding to live state causes races and discarded edits. - Update
cachedStatefrom the control and include the setting in theupdateSettingspayload sent bySettingsView.handleSubmit()(or document and test a deliberate immediate-save flow). - Verify
webviewMessageHandlerhandles any setting-specific normalization or side effects and persists the final value throughContextProxy. Generic settings normally usecontextProxy.setValue(). - Add the setting to
ClineProvider.getState()with the intended default so extension/runtime consumers can read it. - Add the setting to both the destructuring and returned object in
ClineProvider.getStateToPostToWebview(). This completes the storage-to-webview round trip and prevents a saved control from reverting visually. - Update every runtime consumer and ensure all consumers use the same default semantics.
- If users can import/export the setting, verify its schema inclusion makes it round-trip and add special handling only when required (for example, secrets or non-exportable state).
- Add focused tests for: UI binding/save behavior, persistence or normalization, and the saved value returned by
getStateToPostToWebview(). Include bothtrueandfalse/unset cases when defaulting can hide omissions. - Run the narrowest relevant Vitest suites from the package directory that declares Vitest.
Prefer the narrowest test layer that proves the behavior. This follows standard test-pyramid guidance: keep most coverage in fast, focused tests; add integration tests for cross-module contracts; reserve end-to-end tests for full workflow confidence.
- Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling.
- Use integration tests when behavior depends on multiple internal modules working together, but does not require the real VS Code extension host or browser/webview runtime.
- Use
webview-uitests for React rendering, hooks, component state, forms, validation, and webview UI wiring. - Use
apps/vscode-e2eonly when the behavior depends on the real VS Code extension host, VS Code workspace APIs, extension activation, webview/extension messaging, file watcher behavior, or a complete user workflow. - Keep e2e tests focused on high-value smoke coverage across boundaries. Avoid placing detailed protocol, parsing, storage, retry, or edge-case assertions in e2e when they can be covered reliably at a lower layer.
- When fixing a regression, add the regression test at the lowest layer that would have failed for the bug. Add an e2e test only if lower-level tests cannot represent the failure mode.
- For a guard or validator, test the accept path and the reject path through every public entry point. A test that only rejects bad input does not prove the guard still accepts good input.
- Use
it.eachfor repeated cases and share the setup in a helper. Do not copy a test body per input. - A
Stryker disablecomment states the reason in one sentence. ESLintmax-lenlimits it to 300 characters.
- Read
docs/architecture/task-lifecycle-model.mdbefore changing task status, delegation, interruption, completion, abandonment, persistence ownership, or scheduler fan-out behavior. - Keep lifecycle mutations in the shared reducers under
src/core/task-persistence/taskLifecycle.ts. Update model actions, invariants, or named semantic landmarks for every new transition or concurrency bug class representable in the lifecycle model, then runpnpm lifecycle:model-check. - The in-memory run-state submodel lives in
src/core/task/run-state/runState.ts. It is a purenextRunState(state, event)kernel, separate from the persisted reducers. Readdocs/architecture/task-lifecycle-target-architecture.mdbefore changing it. - Add extension-host E2E coverage only for a boundary the reducer model cannot prove, such as restart visibility, real persistence/rehydration, delayed provider streams, scheduler permits, or webview task scoping. Do not duplicate reducer interleavings in E2E.
- Run the focused lifecycle tests and
pnpm testbefore completing a Zoo Code lifecycle change.
- Use
src/test-utils/stream.tsfor mechanical async-stream setup and collection. - Use the typed helpers in
src/test-utils/api.ts,src/test-utils/fs.ts,src/test-utils/reset.ts, andsrc/test-utils/vscode.tswhen they remove repeated setup without hiding the scenario. - Keep provider-specific payloads, failure streams, and assertions inline when they explain the behavior under test.
- Prefer shared helpers for mechanical duplication; use fixtures only when setup is reusable, typed, and independently disposable.
- New helpers must preserve failure clarity, return fresh objects, and avoid
as any; keep unavoidable VS Code structural casts inside the helper with a brief explanation.