From f563fe2a3b4218d618ce628731e70c6c52d59171 Mon Sep 17 00:00:00 2001 From: Chris Yau Date: Fri, 20 Mar 2026 21:38:32 +0800 Subject: [PATCH] fix: codex sync merges AGENTS.md instead of replacing it (#715) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sync script previously overwrote ~/.codex/AGENTS.md on every run, destroying any user-authored content. This adds marker-based merging ( / ) so only the ECC-managed section is replaced on subsequent runs, preserving user content outside the markers. Merge logic: - No file → create with markers - Both markers present (ordered, CRLF-safe) → replace only the ECC section - BEGIN without END (corrupted) → full replace (backup saved) - No markers at all → append ECC block (preserves existing content) Also fixes: - Symlink preservation: uses cat > instead of mv to write through symlinks - CRLF handling: strips \r in marker detection to handle Windows-edited files - Marker ordering: validates BEGIN appears before END, not just that both exist The legacy heading-match heuristic was intentionally removed per council review: any unmarked file is either user-authored (append is safe) or legacy ECC-generated (duplicates once, deduplicates on next run via markers). A timestamped backup is always saved before any mutation. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-authored-by: Claude Co-authored-by: Happy --- scripts/sync-ecc-to-codex.sh | 70 +++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/scripts/sync-ecc-to-codex.sh b/scripts/sync-ecc-to-codex.sh index 77c41980..557f8503 100644 --- a/scripts/sync-ecc-to-codex.sh +++ b/scripts/sync-ecc-to-codex.sh @@ -3,7 +3,7 @@ set -euo pipefail # Sync Everything Claude Code (ECC) assets into a local Codex CLI setup. # - Backs up ~/.codex config and AGENTS.md -# - Replaces AGENTS.md with ECC AGENTS.md +# - Merges ECC AGENTS.md into existing AGENTS.md (marker-based, preserves user content) # - Syncs Codex-ready skills from .agents/skills # - Generates prompt files from commands/*.md # - Generates Codex QA wrappers and optional language rule-pack prompts @@ -143,16 +143,68 @@ if [[ -f "$AGENTS_FILE" ]]; then run_or_echo "cp \"$AGENTS_FILE\" \"$BACKUP_DIR/AGENTS.md\"" fi -log "Replacing global AGENTS.md with ECC AGENTS + Codex supplement" +ECC_BEGIN_MARKER="" +ECC_END_MARKER="" + +compose_ecc_block() { + printf '%s\n' "$ECC_BEGIN_MARKER" + cat "$AGENTS_ROOT_SRC" + printf '\n\n---\n\n' + printf '# Codex Supplement (From ECC .codex/AGENTS.md)\n\n' + cat "$AGENTS_CODEX_SUPP_SRC" + printf '\n%s\n' "$ECC_END_MARKER" +} + +log "Merging ECC AGENTS into $AGENTS_FILE (preserving user content)" if [[ "$MODE" == "dry-run" ]]; then - printf '[dry-run] compose %s from %s + %s\n' "$AGENTS_FILE" "$AGENTS_ROOT_SRC" "$AGENTS_CODEX_SUPP_SRC" + printf '[dry-run] merge ECC block into %s from %s + %s\n' "$AGENTS_FILE" "$AGENTS_ROOT_SRC" "$AGENTS_CODEX_SUPP_SRC" else - { - cat "$AGENTS_ROOT_SRC" - printf '\n\n---\n\n' - printf '# Codex Supplement (From ECC .codex/AGENTS.md)\n\n' - cat "$AGENTS_CODEX_SUPP_SRC" - } > "$AGENTS_FILE" + replace_ecc_section() { + # Replace the ECC block between markers in $AGENTS_FILE with fresh content. + # Uses awk to correctly handle all positions including line 1. + local tmp + tmp="$(mktemp)" + local ecc_tmp + ecc_tmp="$(mktemp)" + compose_ecc_block > "$ecc_tmp" + awk -v begin="$ECC_BEGIN_MARKER" -v end="$ECC_END_MARKER" -v ecc="$ecc_tmp" ' + { gsub(/\r$/, "") } + $0 == begin { skip = 1; while ((getline line < ecc) > 0) print line; close(ecc); next } + $0 == end { skip = 0; next } + !skip { print } + ' "$AGENTS_FILE" > "$tmp" + # Write through the path (preserves symlinks) instead of mv + cat "$tmp" > "$AGENTS_FILE" + rm -f "$tmp" "$ecc_tmp" + } + + if [[ ! -f "$AGENTS_FILE" ]]; then + # No existing file — create fresh with markers + compose_ecc_block > "$AGENTS_FILE" + elif awk -v b="$ECC_BEGIN_MARKER" -v e="$ECC_END_MARKER" ' + { gsub(/\r$/, "") } + $0 == b { found_b = NR } $0 == e { found_e = NR } + END { exit !(found_b && found_e && found_b < found_e) } + ' "$AGENTS_FILE"; then + # Existing file with matched, correctly ordered ECC markers — replace only the ECC section + replace_ecc_section + elif grep -qF "$ECC_BEGIN_MARKER" "$AGENTS_FILE"; then + # BEGIN marker exists but END marker is missing (corrupted). Warn and + # replace the file entirely to restore a valid state. Backup was saved. + log "WARNING: found BEGIN marker but no END marker — replacing file (backup saved)" + compose_ecc_block > "$AGENTS_FILE" + else + # Existing file without markers — append ECC block, preserve user content. + # Note: legacy ECC-only files (from old '>' overwrite) will get a second copy + # on this first run. This is intentional — the alternative (heading-match + # heuristic) risks false-positive overwrites of user-authored files. The next + # run deduplicates via markers, and a timestamped backup was saved above. + log "No ECC markers found — appending managed block (backup saved)" + { + printf '\n\n' + compose_ecc_block + } >> "$AGENTS_FILE" + fi fi log "Syncing ECC Codex skills"