PromptBuilder:システムプロンプトの組み立てライン
役割
PromptBuilder は、Cline が「provider/model を取り出した後」にそのシステムプロンプト (system prompt) を実際に組み立てるクラスである。PromptRegistry.get が必要なタイミングで new して使う (new PromptBuilder:92)。3 つの入力を取る。1 つは PromptVariant (テンプレートとコンポーネント順序を決定)、1 つは SystemPromptContext (実行時コンテキスト、cwd / mcp / rules / skills 等を保持)、1 つは ComponentRegistry (コンポーネント関数テーブル)。最終的に 1 つの文字列を吐き出し、これが Task の attemptApiRequest で ApiHandler.createMessage に渡す systemPrompt になる (getSystemPrompt call:2350)。
PromptRegistry との役割分担はこうだ。Registry は providerInfo に基づいてどの variant を使うかマッチングし、コンポーネントテーブルを管理する。Builder は variant の baseTemplate と componentOrder を実際に走査し、各コンポーネント関数の結果をテンプレートのプレースホルダに流し込み、後処理で空セクションを整理する (build:23)。端的に言えば、Registry がテンプレートを選び、Builder がテンプレートを埋める。
設計動機
- コンポーネント化による巨大文字列の回避:システムプロンプトは
AGENT_ROLE / TOOL_USE / TASK_PROGRESS / MCP / EDITING_FILES / ACT_VS_PLAN / CAPABILITIES / RULES / SYSTEM_INFO / OBJECTIVE / USER_INSTRUCTIONS / SKILLSなどのコンポーネントに分割される (components:44)。各コンポーネントは独立した関数で、文字列または undefined を返し、順序は variant が決める。これによりモデルファミリーごとに一部のコンポーネントだけ差し替え、他は再利用できる。 - placeholder + TemplateEngine:
baseTemplateには/ /といったプレースホルダを書く。Builder はまずコンポーネント結果をcomponentSectionsに詰め、次に標準 placeholder (cwd / currentDate / modelFamily) を一括で流し込み (preparePlaceholders:55)、最後にTemplateEngine.resolveで置換を行う。 - variant matcher でテンプレート選択:
PromptRegistry.getModelFamilyは登録済みの全 variant を走査し、各 variant のmatcher(context)を呼び、最初に true を返したものが勝つ (getModelFamily:42)。generic variant がフォールバック。 - runtimePlaceholders が最優先:context に生やす
runtimePlaceholdersが最後のObject.assignで全値を上書きする (runtime placeholders:79)。これにより実行時に動的計算されるフィールドが variant のデフォルト値を強制上書きできる。 - 後処理でのクリーンアップ:
postProcessは一連の正規表現で連続する空行を 2 つに圧縮し、空 section ヘッダ##や空=====区切り線を取り除く (postProcess:86)。コンポーネントを動的に組み立てると空セクションが残りやすく、放置するとプロンプトが乱雑になるため。 - ツールプロンプトの独立エントリ:
getToolsPromptsは static メソッドで、variant が「ツール XML schema」セクションを組み立てるために専用で呼ぶ (getToolsPrompts:139)。ClineToolSet.getEnabledToolSpecsの結果を<tool_name>...</tool_name>形式のプロンプトテキストに変換する。
主要ファイル
PromptBuilder class:12— variant、context、components、templateEngine の 4 つの依存を持つ。build method:23— 主エントリ、buildComponents → preparePlaceholders → templateEngine.resolve → postProcessの 4 ステップ。buildComponents:30—variant.componentOrderの順に各コンポーネント関数を呼び、結果を sections に格納。preparePlaceholders:55— variant placeholders + 標準 placeholder + コンポーネント sections + runtime placeholders の 4 層上書き。postProcess:86— 一連の正規表現で空行・空 section・空区切りを整理。diff 用の=====は保持。getToolsPrompts:139— static メソッド。有効なツールをプロンプトテキストに変換し、ツールごとに## tool_nameセクションを生成。tool method:146— 単一ツールのプロンプト構築。dependencies と contextRequirements を先にフィルタし、Parameters / Usage セクションを組み立て。buildUsageSection:210—<tool><param>...</param></tool>の XML 利用例を出力。PromptRegistry.get:86—getVariantで variant を選び、nativeTools を算出してnew PromptBuilder(...).build()を呼ぶ。getModelFamily:42— variants を走査して matcher を呼び、最初にマッチしたものが勝ち、さもなくば GENERIC にフォールバック。generic components:44— フォールバック variant のコンポーネント順序。ほぼ全モデルがデフォルトで这套を使用。SystemPromptContext:94— コンテキスト型定義。providerInfo、cwd、mcpHub、skills、rules、browserSettings など数十のフィールドを持つ。
データフロー
Builder の中心は「コンポーネント関数を順に呼び、結果を sections に集め、テンプレートエンジンに渡す」である:
// apps/vscode/src/core/prompts/system-prompt/registry/PromptBuilder.ts
async build(): Promise<string> {
const componentSections = await this.buildComponents()
const placeholderValues = this.preparePlaceholders(componentSections)
const prompt = this.templateEngine.resolve(this.variant.baseTemplate, this.context, placeholderValues)
return this.postProcess(prompt)
}
private async buildComponents(): Promise<Record<string, string>> {
const sections: Record<string, string> = {}
const { componentOrder } = this.variant
// Process components sequentially to maintain order
for (const componentId of componentOrder) {
const componentFn = this.components[componentId]
if (!componentFn) {
Logger.warn(`Warning: Component '${componentId}' not found`)
continue
}
try {
const result = await componentFn(this.variant, this.context)
if (result?.trim()) {
sections[componentId] = result
}
} catch (error) {
Logger.warn(`Warning: Failed to build component '${componentId}':`, error)
}
}
return sections
}buildComponents は for await で順次実行され、Promise.all ではない。これはコンポーネント間に順序依存があるため (例えば OBJECTIVE は前のコンポーネントが組み立てた内容を参照する)、順序実行で保証する (sequential for:35)。単一コンポーネントが例外を投げても warn だけで全体構築は中断せず、欠落したコンポーネントは空のプレースホルダとして後で空文字列に置換される。
preparePlaceholders の上書き順序は重要で、後書きが前書きを上書きする:
// apps/vscode/src/core/prompts/system-prompt/registry/PromptBuilder.ts
const placeholders: Record<string, unknown> = {}
Object.assign(placeholders, this.variant.placeholders) // 1. variant デフォルト値
placeholders[STANDARD_PLACEHOLDERS.CWD] = this.context.cwd || process.cwd() // 2. 標準フィールド
placeholders[STANDARD_PLACEHOLDERS.MODEL_FAMILY] = this.variant.family
placeholders[STANDARD_PLACEHOLDERS.CURRENT_DATE] = new Date().toISOString().split("T")[0]
Object.assign(placeholders, componentSections) // 3. コンポーネント産物
for (const key of STANDARD_PLACEHOLDER_KEYS) {
if (!placeholders[key]) {
placeholders[key] = componentSections[key] || "" // 4. 欠落した標準フィールドは空で補完
}
}
const runtimePlaceholders = (this.context as any).runtimePlaceholders
if (runtimePlaceholders) {
Object.assign(placeholders, runtimePlaceholders) // 5. 実行時上書きが最優先
}
return placeholdersこの層状の設計により、「variant デフォルト値 → 標準フィールド → コンポーネント産物 → runtime 強制上書き」の優先順位が明確で、どの層を変更しても他層を意図せず壊すことはない。
境界と失敗
- コンポーネントが undefined を返す:
result?.trim()で空判定し、非空文字列だけ sections に入る (trim check:44)。そのためコンポーネントは内容を任意に返せる (例:supportsBrowserUse=falseのときBROWSERセクションは空を返す)。 - コンポーネントが例外を投げる:catch して
Logger.warnだけで再送出せず、他のコンポーネントは継続実行 (catch warn:47)。単一コンポーネントのバグでシステムプロンプト全体が崩れるのを防ぐ。 - コンポーネント未登録:
components[componentId]が取れないときは warn 後 continue する (not found warn:37)。対応するプレースホルダはpreparePlaceholdersで空文字列に補完される。 - variant が見つからない:
PromptRegistry.getVariantは全 variant がマッチせず GENERIC フォールバックもない場合に直接例外を投げる (no variant throw:71)。ただし GENERIC variant はデフォルトで登録されるため、通常ここには到達しない。 - matcher が例外を投げる:
getModelFamilyはv.matcher(context)を try/catch で囲み、失敗すれば次の variant に進む (matcher catch:51)。ある variant のバグが選択全体を引きずり下ろさないようにする。 - diff 用
=====の誤削除:postProcessは=====を置換する前後に 50 文字のコンテキストを見て、SEARCH / REPLACE / ++++ / ----といった diff マーカーであれば触らない (diff guard:106)。replace_in_fileツールプロンプト内の diff 区切り線を誤削除しないため。 - nativeTools の重複計算:
PromptRegistry.getのthis.nativeTools = ClineToolSet.getNativeTools(variant, context)は side effect であり (nativeTools side effect:90)、コメントには「Hacky way」と明記されている。systemPrompt を取得するたびに再計算される。
まとめ
PromptBuilder は「システムプロンプト = テンプレート + コンポーネント + コンテキスト」という 3 つを明確に切り分ける。variant がテンプレートとコンポーネント順序を決め、コンポーネント関数が context を読んで文字列を返し、placeholder + TemplateEngine がそれらを結合し、postProcess が仕上げる。この構造により Cline はモデルファミリーごとに迅速にプロンプトをカスタマイズでき (next-gen / glm / gemini-3 / gpt-5 にそれぞれ専用 variant がある)、同時に同一のコンポーネント関数群を共有できる。次はツール定義、または plan/act モードがプロンプトにどう影響するかを見るとよい。
- ツールセットと XML schema:
/prompts/toolset - plan / act モードの差異:
/prompts/mode - プロバイダの選択方法:
/providers/api-handler - Anthropic handler が systemPrompt をどう消費するか:
/providers/anthropic