This file is the secret sauce for working effectively in this codebase. It captures tribal knowledge—the nuanced, non-obvious patterns that make the difference between a quick fix and hours of back-and-forth & human intervention.
When to add to this file:
Proactively suggest additions when any of the above happen—don't wait to be asked.
What NOT to add: Stuff you can figure out from reading a few files, obvious patterns, or standard practices. This file should be high-signal, not comprehensive.
package.json for available scripts before trying to verify builds (e.g., npm run compile, not npm run build).npm run changeset and create a patch changeset. Never create minor or major version bumps. Skip changesets for trivial fixes, internal refactors, or minor UI tweaks that users wouldn't notice.The extension and webview communicate via gRPC-like protocol over VS Code message passing.
Proto files live in proto/ (e.g., proto/cline/task.proto, proto/cline/ui.proto)
.proto fileproto/cline/common.proto (StringRequest, Empty, Int64Request).proto filePascalCaseService, RPCs camelCase, Messages PascalCasestream keyword (see subscribeToAuthCallback in account.proto)Run npm run protos after any proto changes—generates types in:
src/shared/proto/ - Shared type definitionssrc/generated/grpc-js/ - Service implementationssrc/generated/nice-grpc/ - Promise-based clientssrc/generated/hosts/ - Generated handlersAdding new enum values (like a new ClineSay type) requires updating conversion mappings in src/shared/proto-conversions/cline-message.ts
Adding new RPC methods requires:
src/core/controller/<domain>/UiServiceClient.scrollToSettings(StringRequest.create({ value: "browser" }))Example—the explain-changes feature touched:
proto/cline/task.proto - Added ExplainChangesRequest message and explainChanges RPCproto/cline/ui.proto - Added GENERATE_EXPLANATION = 29 to ClineSay enumsrc/shared/ExtensionMessage.ts - Added ClineSayGenerateExplanation typesrc/shared/proto-conversions/cline-message.ts - Added mapping for new say typesrc/core/controller/task/explainChanges.ts - Handler implementationwebview-ui/src/components/chat/ChatRow.tsx - UI renderingThis is tricky—multiple prompt variants and configs. Always search for existing similar tools first and follow their pattern. Look at the full chain from prompt definition → variant configs → handler → UI before implementing.
ClineDefaultTool enum in src/shared/tools.tssrc/core/prompts/system-prompt/tools/ (create file like generate_explanation.ts)
ModelFamily (generic, next-gen, xs, etc.)export const my_tool_variants = [GENERIC, NATIVE_NEXT_GEN, XS])ClineToolSet.getToolByNameWithFallback() automatically falls back to GENERIC. So you only need to export [GENERIC] unless the tool needs model-specific behavior.src/core/prompts/system-prompt/tools/init.ts - Import and spread into allToolVariantssrc/core/prompts/system-prompt/variants/*/config.ts. Add your tool's enum to the .tools() list:
generic/config.ts, next-gen/config.ts, gpt-5/config.ts, native-gpt-5/config.ts, native-gpt-5-1/config.ts, native-next-gen/config.ts, gemini-3/config.ts, glm/config.ts, hermes/config.ts, xs/config.tssrc/core/task/tools/handlers/ToolExecutor.ts if needed for execution flowsrc/core/assistant-message/index.ts if neededClineSay enum in proto, update src/shared/ExtensionMessage.ts, update src/shared/proto-conversions/cline-message.ts, update webview-ui/src/components/chat/ChatRow.tsxRead these first: src/core/prompts/system-prompt/README.md, tools/README.md, __tests__/README.md
System prompt is modular: components (reusable sections) + variants (model-specific configs) + templates (with {{PLACEHOLDER}} resolution).
Key directories:
components/ - Shared sections: rules.ts, capabilities.ts, editing_files.ts, etc.variants/ - Model-specific: generic/, next-gen/, xs/, gpt-5/, gemini-3/, hermes/, glm/, etc.templates/ - Template engine and placeholder definitionsVariant tiers (ask user which to modify):
next-gen/, native-next-gen/, native-gpt-5/, native-gpt-5-1/, gemini-3/, gpt-5/generic/xs/, hermes/, glm/How overrides work: Variants can override components via componentOverrides in their config.ts, or provide a custom template in template.ts (e.g., next-gen/template.ts exports rules_template). If no override, the shared component from components/ is used.
Example: Adding a rule to RULES section
rules_template in variants/*/template.ts or componentOverrides.RULES in config.tscomponents/rules.tstemplate.tsAfter any changes, regenerate snapshots:
UPDATE_SNAPSHOTS=true npm run test:unit
Snapshots live in __tests__/__snapshots__/. Tests validate across model families and context variations (browser, MCP, focus chain).
Three places need updates:
src/core/slash-commands/index.ts - Command definitionssrc/core/prompts/commands.ts - System prompt integrationwebview-ui/src/utils/slash-commands.ts - Webview autocompleteWhen a ChatRow displays a loading/in-progress state (spinner), you must handle what happens when the task is cancelled. This is non-obvious because cancellation doesn't update the message content—you have to infer it from context.
The pattern:
status field (e.g., "generating", "complete", "error") stored in message.text as JSON"generating" forever—no one updates it!isLast — if this message is no longer the last message, something else happened after it (interrupted)lastModifiedMessage?.ask === "resume_task" || "resume_completed_task" — task was just cancelled and is waiting to resumeExample from generate_explanation:
const wasCancelled =
explanationInfo.status === "generating" &&
(!isLast ||
lastModifiedMessage?.ask === "resume_task" ||
lastModifiedMessage?.ask === "resume_completed_task")
const isGenerating = explanationInfo.status === "generating" && !wasCancelled
Why both checks?
!isLast catches: cancelled → resumed → did other stuff → this old message is stalelastModifiedMessage?.ask === "resume_task" catches: just cancelled, hasn't resumed yet, this message is still technically "last"See also: BrowserSessionRow.tsx uses similar pattern with isLastApiReqInterrupted and isLastMessageResume.
Backend side: When streaming is cancelled, clean up properly (close tabs, clear comments, etc.) by checking taskState.abort after the streaming function returns.