Skip to content

Commit a451135

Browse files
authored
feat(ci): docs sync check hook + CI workflow + 補 CI/CD docs (#56)
對應 user 反映「程式碼改動後該檢查 .md 是否同步更新」的 process gap。 ## 新增機制(L2 + L4 雙保險) ### L2 — Claude Code PreToolUse hook - `.claude/settings.json`:PreToolUse matcher=Bash → 跑 check 腳本 - `.github/scripts/check-docs-sync.sh`:共用 script,hook 模式從 stdin 讀 JSON - 違規時 exit 2 block + stderr 提醒 - Override:commit message 加 `[skip-docs-check]` 或 env `SKIP_DOCS_CHECK=1` ### L4 — GitHub Actions PR check - `.github/workflows/docs-sync-check.yml`:PR 觸發,跑同一支 script(CI 模式,傳 PR base SHA) - 違規時 job fail / PR check 紅(非 required,軟提醒) ### 共用偵測範圍 觸發檢查的 code 路徑: - `conftest.py` / `pages/` / `utils/` / `tests/*/conftest.py` - `.github/workflows/*.yml` / `.github/scripts/` - `.claude/settings.json` ## 補 CI/CD docs(B 級) 過去 5 個 CI/CD PR(#51~#55)沒同步動 docs。本 PR 補齊: - `CLAUDE.md`:加 CI/CD 段 + Docs sync check 段;`Running Tests` 補 `CI=true` 跑法 - `docs/cicd.md`(新):workflow / cron 時段 / secrets 清單 / 看 run / 下載 artifact / debug fail / docs sync check 操作 / override 方式 - `docs/README.md`:index 加 `cicd.md` + 補上漏的 `agent-skills-workflow.md` ## 其他 - `.gitignore` 鬆綁:`!.claude/settings.json`(hook 配置要團隊共用) - script 本機測過 4 case:違規 block / 含 doc pass / sentinel override / 非 git commit pass Hook 只在「下次新 Claude session 啟動」載入(本 commit 沒被 hook 檢查到); L4 PR check 立即生效(本 PR 自己會跑一次)。
1 parent c2a85a1 commit a451135

7 files changed

Lines changed: 313 additions & 1 deletion

File tree

.claude/settings.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
3+
"hooks": {
4+
"PreToolUse": [
5+
{
6+
"matcher": "Bash",
7+
"hooks": [
8+
{
9+
"type": "command",
10+
"command": "bash .github/scripts/check-docs-sync.sh"
11+
}
12+
]
13+
}
14+
]
15+
}
16+
}

.github/scripts/check-docs-sync.sh

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env bash
2+
# Docs sync check:commit / PR 變動 code 但無對應 .md 更新時警示。
3+
#
4+
# 兩種使用模式(單一檔案共用):
5+
# Hook 模式(Claude Code PreToolUse 用,無 arg):
6+
# 從 stdin 讀 JSON({"tool_name", "tool_input": {"command"}})
7+
# 僅當 tool=Bash 且 command 含 `git commit` 才作用
8+
# 檢查 git diff --cached 的 staged 檔案
9+
# 違規 → exit 2(block + stderr 給 Claude 看)
10+
#
11+
# CI 模式(GH Actions PR workflow 用,傳 base SHA):
12+
# 檢查 git diff <BASE>..HEAD 的 PR 全變動
13+
# 違規 → exit 1(job fail / PR check 紅)
14+
#
15+
# Override(兩模式皆生效):
16+
# 1. commit message 含 `[skip-docs-check]` → 略過
17+
# 2. env var `SKIP_DOCS_CHECK=1` → 略過
18+
19+
set -euo pipefail
20+
21+
# === 設定(fact-only,要改 mapping 直接動下面)===
22+
# 視為「程式碼」的路徑 regex(任何 match 都會觸發檢查)
23+
CODE_RE='^(conftest\.py|pages/.*|utils/.*|tests/[^/]+/conftest\.py|\.github/workflows/[^/]+\.yml|\.github/scripts/.*|\.claude/settings\.json)$'
24+
25+
# 視為「文件」的路徑 regex
26+
DOC_RE='\.md$'
27+
28+
# === Helpers ===
29+
warn_block() {
30+
local changed_code="$1"
31+
local exit_code="$2"
32+
cat >&2 <<EOF
33+
34+
⚠️ Docs sync check:程式碼有變動但無對應 .md 更新
35+
36+
變動的 code 檔案:
37+
$(echo "$changed_code" | sed 's/^/ - /')
38+
39+
請重新確認下列 docs 是否需要同步更新:
40+
- CLAUDE.md(架構 / 慣例)
41+
- docs/cicd.md(CI/CD 觸發規則 / 操作)
42+
- docs/i18n_locale_text_reference.md(LT selector / i18n 對照表)
43+
- docs/testing-strategy.md(測試策略)
44+
- docs/README.md(文件索引)
45+
46+
確認不需要更新時的 override 方式:
47+
- commit message 加 sentinel:[skip-docs-check] 並附理由
48+
- 或 env var:SKIP_DOCS_CHECK=1
49+
50+
EOF
51+
exit "$exit_code"
52+
}
53+
54+
# === 模式判定 ===
55+
if [ -n "${1:-}" ]; then
56+
MODE="ci"
57+
BASE_SHA="$1"
58+
else
59+
MODE="hook"
60+
fi
61+
62+
# === Override:env var ===
63+
if [ "${SKIP_DOCS_CHECK:-}" = "1" ]; then
64+
exit 0
65+
fi
66+
67+
# === Hook 模式:從 stdin 解析 JSON ===
68+
if [ "$MODE" = "hook" ]; then
69+
STDIN_INPUT="$(cat)"
70+
71+
TOOL_NAME=$(echo "$STDIN_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('tool_name', ''))" 2>/dev/null || echo "")
72+
COMMAND=$(echo "$STDIN_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('tool_input', {}).get('command', ''))" 2>/dev/null || echo "")
73+
74+
# 不是 Bash tool / 不是 git commit → 立即放行
75+
if [ "$TOOL_NAME" != "Bash" ] || ! echo "$COMMAND" | grep -q "git commit"; then
76+
exit 0
77+
fi
78+
79+
# commit message 含 sentinel → 略過
80+
if echo "$COMMAND" | grep -q '\[skip-docs-check\]'; then
81+
exit 0
82+
fi
83+
84+
CHANGED=$(git diff --cached --name-only)
85+
EXIT_CODE=2 # Claude PreToolUse hook:exit 2 = block + stderr
86+
fi
87+
88+
# === CI 模式:對 PR 全變動 ===
89+
if [ "$MODE" = "ci" ]; then
90+
# PR 全 commit 任一含 sentinel → 略過
91+
if git log "$BASE_SHA"..HEAD --pretty=format:%B | grep -q '\[skip-docs-check\]'; then
92+
exit 0
93+
fi
94+
CHANGED=$(git diff --name-only "$BASE_SHA"..HEAD)
95+
EXIT_CODE=1 # workflow fail
96+
fi
97+
98+
# === 共用核心檢查 ===
99+
CHANGED_CODE=$(echo "$CHANGED" | grep -E "$CODE_RE" || true)
100+
CHANGED_DOCS=$(echo "$CHANGED" | grep -E "$DOC_RE" || true)
101+
102+
if [ -n "$CHANGED_CODE" ] && [ -z "$CHANGED_DOCS" ]; then
103+
warn_block "$CHANGED_CODE" "$EXIT_CODE"
104+
fi
105+
106+
exit 0
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Docs Sync Check
2+
3+
# 對 PR 變動做 code-vs-docs 同步檢查。
4+
# - PR 內任一 commit message 含 [skip-docs-check] 或設 SKIP_DOCS_CHECK=1 → 略過
5+
# - 違規 → job fail(PR check 顯示紅,不強制 block merge;要強制可在 Settings → Branches 設 required check)
6+
7+
on:
8+
pull_request:
9+
branches: [main]
10+
11+
# 強制 actions 用 Node 24
12+
env:
13+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
14+
15+
jobs:
16+
docs-sync-check:
17+
name: Docs Sync Check
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 3
20+
21+
steps:
22+
- name: Checkout (fetch full history to diff against base)
23+
uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0 # 預設 1 不夠,需有 base SHA 才能 diff
26+
27+
- name: Run docs sync check
28+
run: |
29+
# PR base commit SHA(GitHub Actions context 內建)
30+
bash .github/scripts/check-docs-sync.sh ${{ github.event.pull_request.base.sha }}

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ screenshots/
3232
dev-notes/*
3333
!dev-notes/README.md
3434

35-
# Claude Code — 追蹤 skills agents(團隊共用)
35+
# Claude Code — 追蹤 skills / agents / settings.json(團隊共用 hook 配置
3636
.claude/*
3737
!.claude/skills/
3838
!.claude/agents/
39+
!.claude/settings.json
3940
.claude/skills/playwright-pro/
4041

4142
# Python virtual environment

CLAUDE.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,32 @@ Key `.env` variables:
3131
.venv/bin/pytest -m p0 # by marker
3232
.venv/bin/pytest -m login # by marker
3333
.venv/bin/pytest tests/rc/test_p0_smoke.py::TestLogin::test_login_success # single test
34+
CI=true .venv/bin/pytest tests/rc/test_p0_smoke.py # 模擬 CI 模式:headless chromium 直接 launch(無 CDP)
3435
```
3536

3637
Reports are written to `reports/report.html` (self-contained HTML).
3738

39+
## CI/CD
40+
41+
GitHub Actions 自動跑測試:
42+
43+
- `p0.yml`:PR 開啟 / push to main / 每天 09:00 台灣 / 手動 → RC + LT + RE + RD P0 smoke 4 站 matrix
44+
- `full-regression.yml`:每週一 08:00 台灣 / 手動 → 4 站全套(P0 + feature)
45+
- `docs-sync-check.yml`:PR 時檢查 code 變動是否有對應 .md 更新(hook 機制 + CI 雙保險)
46+
47+
詳細的 trigger 規則、cron 時段、secrets 清單、如何看 run / 下載 artifact / 加 secret / debug fail → 見 [`docs/cicd.md`](docs/cicd.md)
48+
49+
## Docs sync check(hook + CI 雙保險)
50+
51+
每次 commit / PR 自動檢查「code 變動是否同步更新 docs」:
52+
53+
- **Hook**`.claude/settings.json` + `.github/scripts/check-docs-sync.sh`):Claude 在跑 `git commit` 之前 block,stderr 提醒重看哪些 .md
54+
- **CI**`.github/workflows/docs-sync-check.yml`):PR 時相同檢查跑一次,違規 → PR check 紅
55+
56+
確認****需要更新時的 override:
57+
- commit message 加 sentinel `[skip-docs-check]` 並附理由
58+
- 或設 env var `SKIP_DOCS_CHECK=1`
59+
3860
## Test Strategy
3961

4062
| 測試類型 | Fixture | Scope | 適用情境 |

docs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
| [`testing-strategy.md`](./testing-strategy.md) | 測試分層(L0~L3)、通過標準、flaky 處理、並行限制、marker 規範 |
5050
| [`lt-dashboard-sitemap.md`](./lt-dashboard-sitemap.md) | LT 後台完整功能地圖(25 頁 × 8 分類),後台測試撰寫的事實參考 |
5151
| [`dashboard-technical-notes.md`](./dashboard-technical-notes.md) | 後台測試技術注意事項(TOTP、browser context 分離、session 管理、fixture scope 策略) |
52+
| [`cicd.md`](./cicd.md) | GitHub Actions 操作指南(trigger 規則 / cron / secrets / 看 run / 下載 artifact / docs sync check) |
53+
| [`agent-skills-workflow.md`](./agent-skills-workflow.md) | Agent / skill / subagent 6+3 接力工作流(已存在但漏在表中,補入) |
5254

5355
---
5456

docs/cicd.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# CI/CD 操作指南
2+
3+
GitHub Actions 自動跑這個 repo 的測試。當前 3 個 workflow:
4+
5+
| Workflow | 觸發 | 跑什麼 | 預估時長 |
6+
|---|---|---|---|
7+
| `p0.yml` | PR / push to main / daily cron / 手動 | 4 站 P0 smoke matrix | ~3 分(4 job 並行) |
8+
| `full-regression.yml` | weekly cron(週一) / 手動 | 4 站全套(P0 + feature) | ~17 分(4 job 並行) |
9+
| `docs-sync-check.yml` | PR | code 變動是否同步 docs | < 30 秒 |
10+
11+
## Cron 時段(台灣時區)
12+
13+
| Workflow | Cron (UTC) | 台灣時間 |
14+
|---|---|---|
15+
| `p0.yml` daily | `0 1 * * *` | 每天 09:00 |
16+
| `full-regression.yml` weekly | `0 0 * * 1` | 每週一 08:00 |
17+
18+
注:GitHub cron 可能延遲幾分鐘到 1 小時,負載高時甚至 skip 該 schedule(無補跑機制)。
19+
20+
## Secrets 清單
21+
22+
`https://github.com/<owner>/<repo>/settings/secrets/actions` 設定,共 12 個:
23+
24+
| Site | Secrets |
25+
|---|---|
26+
| RC | `SITE_RC_URL` / `SITE_RC_USERNAME` / `SITE_RC_PASSWORD` |
27+
| LT | `SITE_LT_URL` / `SITE_LT_USERNAME` / `SITE_LT_PASSWORD` |
28+
| RE | `SITE_RE_URL` / `SITE_RE_USERNAME` / `SITE_RE_PASSWORD` |
29+
| RD | `SITE_RD_URL` / `SITE_RD_USERNAME` / `SITE_RD_PASSWORD` |
30+
31+
### `gh` CLI 從本機 .env 一次設好
32+
33+
```bash
34+
for site in RC LT RE RD; do
35+
for k in URL USERNAME PASSWORD; do
36+
grep "^SITE_${site}_${k}=" .env | cut -d= -f2- | gh secret set "SITE_${site}_${k}"
37+
done
38+
done
39+
```
40+
41+
值直接從本機 `.env` 讀,不會 echo 在 terminal。
42+
43+
## Concurrency lock
44+
45+
| Lock group | 用途 |
46+
|---|---|
47+
| `p0-${{ github.ref }}` | 同 PR 重複 push 取消上一次 |
48+
| `full-regression-${{ github.ref }}` | 同 ref 重複手動觸發取消上一次 |
49+
| `<site>-account` | 同 site 帳號不能並行(避免互踢 session);p0.yml + full-regression.yml 共用同 group |
50+
51+
不同 site 不同帳號 → matrix 4 job 可並行。
52+
53+
## 看 workflow run
54+
55+
`https://github.com/<owner>/<repo>/actions` → 點該 run。
56+
57+
run 頁面看得到:
58+
- 各 job ✅/❌
59+
- 每 step 完整 log
60+
- **Job Summary**:pytest 測試結果以 markdown 表格顯示(pass/fail/skip 數量 + 失敗 test 名單),不用下載 artifact 即可 review
61+
- **Artifacts**(頁面最下方):
62+
- `report-html-<site>.zip` / `full-regression-report-<site>.zip`:自包式 HTML 報告
63+
- `failure-screenshots-<site>.zip`**只有失敗時上傳**):紅框截圖 + 自動生成 README.md
64+
65+
## 手動觸發
66+
67+
任一 workflow 的「Actions」頁面右上有「Run workflow」按鈕。
68+
69+
或用 `gh` CLI:
70+
71+
```bash
72+
gh workflow run p0.yml # P0 smoke
73+
gh workflow run full-regression.yml # 全套 regression
74+
gh workflow run p0.yml --ref <branch> # 指定 branch(workflow 須在 default branch)
75+
```
76+
77+
## 模擬 CI 本機跑
78+
79+
```bash
80+
CI=true .venv/bin/pytest tests/rc/test_p0_smoke.py
81+
```
82+
83+
`CI=true` 觸發 `conftest.py``_is_ci()` 分支,走 headless chromium(不走 CDP / Windows Chrome)。預設 `HEADLESS=true`,要 debug 看畫面就 `HEADLESS=false`
84+
85+
## Debug 失敗
86+
87+
| 現象 | 怎查 |
88+
|---|---|
89+
| 某 step fail | 點 step 看 log;常見:`Run <site> P0 smoke` 內可見 pytest output / traceback |
90+
| Test fail 但本機過 | 下載 `failure-screenshots-<site>.zip`,看 README.md 與紅框截圖比對本機行為 |
91+
| Workflow 沒觸發 | 確認 trigger 規則(如 `pull_request: branches: [main]` 只認對 main 的 PR) |
92+
| Cron 沒跑 | GitHub 負載高時可能 skip;隔天看 / 改 cron 加多個時段 |
93+
| Secret 缺 | `gh secret list` 確認;或 step log 會出現 `SITE_X_PASSWORD: ${{ secrets.SITE_X_PASSWORD }}` 變空字串 → 測試 fail 在 login |
94+
95+
## Docs sync check(hook + CI 雙保險)
96+
97+
每次 commit / PR 自動檢查「程式碼變動是否同步更新 docs」。
98+
99+
### 機制
100+
101+
- **L2 Hook**`.claude/settings.json` + `.github/scripts/check-docs-sync.sh`):Claude Code session 內,跑 `git commit` 之前 block,stderr 列出建議重看的 docs
102+
- **L4 CI**`.github/workflows/docs-sync-check.yml`):PR 時相同檢查跑一次,違規 → job fail / PR check 紅
103+
- 兩者**共用同一份 script**`.github/scripts/check-docs-sync.sh`),模式靠 stdin / arg 差異判斷
104+
105+
### 哪些 code 路徑會觸發
106+
107+
```
108+
conftest.py
109+
pages/
110+
utils/
111+
tests/*/conftest.py
112+
.github/workflows/*.yml
113+
.claude/settings.json
114+
```
115+
116+
### Override
117+
118+
確認****需要更新 docs 時:
119+
120+
| 方式 | 用途 |
121+
|---|---|
122+
| commit message 加 sentinel `[skip-docs-check]` 並附理由 | 單次 commit 略過 |
123+
| 設環境變數 `SKIP_DOCS_CHECK=1` | session 內所有 commit 略過 |
124+
125+
範例:
126+
127+
```bash
128+
git commit -m "fix(test): typo in test docstring [skip-docs-check] 純註解錯字無需動 docs"
129+
```
130+
131+
### 想升級成 hard block(PR merge 強制要求)
132+
133+
repo Settings → Branches → main → Add branch protection rule → Require status checks → 勾 `Docs Sync Check`
134+
135+
當前未設 required check,PR 紅但仍可 merge(軟提醒)。

0 commit comments

Comments
 (0)