11import { renderEntrypointGitPostPushWrapperInstall } from "./git-post-push-wrapper.js"
2+ import {
3+ renderPlanToGitAgentHooksInstall ,
4+ renderPlanToGitHookPaths ,
5+ renderPlanToGitPostPushSync ,
6+ renderPlanToGitSyncHelperInstall
7+ } from "./plan-to-git.js"
28import { renderPostPushPrEnsure } from "./post-push-pr.js"
39
410const entrypointGitHooksTemplate = String
511 . raw `# 3) Install global git hooks to protect main/master + managed AGENTS context
612HOOKS_DIR="/opt/docker-git/hooks"
713PRE_PUSH_HOOK="$HOOKS_DIR/pre-push"
814POST_PUSH_ACTION="$HOOKS_DIR/post-push"
9- PLAN_TO_GIT_SYNC_HELPER="$HOOKS_DIR/plan-to-git-sync"
10- PLAN_TO_GIT_CODEX_HOOK="$HOOKS_DIR/plan-to-git-codex-hook"
11- PLAN_TO_GIT_CLAUDE_HOOK="$HOOKS_DIR/plan-to-git-claude-hook"
12- CODEX_REQUIREMENTS_FILE="/etc/codex/requirements.toml"
13- CLAUDE_PLAN_TO_GIT_SETTINGS_FILE="$CLAUDE_CONFIG_DIR/settings.json"
15+ ${ renderPlanToGitHookPaths ( ) }
1416mkdir -p "$HOOKS_DIR"
1517
1618cat <<'EOF' > "$PRE_PUSH_HOOK"
@@ -138,74 +140,7 @@ done
138140EOF
139141chmod 0755 "$PRE_PUSH_HOOK"
140142
141- cat <<'EOF' > "$PLAN_TO_GIT_SYNC_HELPER"
142- #!/usr/bin/env bash
143- set -euo pipefail
144-
145- if [ "${ "${" } DOCKER_GIT_SKIP_PLAN_TO_GIT:-}" = "1" ]; then
146- exit 0
147- fi
148-
149- if ! command -v plan-to-git >/dev/null 2>&1; then
150- echo "[plan-to-git] Error: plan-to-git not found" >&2
151- exit 1
152- fi
153-
154- export PLAN_TO_GIT_STATE_DIR="${ "${" } PLAN_TO_GIT_STATE_DIR:-/tmp/plan-to-git}"
155-
156- docker_git_plan_to_git_explicit_pr_supported() {
157- plan-to-git sync --help 2>/dev/null | grep -q -- "--pr <PR>"
158- }
159-
160- docker_git_plan_to_git_resolve_pr_number() {
161- local candidate=""
162- local key=""
163- for key in DOCKER_GIT_PR_NUMBER PR_NUMBER GITHUB_PR_NUMBER; do
164- candidate="${ "${" } !key:-}"
165- if [[ "$candidate" =~ ^[0-9]+$ ]]; then
166- printf "%s\n" "$candidate"
167- return 0
168- fi
169- done
170-
171- candidate="${ "${" } REPO_REF:-}"
172- if [[ "$candidate" =~ ^refs/pull/([0-9]+)/head$ ]]; then
173- printf "%s\n" "${ "${" } BASH_REMATCH[1]}"
174- return 0
175- fi
176- if [[ "$candidate" =~ ^pull/([0-9]+)$ ]]; then
177- printf "%s\n" "${ "${" } BASH_REMATCH[1]}"
178- return 0
179- fi
180-
181- if command -v gh >/dev/null 2>&1; then
182- candidate="$(gh pr view --json number --jq .number 2>/dev/null || true)"
183- if [[ "$candidate" =~ ^[0-9]+$ ]]; then
184- printf "%s\n" "$candidate"
185- return 0
186- fi
187- fi
188-
189- return 0
190- }
191-
192- docker_git_plan_to_git_sync() {
193- local pr_number=""
194- pr_number="$(docker_git_plan_to_git_resolve_pr_number || true)"
195-
196- if [[ -n "$pr_number" ]] && docker_git_plan_to_git_explicit_pr_supported; then
197- echo "[plan-to-git] Syncing queued agent plans to PR #$pr_number"
198- plan-to-git sync --pr "$pr_number"
199- return 0
200- fi
201-
202- echo "[plan-to-git] Syncing queued agent plans via current branch discovery"
203- plan-to-git sync
204- }
205-
206- docker_git_plan_to_git_sync
207- EOF
208- chmod 0755 "$PLAN_TO_GIT_SYNC_HELPER"
143+ ${ renderPlanToGitSyncHelperInstall ( ) }
209144
210145cat <<'EOF' > "$POST_PUSH_ACTION"
211146#!/usr/bin/env bash
@@ -220,25 +155,7 @@ cd "$REPO_ROOT"
220155
221156${ renderPostPushPrEnsure ( ) }
222157
223- # CHANGE: backfill agent session plans before syncing the current branch or explicit PR.
224- # WHY: live agent hooks can be unavailable in already-running sessions; session logs are the durable fallback.
225- # QUOTE(ТЗ): "что бы всё уходило на гитхаб автоматически"
226- # REF: issue-397
227- if [ "${ "${" } DOCKER_GIT_SKIP_PLAN_TO_GIT:-}" != "1" ]; then
228- if ! command -v plan-to-git >/dev/null 2>&1; then
229- echo "[plan-to-git] Error: plan-to-git not found" >&2
230- exit 1
231- fi
232- plan-to-git import-codex --no-sync
233- plan-to-git import-claude --no-sync
234- PLAN_TO_GIT_SYNC_HELPER="${ "${" } DOCKER_GIT_PLAN_TO_GIT_SYNC_HELPER:-/opt/docker-git/hooks/plan-to-git-sync}"
235- if [[ -x "$PLAN_TO_GIT_SYNC_HELPER" ]]; then
236- "$PLAN_TO_GIT_SYNC_HELPER"
237- else
238- echo "[plan-to-git] Sync helper not found; falling back to current branch discovery" >&2
239- plan-to-git sync
240- fi
241- fi
158+ ${ renderPlanToGitPostPushSync ( ) }
242159
243160# CHANGE: keep post-push backup logic in a reusable action script
244161# WHY: git has no client-side post-push hook, so the global git wrapper
258175EOF
259176chmod 0755 "$POST_PUSH_ACTION"
260177
261- cat <<'EOF' > "$PLAN_TO_GIT_CODEX_HOOK"
262- #!/usr/bin/env bash
263- set -euo pipefail
264-
265- if [ "${ "${" } DOCKER_GIT_SKIP_PLAN_TO_GIT:-}" = "1" ]; then
266- exit 0
267- fi
268-
269- if ! command -v plan-to-git >/dev/null 2>&1; then
270- echo "[plan-to-git] Error: plan-to-git not found" >&2
271- exit 1
272- fi
273-
274- export PLAN_TO_GIT_STATE_DIR="${ "${" } PLAN_TO_GIT_STATE_DIR:-/tmp/plan-to-git}"
275- plan-to-git hook --source codex
276- PLAN_TO_GIT_SYNC_HELPER="${ "${" } DOCKER_GIT_PLAN_TO_GIT_SYNC_HELPER:-/opt/docker-git/hooks/plan-to-git-sync}"
277- "$PLAN_TO_GIT_SYNC_HELPER" >&2 || true
278- EOF
279- chmod 0755 "$PLAN_TO_GIT_CODEX_HOOK"
280-
281- cat <<'EOF' > "$PLAN_TO_GIT_CLAUDE_HOOK"
282- #!/usr/bin/env bash
283- set -euo pipefail
284-
285- if [ "${ "${" } DOCKER_GIT_SKIP_PLAN_TO_GIT:-}" = "1" ]; then
286- exit 0
287- fi
288-
289- if ! command -v plan-to-git >/dev/null 2>&1; then
290- echo "[plan-to-git] Error: plan-to-git not found" >&2
291- exit 1
292- fi
293-
294- export PLAN_TO_GIT_STATE_DIR="${ "${" } PLAN_TO_GIT_STATE_DIR:-/tmp/plan-to-git}"
295- plan-to-git hook --source claude
296- PLAN_TO_GIT_SYNC_HELPER="${ "${" } DOCKER_GIT_PLAN_TO_GIT_SYNC_HELPER:-/opt/docker-git/hooks/plan-to-git-sync}"
297- "$PLAN_TO_GIT_SYNC_HELPER" >&2 || true
298- EOF
299- chmod 0755 "$PLAN_TO_GIT_CLAUDE_HOOK"
300-
301- mkdir -p "$(dirname "$CODEX_REQUIREMENTS_FILE")"
302- cat <<'EOF' > "$CODEX_REQUIREMENTS_FILE"
303- # docker-git managed Codex requirements
304-
305- [features]
306- hooks = true
307-
308- [hooks]
309- managed_dir = "/opt/docker-git/hooks"
310-
311- [[hooks.UserPromptSubmit]]
312- [[hooks.UserPromptSubmit.hooks]]
313- type = "command"
314- command = "/opt/docker-git/hooks/plan-to-git-codex-hook"
315- statusMessage = "Capturing plan decision"
316-
317- [[hooks.Stop]]
318- [[hooks.Stop.hooks]]
319- type = "command"
320- command = "/opt/docker-git/hooks/plan-to-git-codex-hook"
321- statusMessage = "Capturing agent plan"
322- EOF
323- chmod 0644 "$CODEX_REQUIREMENTS_FILE"
324-
325- docker_git_install_claude_plan_to_git_hooks() {
326- if [ "${ "${" } DOCKER_GIT_SKIP_PLAN_TO_GIT:-}" = "1" ]; then
327- return 0
328- fi
329-
330- CLAUDE_PLAN_TO_GIT_SETTINGS_FILE="${ "${" } CLAUDE_PLAN_TO_GIT_SETTINGS_FILE:-${ "${" } CLAUDE_CONFIG_DIR:-/home/dev/.claude}/settings.json}"
331- CLAUDE_PLAN_TO_GIT_SETTINGS_FILE="$CLAUDE_PLAN_TO_GIT_SETTINGS_FILE" PLAN_TO_GIT_CLAUDE_HOOK="$PLAN_TO_GIT_CLAUDE_HOOK" node - <<'NODE'
332- const fs = require("node:fs")
333- const path = require("node:path")
334-
335- const settingsPath = process.env.CLAUDE_PLAN_TO_GIT_SETTINGS_FILE
336- const hookCommand = process.env.PLAN_TO_GIT_CLAUDE_HOOK || "/opt/docker-git/hooks/plan-to-git-claude-hook"
337- if (typeof settingsPath !== "string" || settingsPath.length === 0) {
338- process.exit(0)
339- }
340-
341- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
342-
343- let settings = {}
344- try {
345- const parsed = JSON.parse(fs.readFileSync(settingsPath, "utf8"))
346- settings = isRecord(parsed) ? parsed : {}
347- } catch {
348- settings = {}
349- }
350-
351- const currentHooks = isRecord(settings.hooks) ? settings.hooks : {}
352- const nextHooks = { ...currentHooks }
353- const managedHook = { type: "command", command: hookCommand }
354- const ensureEventHook = (eventName) => {
355- const currentEventHooks = Array.isArray(nextHooks[eventName]) ? nextHooks[eventName] : []
356- const alreadyInstalled = currentEventHooks.some((entry) =>
357- isRecord(entry) &&
358- Array.isArray(entry.hooks) &&
359- entry.hooks.some((hook) => isRecord(hook) && hook.type === "command" && hook.command === hookCommand)
360- )
361- nextHooks[eventName] = alreadyInstalled ? currentEventHooks : [...currentEventHooks, { hooks: [managedHook] }]
362- }
363-
364- ensureEventHook("UserPromptSubmit")
365- ensureEventHook("Stop")
366-
367- const nextSettings = { ...settings, hooks: nextHooks }
368- if (JSON.stringify(settings) === JSON.stringify(nextSettings)) {
369- process.exit(0)
370- }
371-
372- fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
373- fs.writeFileSync(settingsPath, JSON.stringify(nextSettings, null, 2) + "\n", { mode: 0o600 })
374- NODE
375- chmod 0600 "$CLAUDE_PLAN_TO_GIT_SETTINGS_FILE" 2>/dev/null || true
376- chown 1000:1000 "$CLAUDE_PLAN_TO_GIT_SETTINGS_FILE" 2>/dev/null || true
377- }
378-
379- docker_git_install_claude_plan_to_git_hooks
178+ ${ renderPlanToGitAgentHooksInstall ( ) }
380179
381180${ renderEntrypointGitPostPushWrapperInstall ( ) }
382181
0 commit comments