Skip to content

Commit 5624056

Browse files
skulidropekclaude
andcommitted
refactor(lib): replace __PLACEHOLDER__ with direct ${} interpolation
- claude-system-prompt.ts: single clean template string via renderSharedPrompt(targetDir, workspaceContext) with ${} params; export PROMPT_* constants + renderFocusLine() for Codex - claude-extra-config.ts: renderClaudeGlobalPromptSetup() builds bash script inline — no module-level template, no .replaceAll() chains - gemini.ts: renderEntrypointGeminiNotice() same approach - codex.ts: FOCUS_LINE uses renderFocusLine() via __FOCUS_LINE__ stub (kept because the 100-line managed-block template stays module-level) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5818250 commit 5624056

File tree

4 files changed

+60
-68
lines changed

4 files changed

+60
-68
lines changed

packages/lib/src/core/templates-entrypoint/claude-extra-config.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
import type { TemplateConfig } from "../domain.js"
22
import { renderSharedPrompt } from "./claude-system-prompt.js"
33

4-
const entrypointClaudeGlobalPromptTemplate = String
5-
.raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code)
6-
CLAUDE_GLOBAL_PROMPT_FILE="/home/__SSH_USER__/.claude/CLAUDE.md"
4+
const escapeForDoubleQuotes = (value: string): string => {
5+
const backslash = String.fromCodePoint(92)
6+
const quote = String.fromCodePoint(34)
7+
const escapedBackslash = `${backslash}${backslash}`
8+
const escapedQuote = `${backslash}${quote}`
9+
return value
10+
.replaceAll(backslash, escapedBackslash)
11+
.replaceAll(quote, escapedQuote)
12+
}
13+
14+
export const renderClaudeGlobalPromptSetup = (config: TemplateConfig): string => {
15+
const promptContent = renderSharedPrompt(config.targetDir, "$CLAUDE_WORKSPACE_CONTEXT")
16+
const repoRefDefault = escapeForDoubleQuotes(config.repoRef)
17+
const repoUrlDefault = escapeForDoubleQuotes(config.repoUrl)
18+
19+
return String.raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code)
20+
CLAUDE_GLOBAL_PROMPT_FILE="/home/${config.sshUser}/.claude/CLAUDE.md"
721
CLAUDE_AUTO_SYSTEM_PROMPT="${"$"}{CLAUDE_AUTO_SYSTEM_PROMPT:-1}"
822
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: repository"
9-
REPO_REF_VALUE="${"$"}{REPO_REF:-__REPO_REF_DEFAULT__}"
10-
REPO_URL_VALUE="${"$"}{REPO_URL:-__REPO_URL_DEFAULT__}"
23+
REPO_REF_VALUE="${"$"}{REPO_REF:-${repoRefDefault}}"
24+
REPO_URL_VALUE="${"$"}{REPO_URL:-${repoUrlDefault}}"
1125
1226
if [[ "$REPO_REF_VALUE" == issue-* ]]; then
1327
ISSUE_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -E 's#^issue-##')"
@@ -47,7 +61,7 @@ if [[ "$CLAUDE_AUTO_SYSTEM_PROMPT" == "1" ]]; then
4761
if [[ ! -f "$CLAUDE_GLOBAL_PROMPT_FILE" ]] || grep -q "^<!-- docker-git-managed:claude-md -->$" "$CLAUDE_GLOBAL_PROMPT_FILE"; then
4862
cat <<EOF > "$CLAUDE_GLOBAL_PROMPT_FILE"
4963
<!-- docker-git-managed:claude-md -->
50-
${renderSharedPrompt("$CLAUDE_WORKSPACE_CONTEXT")}
64+
${promptContent}
5165
<!-- /docker-git-managed:claude-md -->
5266
EOF
5367
chmod 0644 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
@@ -56,24 +70,8 @@ EOF
5670
fi
5771
5872
export CLAUDE_AUTO_SYSTEM_PROMPT`
59-
60-
const escapeForDoubleQuotes = (value: string): string => {
61-
const backslash = String.fromCodePoint(92)
62-
const quote = String.fromCodePoint(34)
63-
const escapedBackslash = `${backslash}${backslash}`
64-
const escapedQuote = `${backslash}${quote}`
65-
return value
66-
.replaceAll(backslash, escapedBackslash)
67-
.replaceAll(quote, escapedQuote)
6873
}
6974

70-
export const renderClaudeGlobalPromptSetup = (config: TemplateConfig): string =>
71-
entrypointClaudeGlobalPromptTemplate
72-
.replaceAll("__TARGET_DIR__", config.targetDir)
73-
.replaceAll("__SSH_USER__", config.sshUser)
74-
.replaceAll("__REPO_REF_DEFAULT__", escapeForDoubleQuotes(config.repoRef))
75-
.replaceAll("__REPO_URL_DEFAULT__", escapeForDoubleQuotes(config.repoUrl))
76-
7775
export const renderClaudeWrapperSetup = (): string =>
7876
String.raw`CLAUDE_WRAPPER_BIN="/usr/local/bin/claude"
7977
if command -v claude >/dev/null 2>&1; then
Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,42 @@
11
/**
2-
* Shared system prompt injected into every agent (Claude, Gemini, Codex).
2+
* Single source of truth for the agent system prompt.
3+
* Edit this file to change behaviour across all providers (Claude, Gemini, Codex).
34
*
4-
* Edit this file to change the global agent behaviour across all providers.
5-
*
6-
* Placeholders resolved at TypeScript render time:
7-
* __TARGET_DIR__ – absolute path to the cloned repo inside the container
8-
*
9-
* Placeholders resolved at container start-up (bash):
10-
* workspaceContextBashVar – per-agent bash variable reference passed to
11-
* renderSharedPrompt(), e.g. "$CLAUDE_WORKSPACE_CONTEXT"
5+
* @param targetDir – absolute path to the repo inside the container
6+
* @param workspaceContext – bash variable reference resolved at container start-up,
7+
* e.g. "$CLAUDE_WORKSPACE_CONTEXT"
128
*/
139

14-
export const PROMPT_INTRO =
10+
const INTRO =
1511
`Ты автономный агент, который имеет полностью все права управления контейнером. ` +
1612
`У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, sshpass, ` +
1713
`claude, gemini, git, node, pnpm и всем остальным другим. ` +
1814
`Проекты с которыми идёт работа лежат по пути ~`
1915

20-
export const PROMPT_INTERNET =
16+
const INTERNET =
2117
`Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.`
2218

23-
export const PROMPT_SUBAGENTS =
19+
const SUBAGENTS =
2420
`Для решения задач обязательно используй subagents. ` +
2521
`Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю.`
2622

27-
export const PROMPT_FILES =
23+
const FILES =
2824
`Если ты видишь файлы AGENTS.md, GEMINI.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.`
2925

30-
/**
31-
* Builds the full prompt string for heredoc injection (Claude, Gemini).
32-
*
33-
* @param workspaceContextBashVar – bash variable reference that will be
34-
* interpolated at container start-up, e.g. "$CLAUDE_WORKSPACE_CONTEXT"
35-
*
36-
* __TARGET_DIR__ is left as-is and replaced by each agent's render function.
37-
*/
38-
export const renderSharedPrompt = (workspaceContextBashVar: string): string =>
39-
[
40-
PROMPT_INTRO,
41-
`Рабочая папка проекта (git clone): __TARGET_DIR__`,
42-
`Доступные workspace пути: __TARGET_DIR__`,
43-
workspaceContextBashVar,
44-
`Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__`,
45-
PROMPT_INTERNET,
46-
PROMPT_SUBAGENTS,
47-
PROMPT_FILES,
48-
].join("\n")
26+
// Used by Codex's bash-variable injection pattern
27+
export const PROMPT_INTRO = INTRO
28+
export const PROMPT_INTERNET = INTERNET
29+
export const PROMPT_SUBAGENTS = SUBAGENTS
30+
export const PROMPT_FILES = FILES
31+
export const renderFocusLine = (targetDir: string): string =>
32+
`Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: ${targetDir}`
33+
34+
export const renderSharedPrompt = (targetDir: string, workspaceContext: string): string =>
35+
`${INTRO}
36+
Рабочая папка проекта (git clone): ${targetDir}
37+
Доступные workspace пути: ${targetDir}
38+
${workspaceContext}
39+
${renderFocusLine(targetDir)}
40+
${INTERNET}
41+
${SUBAGENTS}
42+
${FILES}`

packages/lib/src/core/templates-entrypoint/codex.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { TemplateConfig } from "../domain.js"
2-
import { PROMPT_FILES, PROMPT_INTERNET, PROMPT_INTRO, PROMPT_SUBAGENTS } from "./claude-system-prompt.js"
2+
import { PROMPT_FILES, PROMPT_INTERNET, PROMPT_INTRO, PROMPT_SUBAGENTS, renderFocusLine } from "./claude-system-prompt.js"
33

44
export { renderEntrypointCodexResumeHint } from "./codex-resume-hint.js"
55

@@ -128,7 +128,7 @@ LEGACY_AGENTS_PATH="/home/__SSH_USER__/AGENTS.md"
128128
PROJECT_LINE="Рабочая папка проекта (git clone): __TARGET_DIR__"
129129
WORKSPACES_LINE="Доступные workspace пути: __TARGET_DIR__"
130130
WORKSPACE_INFO_LINE="Контекст workspace: repository"
131-
FOCUS_LINE="Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__"
131+
FOCUS_LINE="__FOCUS_LINE__"
132132
INTERNET_LINE="${PROMPT_INTERNET}"
133133
SUBAGENTS_LINE="${PROMPT_SUBAGENTS}"
134134
if [[ "$REPO_REF" == issue-* ]]; then
@@ -230,7 +230,8 @@ if [[ -f "$LEGACY_AGENTS_PATH" && -f "$AGENTS_PATH" ]]; then
230230
fi`
231231

232232
export const renderEntrypointAgentsNotice = (config: TemplateConfig): string =>
233-
entrypointAgentsNoticeTemplate.replaceAll("__CODEX_HOME__", config.codexHome).replaceAll(
234-
"__SSH_USER__",
235-
config.sshUser
236-
).replaceAll("__TARGET_DIR__", config.targetDir)
233+
entrypointAgentsNoticeTemplate
234+
.replaceAll("__CODEX_HOME__", config.codexHome)
235+
.replaceAll("__SSH_USER__", config.sshUser)
236+
.replaceAll("__TARGET_DIR__", config.targetDir)
237+
.replaceAll("__FOCUS_LINE__", renderFocusLine(config.targetDir))

packages/lib/src/core/templates-entrypoint/gemini.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,11 @@ docker_git_upsert_ssh_env "GEMINI_CLI_DISABLE_UPDATE_CHECK" "true"
230230
docker_git_upsert_ssh_env "GEMINI_CLI_NONINTERACTIVE" "true"
231231
docker_git_upsert_ssh_env "GEMINI_CLI_APPROVAL_MODE" "yolo"`
232232

233-
const entrypointGeminiNoticeTemplate = String.raw`# Ensure global GEMINI.md exists for container context
234-
GEMINI_MD_PATH="__GEMINI_HOME__/GEMINI.md"
233+
const renderEntrypointGeminiNotice = (config: TemplateConfig): string => {
234+
const promptContent = renderSharedPrompt(config.targetDir, "$GEMINI_WORKSPACE_CONTEXT")
235+
236+
return String.raw`# Ensure global GEMINI.md exists for container context
237+
GEMINI_MD_PATH="${config.geminiHome}/GEMINI.md"
235238
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: repository"
236239
if [[ "$REPO_REF" == issue-* ]]; then
237240
ISSUE_ID="$(printf "%s" "$REPO_REF" | sed -E 's#^issue-##')"
@@ -267,15 +270,11 @@ fi
267270
268271
cat <<EOF > "$GEMINI_MD_PATH"
269272
<!-- docker-git-managed:gemini-md -->
270-
${renderSharedPrompt("$GEMINI_WORKSPACE_CONTEXT")}
273+
${promptContent}
271274
<!-- /docker-git-managed:gemini-md -->
272275
EOF
273276
chown 1000:1000 "$GEMINI_MD_PATH" || true`
274-
275-
const renderEntrypointGeminiNotice = (config: TemplateConfig): string =>
276-
entrypointGeminiNoticeTemplate
277-
.replaceAll("__GEMINI_HOME__", config.geminiHome)
278-
.replaceAll("__TARGET_DIR__", config.targetDir)
277+
}
279278

280279
export const renderEntrypointGeminiConfig = (config: TemplateConfig): string =>
281280
[

0 commit comments

Comments
 (0)