Skip to content

Commit 8e5d328

Browse files
committed
feat: auto-load project codex skills
1 parent 546c727 commit 8e5d328

6 files changed

Lines changed: 70 additions & 0 deletions

File tree

packages/app/tests/docker-git/entrypoint-auth.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ describe("renderEntrypoint auth bridge", () => {
7979
expect(entrypoint).toContain("CLAUDE_GLOBAL_PROMPT_FILE=\"/home/dev/.claude/CLAUDE.md\"")
8080
expect(entrypoint).toContain("CLAUDE_AUTO_SYSTEM_PROMPT=\"${CLAUDE_AUTO_SYSTEM_PROMPT:-1}\"")
8181
expect(entrypoint).toContain("docker-git-managed:claude-md")
82+
expect(entrypoint).toContain("docker_git_sync_project_codex_skills()")
83+
expect(entrypoint).toContain('project_skills_root="$codex_home/skills/.docker-git-project"')
84+
expect(entrypoint).toContain('"20-agents-skills::.agents/skills"')
85+
expect(entrypoint).toContain('"30-agents-dot-skills::.agents/.skills"')
86+
expect(entrypoint).toContain('"80-codex-skills::.codex/skills"')
87+
expect(entrypoint).toContain('"90-codex-dot-skills::.codex/.skills"')
88+
expect(entrypoint).toContain('docker_git_sync_project_codex_skills')
8289
expect(entrypoint).toContain(
8390
"SUBAGENTS_LINE=\"Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю.\""
8491
)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { renderEntrypointClaudeConfig } from "./templates-entrypoint/claude.js"
1515
import {
1616
renderEntrypointAgentsNotice,
1717
renderEntrypointCodexHome,
18+
renderEntrypointProjectCodexSkillsSync,
1819
renderEntrypointCodexResumeHint,
1920
renderEntrypointCodexSharedAuth,
2021
renderEntrypointMcpPlaywright
@@ -51,6 +52,7 @@ export const renderEntrypoint = (config: TemplateConfig): string =>
5152
renderEntrypointInputRc(config),
5253
renderEntrypointZshConfig(),
5354
renderEntrypointCodexResumeHint(config),
55+
renderEntrypointProjectCodexSkillsSync(config),
5456
renderEntrypointAgentsNotice(config),
5557
renderEntrypointDockerSocket(config),
5658
renderEntrypointGitConfig(config),

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,53 @@ export const renderEntrypointMcpPlaywright = (config: TemplateConfig): string =>
121121
.replaceAll("__CODEX_HOME__", config.codexHome)
122122
.replaceAll("__SERVICE_NAME__", config.serviceName)
123123

124+
const entrypointProjectCodexSkillsSyncTemplate = String.raw`# Mirror project-owned Codex skill trees into CODEX_HOME without overwriting global skills.
125+
docker_git_sync_project_codex_skills() {
126+
local codex_home="${"$"}{CODEX_HOME:-__CODEX_HOME__}"
127+
local project_dir="${"$"}{TARGET_DIR:-}"
128+
local project_skills_root="$codex_home/skills/.docker-git-project"
129+
local linked=0
130+
local spec=""
131+
local mount_name=""
132+
local relative_path=""
133+
134+
if [[ -z "$project_dir" || ! -d "$project_dir" ]]; then
135+
return 0
136+
fi
137+
138+
mkdir -p "$codex_home/skills"
139+
rm -rf "$project_skills_root"
140+
mkdir -p "$project_skills_root"
141+
142+
# Priority goes from generic -> shared agent dirs -> agent-specific Codex dirs.
143+
for spec in \
144+
"10-root-skills::.skills" \
145+
"20-agents-skills::.agents/skills" \
146+
"30-agents-dot-skills::.agents/.skills" \
147+
"40-claude-skills::.claude/skills" \
148+
"50-claude-dot-skills::.claude/.skills" \
149+
"80-codex-skills::.codex/skills" \
150+
"90-codex-dot-skills::.codex/.skills"; do
151+
mount_name="${"$"}{spec%%::*}"
152+
relative_path="${"$"}{spec#*::}"
153+
154+
if [[ -d "$project_dir/$relative_path" ]]; then
155+
ln -sfn "$project_dir/$relative_path" "$project_skills_root/$mount_name"
156+
chown -h 1000:1000 "$project_skills_root/$mount_name" 2>/dev/null || true
157+
linked=1
158+
fi
159+
done
160+
161+
chown 1000:1000 "$codex_home/skills" "$project_skills_root" 2>/dev/null || true
162+
163+
if [[ "$linked" -eq 1 ]]; then
164+
echo "[codex-skills] linked project skill trees into $project_skills_root"
165+
fi
166+
}`
167+
168+
export const renderEntrypointProjectCodexSkillsSync = (config: TemplateConfig): string =>
169+
entrypointProjectCodexSkillsSyncTemplate.replaceAll("__CODEX_HOME__", config.codexHome)
170+
124171
const entrypointAgentsNoticeTemplate = String.raw`# Ensure global AGENTS.md exists for container context
125172
AGENTS_PATH="__CODEX_HOME__/AGENTS.md"
126173
LEGACY_AGENTS_PATH="/home/__SSH_USER__/AGENTS.md"

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,5 +221,9 @@ ${renderEntrypointAutoUpdate()}
221221
222222
${renderEntrypointClone(config)}
223223
224+
if [[ "$CLONE_OK" -eq 1 ]]; then
225+
docker_git_sync_project_codex_skills
226+
fi
227+
224228
${renderAgentLaunch(config)}
225229
) &`

packages/lib/tests/usecases/apply.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ describe("applyProjectFiles", () => {
153153
expect(composeAfter).toContain("cpus:")
154154
expect(composeAfter).toContain('mem_limit: "')
155155

156+
const entrypointAfter = yield* _(fs.readFileString(path.join(outDir, "entrypoint.sh")))
157+
expect(entrypointAfter).toContain("docker_git_sync_project_codex_skills()")
158+
expect(entrypointAfter).toContain('"20-agents-skills::.agents/skills"')
159+
156160
const configAfter = yield* _(fs.readFileString(configPath))
157161
expect(configAfter).toContain('"cpuLimit": "30%"')
158162
expect(configAfter).toContain('"ramLimit": "30%"')

packages/lib/tests/usecases/prepare-files.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ describe("prepareProjectFiles", () => {
175175
expect(entrypoint).toContain('. /etc/profile 2>/dev/null || true;')
176176
expect(entrypoint).toContain("codex exec")
177177
expect(entrypoint).not.toContain("codex --approval-mode full-auto")
178+
expect(entrypoint).toContain("docker_git_sync_project_codex_skills()")
179+
expect(entrypoint).toContain('project_skills_root="$codex_home/skills/.docker-git-project"')
180+
expect(entrypoint).toContain('"10-root-skills::.skills"')
181+
expect(entrypoint).toContain('"20-agents-skills::.agents/skills"')
182+
expect(entrypoint).toContain('"90-codex-dot-skills::.codex/.skills"')
178183
expect(entrypoint).toContain("docker_git_repair_dns() {")
179184
expect(entrypoint).toContain('local test_domain="github.com"')
180185
expect(entrypoint).toContain('local fallback_dns="8.8.8.8 8.8.4.4 1.1.1.1"')
@@ -186,6 +191,7 @@ describe("prepareProjectFiles", () => {
186191
expect(entrypoint).toContain("cat > \"$MOVE_SCRIPT\" << 'EOFMOVE'")
187192
expect(entrypoint).toMatch(/\nEOFMOVE\n\s*chmod \+x "\$MOVE_SCRIPT"/)
188193
expect(entrypoint).not.toContain("\n EOFMOVE\n")
194+
expect(entrypoint).toContain("if [[ \"$CLONE_OK\" -eq 1 ]]; then\n docker_git_sync_project_codex_skills\nfi")
189195
expect(composeBefore).toContain("container_name: dg-test")
190196
expect(composeBefore).toContain("restart: unless-stopped")
191197
expect(composeBefore).toContain(":/home/dev/.docker-git")

0 commit comments

Comments
 (0)