Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/mirror-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: mirror-sync

# 한국어 정본을 고치면서 docs/en 미러를 두고 가는 PR을 막는다.
# 번역의 정확성이 아니라 "같이 갱신했는가"만 본다.
# 한쪽만 바꾸는 것이 의도된 PR에는 'mirror-sync-exempt' 라벨을 붙인다.

on:
pull_request:
paths:
- "CLAUDE.md"
- "AGENTS.md"
- "agents/**"
- "reference/**"
- "docs/en/**"
- "README.md"
- "README.ko.md"
- "scripts/check-mirror-sync.sh"
- ".github/workflows/mirror-sync.yml"

permissions:
contents: read

jobs:
mirror-sync:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'mirror-sync-exempt') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: 정본과 docs/en 미러가 함께 갱신되었는지 검사
run: bash scripts/check-mirror-sync.sh "origin/${{ github.base_ref }}"
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ claude plugin validate . # 매니페스트·구조 검증

`.claude/agents/`나 `.claude/settings.json`으로 되돌리지 마라 — 플러그인 레이아웃(`agents/`, `hooks/hooks.json`)이 정본이다.

**한국어 정본을 고치면 `docs/en/` 미러도 같은 PR에서 고쳐라.** `CLAUDE.md`·`AGENTS.md`·`agents/`·`reference/`가 대상이고, CI(`mirror-sync`)가 한쪽만 바뀐 PR을 막는다. 한쪽만 바꾸는 것이 의도된 PR에는 `mirror-sync-exempt` 라벨을 붙인다.

```bash
bash scripts/check-mirror-sync.sh # PR 모드 — origin/main과의 변경분 검사
bash scripts/check-mirror-sync.sh --audit # 감사 모드 — 저장소 전체의 미러 최신성
```

## 경로 규칙 (중요)

플러그인 사용자는 **자기 작업 폴더**에서 이걸 쓴다. 저장소 안이 아니다. 따라서:
Expand Down
9 changes: 6 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ evaluation. Contributions of all sizes are welcome.

1. Open an issue describing the change (or comment on an existing one).
2. Fork, create a topic branch, make your change.
3. Keep Korean and English docs in sync where practical (the Korean files under
`.claude/`, `CLAUDE.md`, `AGENTS.md`, `reference/` are the executable source;
English lives under `docs/en/`).
3. Keep Korean and English docs in sync (the Korean files `CLAUDE.md`,
`AGENTS.md`, `agents/`, and `reference/` are the executable source; English
lives under `docs/en/`, mirroring the same path). Update both sides in the
same PR — the `mirror-sync` check enforces this, and a deliberately
one-sided PR needs the `mirror-sync-exempt` label. Run it yourself with
`bash scripts/check-mirror-sync.sh` (or `--audit` for the whole repo).
4. Open a pull request with a clear description and, for methodology changes, the
supporting citation.

Expand Down
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ python3 scripts/open_runner.py --out docs/open-model-demo-output.md
DevEval의 증거 게이트는 "근거 없으면 등급 없음"이다. 같은 관리자의
[ODA Intelligence 플러그인](https://github.com/amnotyoung/oda-intelligence-plugin)을
함께 설치하면 **대조 가능한 근거의 범위**가 넓어진다 — 공개 read-only MCP
게이트웨이 도구 29종: 국가 지표·타 공여기관 활동(IATI·세계은행 등, 적절성·일관성),
게이트웨이 도구 30종: 국가 지표·타 공여기관 활동(IATI·세계은행 등, 적절성·일관성),
같은 나라 한국 ODA 사업 지도(중복·연계 확인), KOICA 규정 조문 전문, 그리고
보고서가 인용한 조문의 실재를 검증하는 `verify_citation`.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Or reproduce it on free Google Colab: [`notebooks/open-model-demo.ipynb`](notebo

DevEval's evidence gate says *no evidence → no grade*. The optional
[ODA Intelligence plugin](https://github.com/amnotyoung/oda-intelligence-plugin)
(same maintainer) widens what counts as checkable evidence: 29 read-only tools
(same maintainer) widens what counts as checkable evidence: 30 read-only tools
over one public MCP gateway — country indicators and other donors' activities
(IATI, World Bank, …) for relevance/coherence, the Korean ODA project map for
duplication checks, KOICA regulation full text, and `verify_citation`, which
Expand Down
7 changes: 7 additions & 0 deletions docs/en/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ claude plugin validate . # validate the manifest & structure

Do not fall back to `.claude/agents/` or `.claude/settings.json` — the plugin layout (`agents/`, `hooks/hooks.json`) is canonical.

**When you change a Korean canonical file, update its `docs/en/` mirror in the same PR.** This covers `CLAUDE.md`, `AGENTS.md`, `agents/`, and `reference/`; CI (`mirror-sync`) blocks a PR that changes only one side. Label a deliberately one-sided PR `mirror-sync-exempt`.

```bash
bash scripts/check-mirror-sync.sh # PR mode — check the diff against origin/main
bash scripts/check-mirror-sync.sh --audit # audit mode — mirror freshness across the repo
```

## Path rules (important)

Plugin users run this from **their own working folder** — not from inside the repo. Therefore:
Expand Down
135 changes: 135 additions & 0 deletions scripts/check-mirror-sync.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
#
# 정본(한국어 실행본)과 docs/en 미러가 함께 갱신되는지 검사한다.
#
# scripts/check-mirror-sync.sh [base-ref] PR 모드 — base와의 변경분만 본다 (기본 origin/main)
# scripts/check-mirror-sync.sh --audit 감사 모드 — 저장소 전체의 미러 최신성을 본다
#
# 왜 필요한가: CONTRIBUTING의 규칙대로 한국어 파일이 실행 정본이고 영문은
# docs/en/ 미러다. 정본만 고치고 미러를 두면 미러가 조용히 낡는다 —
# docs/en/CLAUDE.md가 실제로 한 달간 옛 판에 머물렀다.
#
# 번역의 정확성은 검사하지 않는다. "같이 갱신했는가"만 본다.

set -uo pipefail
cd "$(git rev-parse --show-toplevel)" || exit 1

MIRROR_PREFIX="docs/en/"
# 미러를 두는 정본 — 루트 파일과 디렉터리
MIRRORED_FILES=("CLAUDE.md" "AGENTS.md")
MIRRORED_DIRS=("agents" "reference")
# 한 쌍이지만 한쪽만 고치는 일이 정상인 문서 — 실패가 아니라 경고
SOFT_PAIR_A="README.md"
SOFT_PAIR_B="README.ko.md"

# 한글 파일명이 \354... 로 이스케이프되면 경로 비교가 깨진다
git() { command git -c core.quotepath=false "$@"; }

is_mirrored() { # 경로가 미러를 둬야 하는 정본인가
local path=$1 f d
for f in "${MIRRORED_FILES[@]}"; do
[ "$path" = "$f" ] && return 0
done
for d in "${MIRRORED_DIRS[@]}"; do
case "$path" in "$d"/*.md) return 0 ;; esac
done
return 1
}

annotate() { # GitHub Actions 주석 (로컬에서는 평문)
local level=$1 message=$2
if [ -n "${GITHUB_ACTIONS:-}" ]; then
printf '::%s::%s\n' "$level" "$message"
else
printf ' [%s] %s\n' "$level" "$message"
fi
}

audit_mode() {
local failures=0 canonical mirror c_time m_time
echo "미러 감사 — 정본이 미러보다 나중에 바뀐 쌍을 찾는다"
echo

while IFS= read -r canonical; do
is_mirrored "$canonical" || continue
mirror="${MIRROR_PREFIX}${canonical}"

if [ ! -f "$mirror" ]; then
annotate error "미러 없음: $canonical → $mirror"
failures=$((failures + 1))
continue
fi

c_time=$(git log -1 --format=%ct -- "$canonical")
m_time=$(git log -1 --format=%ct -- "$mirror")
if [ -n "$c_time" ] && [ -n "$m_time" ] && [ "$c_time" -gt "$m_time" ]; then
annotate error "미러가 낡음: $mirror (정본 $canonical 이 더 최신)"
failures=$((failures + 1))
fi
done < <(git ls-files)

if [ "$failures" -eq 0 ]; then
echo "모든 미러가 정본과 같거나 더 최신이다."
return 0
fi
echo
echo "낡은 미러 ${failures}건. 정본을 다시 읽고 docs/en 쪽을 갱신하라."
return 1
}

pr_mode() {
local base=$1 base_sha changed failures=0 canonical mirror
base_sha=$(git merge-base "$base" HEAD 2>/dev/null)
if [ -z "$base_sha" ]; then
echo "base ref를 해석할 수 없다: $base" >&2
return 2
fi

changed=$(git diff --name-only "$base_sha" HEAD)
if [ -z "$changed" ]; then
echo "변경된 파일이 없다."
return 0
fi

echo "base $(git rev-parse --short "$base_sha") 와 비교 — 변경 $(printf '%s\n' "$changed" | wc -l | tr -d ' ')건"
echo

while IFS= read -r canonical; do
[ -z "$canonical" ] && continue
is_mirrored "$canonical" || continue
mirror="${MIRROR_PREFIX}${canonical}"

if printf '%s\n' "$changed" | grep -qxF "$mirror"; then
echo " OK $canonical → $mirror 같이 갱신됨"
else
annotate error "$canonical 이 바뀌었는데 $mirror 는 그대로다. 같은 PR에서 미러도 갱신하라."
failures=$((failures + 1))
fi
done < <(printf '%s\n' "$changed")

# README 쌍은 한쪽만 고치는 일이 정상이라 경고만 한다
local a_changed=no b_changed=no
printf '%s\n' "$changed" | grep -qxF "$SOFT_PAIR_A" && a_changed=yes
printf '%s\n' "$changed" | grep -qxF "$SOFT_PAIR_B" && b_changed=yes
if [ "$a_changed" != "$b_changed" ]; then
annotate warning "$SOFT_PAIR_A 와 $SOFT_PAIR_B 중 한쪽만 바뀌었다. 의도한 것이면 그대로 두라."
fi

echo
if [ "$failures" -eq 0 ]; then
echo "정본과 미러가 함께 갱신되었다."
return 0
fi
echo "미러가 빠진 정본 ${failures}건."
echo "의도적으로 한쪽만 바꾸는 PR이면 'mirror-sync-exempt' 라벨을 붙이면 이 검사를 건너뛴다."
return 1
}

case "${1:---pr}" in
--audit) audit_mode ;;
--pr) pr_mode "origin/main" ;;
-h | --help)
sed -n '3,12p' "$0"
;;
*) pr_mode "$1" ;;
esac
Loading