diff --git a/packages/lib/src/core/templates-entrypoint/claude-extra-config.ts b/packages/lib/src/core/templates-entrypoint/claude-extra-config.ts index e376aa6..ad28223 100644 --- a/packages/lib/src/core/templates-entrypoint/claude-extra-config.ts +++ b/packages/lib/src/core/templates-entrypoint/claude-extra-config.ts @@ -1,12 +1,27 @@ import type { TemplateConfig } from "../domain.js" +import { renderSharedPrompt } from "./claude-system-prompt.js" -const entrypointClaudeGlobalPromptTemplate = String - .raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code) -CLAUDE_GLOBAL_PROMPT_FILE="/home/__SSH_USER__/.claude/CLAUDE.md" +const escapeForDoubleQuotes = (value: string): string => { + const backslash = String.fromCodePoint(92) + const quote = String.fromCodePoint(34) + const escapedBackslash = `${backslash}${backslash}` + const escapedQuote = `${backslash}${quote}` + return value + .replaceAll(backslash, escapedBackslash) + .replaceAll(quote, escapedQuote) +} + +export const renderClaudeGlobalPromptSetup = (config: TemplateConfig): string => { + const promptContent = renderSharedPrompt(config.targetDir, "$CLAUDE_WORKSPACE_CONTEXT") + const repoRefDefault = escapeForDoubleQuotes(config.repoRef) + const repoUrlDefault = escapeForDoubleQuotes(config.repoUrl) + + return String.raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code) +CLAUDE_GLOBAL_PROMPT_FILE="/home/${config.sshUser}/.claude/CLAUDE.md" CLAUDE_AUTO_SYSTEM_PROMPT="${"$"}{CLAUDE_AUTO_SYSTEM_PROMPT:-1}" CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: repository" -REPO_REF_VALUE="${"$"}{REPO_REF:-__REPO_REF_DEFAULT__}" -REPO_URL_VALUE="${"$"}{REPO_URL:-__REPO_URL_DEFAULT__}" +REPO_REF_VALUE="${"$"}{REPO_REF:-${repoRefDefault}}" +REPO_URL_VALUE="${"$"}{REPO_URL:-${repoUrlDefault}}" if [[ "$REPO_REF_VALUE" == issue-* ]]; then ISSUE_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -E 's#^issue-##')" @@ -46,14 +61,7 @@ if [[ "$CLAUDE_AUTO_SYSTEM_PROMPT" == "1" ]]; then if [[ ! -f "$CLAUDE_GLOBAL_PROMPT_FILE" ]] || grep -q "^$" "$CLAUDE_GLOBAL_PROMPT_FILE"; then cat < "$CLAUDE_GLOBAL_PROMPT_FILE" -Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, sshpass, claude, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~ -Рабочая папка проекта (git clone): __TARGET_DIR__ -Доступные workspace пути: __TARGET_DIR__ -$CLAUDE_WORKSPACE_CONTEXT -Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__ -Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе. -Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю. -Если ты видишь файлы AGENTS.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции. +${promptContent} EOF chmod 0644 "$CLAUDE_GLOBAL_PROMPT_FILE" || true @@ -62,24 +70,8 @@ EOF fi export CLAUDE_AUTO_SYSTEM_PROMPT` - -const escapeForDoubleQuotes = (value: string): string => { - const backslash = String.fromCodePoint(92) - const quote = String.fromCodePoint(34) - const escapedBackslash = `${backslash}${backslash}` - const escapedQuote = `${backslash}${quote}` - return value - .replaceAll(backslash, escapedBackslash) - .replaceAll(quote, escapedQuote) } -export const renderClaudeGlobalPromptSetup = (config: TemplateConfig): string => - entrypointClaudeGlobalPromptTemplate - .replaceAll("__TARGET_DIR__", config.targetDir) - .replaceAll("__SSH_USER__", config.sshUser) - .replaceAll("__REPO_REF_DEFAULT__", escapeForDoubleQuotes(config.repoRef)) - .replaceAll("__REPO_URL_DEFAULT__", escapeForDoubleQuotes(config.repoUrl)) - export const renderClaudeWrapperSetup = (): string => String.raw`CLAUDE_WRAPPER_BIN="/usr/local/bin/claude" if command -v claude >/dev/null 2>&1; then diff --git a/packages/lib/src/core/templates-entrypoint/claude-system-prompt.ts b/packages/lib/src/core/templates-entrypoint/claude-system-prompt.ts new file mode 100644 index 0000000..2ed18dd --- /dev/null +++ b/packages/lib/src/core/templates-entrypoint/claude-system-prompt.ts @@ -0,0 +1,42 @@ +/** + * Single source of truth for the agent system prompt. + * Edit this file to change behaviour across all providers (Claude, Gemini, Codex). + * + * @param targetDir – absolute path to the repo inside the container + * @param workspaceContext – bash variable reference resolved at container start-up, + * e.g. "$CLAUDE_WORKSPACE_CONTEXT" + */ + +const INTRO = + `Ты автономный агент, который имеет полностью все права управления контейнером. ` + + `У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, sshpass, ` + + `claude, gemini, git, node, pnpm и всем остальным другим. ` + + `Проекты с которыми идёт работа лежат по пути ~` + +const INTERNET = + `Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.` + +const SUBAGENTS = + `Для решения задач обязательно используй subagents. ` + + `Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю.` + +const FILES = + `Если ты видишь файлы AGENTS.md, GEMINI.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.` + +// Used by Codex's bash-variable injection pattern +export const PROMPT_INTRO = INTRO +export const PROMPT_INTERNET = INTERNET +export const PROMPT_SUBAGENTS = SUBAGENTS +export const PROMPT_FILES = FILES +export const renderFocusLine = (targetDir: string): string => + `Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: ${targetDir}` + +export const renderSharedPrompt = (targetDir: string, workspaceContext: string): string => + `${INTRO} +Рабочая папка проекта (git clone): ${targetDir} +Доступные workspace пути: ${targetDir} +${workspaceContext} +${renderFocusLine(targetDir)} +${INTERNET} +${SUBAGENTS} +${FILES}` diff --git a/packages/lib/src/core/templates-entrypoint/codex.ts b/packages/lib/src/core/templates-entrypoint/codex.ts index a2b3b93..e4cab14 100644 --- a/packages/lib/src/core/templates-entrypoint/codex.ts +++ b/packages/lib/src/core/templates-entrypoint/codex.ts @@ -1,4 +1,5 @@ import type { TemplateConfig } from "../domain.js" +import { PROMPT_FILES, PROMPT_INTERNET, PROMPT_INTRO, PROMPT_SUBAGENTS, renderFocusLine } from "./claude-system-prompt.js" export { renderEntrypointCodexResumeHint } from "./codex-resume-hint.js" @@ -127,9 +128,9 @@ LEGACY_AGENTS_PATH="/home/__SSH_USER__/AGENTS.md" PROJECT_LINE="Рабочая папка проекта (git clone): __TARGET_DIR__" WORKSPACES_LINE="Доступные workspace пути: __TARGET_DIR__" WORKSPACE_INFO_LINE="Контекст workspace: repository" -FOCUS_LINE="Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__" -INTERNET_LINE="Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе." -SUBAGENTS_LINE="Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю." +FOCUS_LINE="__FOCUS_LINE__" +INTERNET_LINE="${PROMPT_INTERNET}" +SUBAGENTS_LINE="${PROMPT_SUBAGENTS}" if [[ "$REPO_REF" == issue-* ]]; then ISSUE_ID="$(printf "%s" "$REPO_REF" | sed -E 's#^issue-##')" ISSUE_URL="" @@ -176,9 +177,9 @@ $MANAGED_END EOF )" cat < "$AGENTS_PATH" -Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, sshpass, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~ +${PROMPT_INTRO} $MANAGED_BLOCK -Если ты видишь файлы AGENTS.md внутри проекта, ты обязан их читать и соблюдать инструкции. +${PROMPT_FILES} EOF chown 1000:1000 "$AGENTS_PATH" || true fi @@ -229,7 +230,8 @@ if [[ -f "$LEGACY_AGENTS_PATH" && -f "$AGENTS_PATH" ]]; then fi` export const renderEntrypointAgentsNotice = (config: TemplateConfig): string => - entrypointAgentsNoticeTemplate.replaceAll("__CODEX_HOME__", config.codexHome).replaceAll( - "__SSH_USER__", - config.sshUser - ).replaceAll("__TARGET_DIR__", config.targetDir) + entrypointAgentsNoticeTemplate + .replaceAll("__CODEX_HOME__", config.codexHome) + .replaceAll("__SSH_USER__", config.sshUser) + .replaceAll("__TARGET_DIR__", config.targetDir) + .replaceAll("__FOCUS_LINE__", renderFocusLine(config.targetDir)) diff --git a/packages/lib/src/core/templates-entrypoint/gemini.ts b/packages/lib/src/core/templates-entrypoint/gemini.ts index b7a6f8a..9b10fdf 100644 --- a/packages/lib/src/core/templates-entrypoint/gemini.ts +++ b/packages/lib/src/core/templates-entrypoint/gemini.ts @@ -1,4 +1,5 @@ import type { TemplateConfig } from "../domain.js" +import { renderSharedPrompt } from "./claude-system-prompt.js" // CHANGE: add Gemini CLI entrypoint configuration // WHY: enable Gemini CLI in Docker with automated auth, trust settings and MCP @@ -229,8 +230,11 @@ docker_git_upsert_ssh_env "GEMINI_CLI_DISABLE_UPDATE_CHECK" "true" docker_git_upsert_ssh_env "GEMINI_CLI_NONINTERACTIVE" "true" docker_git_upsert_ssh_env "GEMINI_CLI_APPROVAL_MODE" "yolo"` -const entrypointGeminiNoticeTemplate = String.raw`# Ensure global GEMINI.md exists for container context -GEMINI_MD_PATH="__GEMINI_HOME__/GEMINI.md" +const renderEntrypointGeminiNotice = (config: TemplateConfig): string => { + const promptContent = renderSharedPrompt(config.targetDir, "$GEMINI_WORKSPACE_CONTEXT") + + return String.raw`# Ensure global GEMINI.md exists for container context +GEMINI_MD_PATH="${config.geminiHome}/GEMINI.md" GEMINI_WORKSPACE_CONTEXT="Контекст workspace: repository" if [[ "$REPO_REF" == issue-* ]]; then ISSUE_ID="$(printf "%s" "$REPO_REF" | sed -E 's#^issue-##')" @@ -266,22 +270,11 @@ fi cat < "$GEMINI_MD_PATH" -Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, gemini, claude, opencode, oh-my-opencode, sshpass, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~ -Рабочая папка проекта (git clone): __TARGET_DIR__ -Доступные workspace пути: __TARGET_DIR__ -$GEMINI_WORKSPACE_CONTEXT -Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__ -Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе. -Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю. -Если ты видишь файлы AGENTS.md, GEMINI.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции. +${promptContent} EOF chown 1000:1000 "$GEMINI_MD_PATH" || true` - -const renderEntrypointGeminiNotice = (config: TemplateConfig): string => - entrypointGeminiNoticeTemplate - .replaceAll("__GEMINI_HOME__", config.geminiHome) - .replaceAll("__TARGET_DIR__", config.targetDir) +} export const renderEntrypointGeminiConfig = (config: TemplateConfig): string => [