Skip to content

Commit 7d6d960

Browse files
authored
fix: keep issue metadata only in ~/.codex/AGENTS.md (#69)
* fix(docker-git): keep issue context out of project AGENTS * refactor(lib): extract git hooks template constant
1 parent 5db5aea commit 7d6d960

File tree

7 files changed

+186
-112
lines changed

7 files changed

+186
-112
lines changed

AGENTS.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -373,12 +373,3 @@ describe("Message invariants", () => {
373373
Каждый эффект — это контролируемое взаимодействие с реальным миром.
374374

375375
ПРИНЦИП: Сначала формализуем, потом программируем.
376-
377-
<!-- docker-git:issue-managed:start -->
378-
Issue workspace: #61
379-
Issue URL: https://github.com/ProverCoderAI/docker-git/issues/61
380-
Workspace path: /home/dev/provercoderai/docker-git/issue-61
381-
382-
Работай только над этим issue, если пользователь не попросил другое.
383-
Если нужен первоисточник требований, открой Issue URL.
384-
<!-- docker-git:issue-managed:end -->

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ Force modes:
4545

4646
Agent context for issue workspaces:
4747
- Global `${CODEX_HOME}/AGENTS.md` includes workspace path + issue/PR context.
48-
- For `issue-*` workspaces, docker-git creates `${TARGET_DIR}/AGENTS.md` (if missing) with issue context and auto-adds it to `.git/info/exclude`.
4948

5049
## Projects Root Layout
5150

packages/docker-git/tests/core/templates.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ describe("planFiles", () => {
7373
"GIT_CREDENTIAL_HELPER_PATH=\"/usr/local/bin/docker-git-credential-helper\""
7474
)
7575
expect(entrypointSpec.contents).toContain("token=\"$GITHUB_TOKEN\"")
76+
expect(entrypointSpec.contents).toContain("issue_managed_start='<!-- docker-git:issue-managed:start -->'")
77+
expect(entrypointSpec.contents).toContain("check_issue_managed_block_range")
78+
expect(entrypointSpec.contents).toContain(
79+
"push contains commit updating managed issue block in AGENTS.md"
80+
)
7681
expect(entrypointSpec.contents).toContain("CACHE_ROOT=\"/home/dev/.docker-git/.cache/git-mirrors\"")
7782
expect(entrypointSpec.contents).toContain("PACKAGE_CACHE_ROOT=\"/home/dev/.docker-git/.cache/packages\"")
7883
expect(entrypointSpec.contents).toContain("npm_config_store_dir")
@@ -149,13 +154,17 @@ describe("planFiles", () => {
149154
if (entrypointSpec && entrypointSpec._tag === "File") {
150155
expect(entrypointSpec.contents).toContain("Доступные workspace пути:")
151156
expect(entrypointSpec.contents).toContain("Контекст workspace:")
152-
expect(entrypointSpec.contents).toContain("Issue AGENTS.md:")
153-
expect(entrypointSpec.contents).toContain("ISSUE_AGENTS_PATH=\"$TARGET_DIR/AGENTS.md\"")
154-
expect(entrypointSpec.contents).toContain("grep -qx \"AGENTS.md\" \"$EXCLUDE_PATH\"")
155157
expect(entrypointSpec.contents).toContain("docker_git_workspace_context_line()")
156158
expect(entrypointSpec.contents).toContain("REPO_REF_VALUE=\"${REPO_REF:-issue-5}\"")
157159
expect(entrypointSpec.contents).toContain("REPO_URL_VALUE=\"${REPO_URL:-https://github.com/org/repo.git}\"")
158160
expect(entrypointSpec.contents).toContain("Контекст workspace: issue #$ISSUE_ID_VALUE ($ISSUE_URL_VALUE)")
161+
expect(entrypointSpec.contents).not.toContain("ISSUE_AGENTS_HINT_LINE=")
162+
expect(entrypointSpec.contents).not.toContain("Issue AGENTS.md: __TARGET_DIR__/AGENTS.md")
163+
expect(entrypointSpec.contents).not.toContain("ISSUE_AGENTS_PATH=\"$TARGET_DIR/AGENTS.md\"")
164+
expect(entrypointSpec.contents).not.toContain(
165+
"ISSUE_MANAGED_START=\"<!-- docker-git:issue-managed:start -->\""
166+
)
167+
expect(entrypointSpec.contents).not.toContain("grep -qx \"AGENTS.md\" \"$EXCLUDE_PATH\"")
159168
}
160169
}))
161170

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ PROJECT_LINE="Рабочая папка проекта (git clone): __TARGET_DIR
206206
WORKSPACES_LINE="Доступные workspace пути: __TARGET_DIR__"
207207
WORKSPACE_INFO_LINE="Контекст workspace: repository"
208208
FOCUS_LINE="Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__"
209-
ISSUE_AGENTS_HINT_LINE="Issue AGENTS.md: n/a"
210209
INTERNET_LINE="Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе."
211210
if [[ "$REPO_REF" == issue-* ]]; then
212211
ISSUE_ID="$(printf "%s" "$REPO_REF" | sed -E 's#^issue-##')"
@@ -222,7 +221,6 @@ if [[ "$REPO_REF" == issue-* ]]; then
222221
else
223222
WORKSPACE_INFO_LINE="Контекст workspace: issue #$ISSUE_ID"
224223
fi
225-
ISSUE_AGENTS_HINT_LINE="Issue AGENTS.md: __TARGET_DIR__/AGENTS.md"
226224
elif [[ "$REPO_REF" == refs/pull/*/head ]]; then
227225
PR_ID="$(printf "%s" "$REPO_REF" | sed -nE 's#^refs/pull/([0-9]+)/head$#\1#p')"
228226
PR_URL=""
@@ -249,7 +247,6 @@ $PROJECT_LINE
249247
$WORKSPACES_LINE
250248
$WORKSPACE_INFO_LINE
251249
$FOCUS_LINE
252-
$ISSUE_AGENTS_HINT_LINE
253250
$INTERNET_LINE
254251
$MANAGED_END
255252
EOF
@@ -270,7 +267,6 @@ $PROJECT_LINE
270267
$WORKSPACES_LINE
271268
$WORKSPACE_INFO_LINE
272269
$FOCUS_LINE
273-
$ISSUE_AGENTS_HINT_LINE
274270
$INTERNET_LINE
275271
$MANAGED_END
276272
EOF

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

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,112 @@ export const renderEntrypointGitConfig = (config: TemplateConfig): string =>
8181
renderEntrypointGitIdentity(config)
8282
].join("\n\n")
8383

84-
export const renderEntrypointGitHooks = (): string =>
85-
String.raw`# 3) Install global git hooks to protect main/master
84+
const entrypointGitHooksTemplate = String
85+
.raw`# 3) Install global git hooks to protect main/master + managed AGENTS context
8686
HOOKS_DIR="/opt/docker-git/hooks"
8787
PRE_PUSH_HOOK="$HOOKS_DIR/pre-push"
8888
mkdir -p "$HOOKS_DIR"
89-
if [[ ! -f "$PRE_PUSH_HOOK" ]]; then
90-
cat <<'EOF' > "$PRE_PUSH_HOOK"
89+
90+
cat <<'EOF' > "$PRE_PUSH_HOOK"
9191
#!/usr/bin/env bash
9292
set -euo pipefail
9393
9494
protected_branches=("refs/heads/main" "refs/heads/master")
9595
allow_delete="${"${"}DOCKER_GIT_ALLOW_DELETE:-}"
96+
zero_sha="0000000000000000000000000000000000000000"
97+
issue_managed_start='<!-- docker-git:issue-managed:start -->'
98+
issue_managed_end='<!-- docker-git:issue-managed:end -->'
99+
100+
extract_issue_block() {
101+
local ref="$1"
102+
103+
if ! git cat-file -e "$ref" 2>/dev/null; then
104+
return 0
105+
fi
106+
107+
local awk_status=0
108+
if ! git cat-file -p "$ref" | awk -v start="$issue_managed_start" -v end="$issue_managed_end" '
109+
BEGIN { in_block = 0; found = 0 }
110+
$0 == start { in_block = 1; found = 1 }
111+
in_block == 1 { print }
112+
$0 == end && in_block == 1 { in_block = 0; exit }
113+
END {
114+
if (found == 0) exit 3
115+
if (in_block == 1) exit 2
116+
}
117+
'; then
118+
awk_status=$?
119+
if [[ "$awk_status" -eq 3 ]]; then
120+
return 0
121+
fi
122+
return "$awk_status"
123+
fi
124+
}
125+
126+
commit_changes_issue_block() {
127+
local commit="$1"
128+
local parent=""
129+
local commit_block=""
130+
local parent_block=""
131+
132+
if ! git diff-tree --no-commit-id --name-only -r "$commit" -- AGENTS.md | grep -qx "AGENTS.md"; then
133+
return 1
134+
fi
135+
136+
if ! commit_block="$(extract_issue_block "$commit:AGENTS.md")"; then
137+
return 2
138+
fi
139+
140+
parent="$(git rev-list --parents -n 1 "$commit" | awk '{print $2}')"
141+
if [[ -n "$parent" ]]; then
142+
if ! parent_block="$(extract_issue_block "$parent:AGENTS.md")"; then
143+
return 2
144+
fi
145+
fi
146+
147+
if [[ "$commit_block" != "$parent_block" ]]; then
148+
return 0
149+
fi
150+
return 1
151+
}
152+
153+
check_issue_managed_block_range() {
154+
local local_sha="$1"
155+
local remote_sha="$2"
156+
local commits=""
157+
local commit=""
158+
local guard_status=0
159+
160+
if [[ "$local_sha" == "$zero_sha" ]]; then
161+
return 0
162+
fi
163+
164+
if [[ "$remote_sha" == "$zero_sha" ]]; then
165+
commits="$(git rev-list "$local_sha" --not --remotes 2>/dev/null || true)"
166+
if [[ -z "$commits" ]]; then
167+
commits="$local_sha"
168+
fi
169+
else
170+
commits="$(git rev-list "$remote_sha..$local_sha" 2>/dev/null || true)"
171+
fi
172+
173+
for commit in $commits; do
174+
commit_changes_issue_block "$commit"
175+
guard_status=$?
176+
if [[ "$guard_status" -eq 0 ]]; then
177+
echo "docker-git: push contains commit updating managed issue block in AGENTS.md: $commit"
178+
echo "docker-git: this block is runtime context and must stay outside repository history."
179+
return 1
180+
fi
181+
if [[ "$guard_status" -eq 2 ]]; then
182+
echo "docker-git: failed to parse managed issue block in AGENTS.md for commit $commit"
183+
echo "docker-git: push blocked to prevent committing runtime workspace metadata."
184+
return 1
185+
fi
186+
done
187+
188+
return 0
189+
}
96190
97191
while read -r local_ref local_sha remote_ref remote_sha; do
98192
if [[ -z "$remote_ref" ]]; then
@@ -105,15 +199,19 @@ while read -r local_ref local_sha remote_ref remote_sha; do
105199
exit 1
106200
fi
107201
done
108-
if [[ "$local_sha" == "0000000000000000000000000000000000000000" && "$remote_ref" == refs/heads/* ]]; then
202+
if ! check_issue_managed_block_range "$local_sha" "$remote_sha"; then
203+
exit 1
204+
fi
205+
if [[ "$local_sha" == "$zero_sha" && "$remote_ref" == refs/heads/* ]]; then
109206
if [[ "$allow_delete" != "1" ]]; then
110207
echo "docker-git: deleting remote branches is disabled (set DOCKER_GIT_ALLOW_DELETE=1 to override)."
111208
exit 1
112209
fi
113210
fi
114211
done
115212
EOF
116-
chmod 0755 "$PRE_PUSH_HOOK"
117-
fi
213+
chmod 0755 "$PRE_PUSH_HOOK"
118214
git config --system core.hooksPath "$HOOKS_DIR" || true
119215
git config --global core.hooksPath "$HOOKS_DIR" || true`
216+
217+
export const renderEntrypointGitHooks = (): string => entrypointGitHooksTemplate

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

Lines changed: 1 addition & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -133,89 +133,6 @@ const renderCloneCacheFinalize = (config: TemplateConfig): string =>
133133
fi
134134
fi`
135135

136-
const renderIssueWorkspaceAgentsResolve = (): string =>
137-
`ISSUE_ID="$(printf "%s" "$REPO_REF" | sed -E 's#^issue-##')"
138-
ISSUE_URL=""
139-
if [[ "$REPO_URL" == https://github.com/* ]]; then
140-
ISSUE_REPO="$(printf "%s" "$REPO_URL" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
141-
if [[ -n "$ISSUE_REPO" ]]; then
142-
ISSUE_URL="https://github.com/$ISSUE_REPO/issues/$ISSUE_ID"
143-
fi
144-
fi
145-
if [[ -z "$ISSUE_URL" ]]; then
146-
ISSUE_URL="n/a"
147-
fi`
148-
149-
const renderIssueWorkspaceAgentsManagedBlock = (): string =>
150-
`ISSUE_AGENTS_PATH="$TARGET_DIR/AGENTS.md"
151-
ISSUE_MANAGED_START="<!-- docker-git:issue-managed:start -->"
152-
ISSUE_MANAGED_END="<!-- docker-git:issue-managed:end -->"
153-
ISSUE_MANAGED_BLOCK="$(cat <<EOF
154-
$ISSUE_MANAGED_START
155-
Issue workspace: #$ISSUE_ID
156-
Issue URL: $ISSUE_URL
157-
Workspace path: $TARGET_DIR
158-
159-
Работай только над этим issue, если пользователь не попросил другое.
160-
Если нужен первоисточник требований, открой Issue URL.
161-
$ISSUE_MANAGED_END
162-
EOF
163-
)"`
164-
165-
const renderIssueWorkspaceAgentsWrite = (): string =>
166-
`if [[ ! -e "$ISSUE_AGENTS_PATH" ]]; then
167-
printf "%s\n" "$ISSUE_MANAGED_BLOCK" > "$ISSUE_AGENTS_PATH"
168-
else
169-
TMP_ISSUE_AGENTS_PATH="$(mktemp)"
170-
if grep -qF "$ISSUE_MANAGED_START" "$ISSUE_AGENTS_PATH" && grep -qF "$ISSUE_MANAGED_END" "$ISSUE_AGENTS_PATH"; then
171-
awk -v start="$ISSUE_MANAGED_START" -v end="$ISSUE_MANAGED_END" -v repl="$ISSUE_MANAGED_BLOCK" '
172-
BEGIN { in_block = 0 }
173-
$0 == start { print repl; in_block = 1; next }
174-
$0 == end { in_block = 0; next }
175-
in_block == 0 { print }
176-
' "$ISSUE_AGENTS_PATH" > "$TMP_ISSUE_AGENTS_PATH"
177-
else
178-
sed \
179-
-e '/^# docker-git issue workspace$/d' \
180-
-e '/^Issue workspace: #/d' \
181-
-e '/^Issue URL: /d' \
182-
-e '/^Workspace path: /d' \
183-
-e '/^Работай только над этим issue, если пользователь не попросил другое[.]$/d' \
184-
-e '/^Если нужен первоисточник требований, открой Issue URL[.]$/d' \
185-
"$ISSUE_AGENTS_PATH" > "$TMP_ISSUE_AGENTS_PATH"
186-
if [[ -s "$TMP_ISSUE_AGENTS_PATH" ]]; then
187-
printf "\n" >> "$TMP_ISSUE_AGENTS_PATH"
188-
fi
189-
printf "%s\n" "$ISSUE_MANAGED_BLOCK" >> "$TMP_ISSUE_AGENTS_PATH"
190-
fi
191-
mv "$TMP_ISSUE_AGENTS_PATH" "$ISSUE_AGENTS_PATH"
192-
fi
193-
if [[ -e "$ISSUE_AGENTS_PATH" ]]; then
194-
chown 1000:1000 "$ISSUE_AGENTS_PATH" || true
195-
fi`
196-
197-
const renderIssueWorkspaceAgentsExclude = (): string =>
198-
`EXCLUDE_PATH="$TARGET_DIR/.git/info/exclude"
199-
if [[ -f "$ISSUE_AGENTS_PATH" ]]; then
200-
touch "$EXCLUDE_PATH"
201-
if ! grep -qx "AGENTS.md" "$EXCLUDE_PATH"; then
202-
printf "%s\n" "AGENTS.md" >> "$EXCLUDE_PATH"
203-
fi
204-
fi`
205-
206-
const renderIssueWorkspaceAgents = (): string =>
207-
[
208-
`if [[ "$CLONE_OK" -eq 1 && "$REPO_REF" == issue-* && -d "$TARGET_DIR/.git" ]]; then`,
209-
renderIssueWorkspaceAgentsResolve(),
210-
"",
211-
renderIssueWorkspaceAgentsManagedBlock(),
212-
"",
213-
renderIssueWorkspaceAgentsWrite(),
214-
"",
215-
renderIssueWorkspaceAgentsExclude(),
216-
"fi"
217-
].join("\n")
218-
219136
const renderCloneBody = (config: TemplateConfig): string =>
220137
[
221138
renderCloneBodyStart(config),
@@ -224,9 +141,7 @@ const renderCloneBody = (config: TemplateConfig): string =>
224141
"",
225142
renderCloneRemotes(config),
226143
"",
227-
renderCloneCacheFinalize(config),
228-
"",
229-
renderIssueWorkspaceAgents()
144+
renderCloneCacheFinalize(config)
230145
].join("\n")
231146

232147
const renderCloneFinalize = (): string =>

0 commit comments

Comments
 (0)