# Lane E — cross-cut+wave

> RETURN-only tu /fable-clone ensemble wf_1f6bd5e2-478 (vai investigator-codebase, model opus).
> Lead ghi (B3 single-writer) — sub KHONG tu ghi. agentId=a82e63dd4c6c618fa

---

VERDICT: Thứ-tự BẮT BUỘC = re-smudge EOL **NGAY BÂY GIỜ** (worktree đang sạch = cửa-sổ an-toàn DUY-NHẤT của phiên; sau khi session-end ghi STATUS/HANDOFF/agent-memory thì 6 file trong 188 đó bẩn → `git checkout` mù = hủy việc); re-smudge = **0 commit / 0 CI** (đo: index 1267/1267 `i/lf`, **0 `i/crlf`**) nên KHÔNG race commit với ai — nhưng `.claude/commands/session-end.md` bị **7 hạng-mục** cùng chạm → PHẢI 1-owner-1-pass em-main serial, và **B4-push-guard `-eq 0` chép nguyên mẫu sẽ DEADLOCK push** vì §5.0 SE cố-ý KHÔNG squash `wal:` kẹp-sâu.

---

## (a) ĐỒ-THỊ PHỤ-THUỘC

```
[D0] re-smudge 188  ──PHẢI TRƯỚC──> mọi write của phiên (STATUS/HANDOFF/agent-memory nằm TRONG 188)
  └─ [D1] .gitattributes ③a ──PHẢI TRƯỚC──> [D0] (checkout materialize theo attr mới)
  └─ [D2] [D0]=0 ──PHẢI TRƯỚC──> ④c EOL-assert cắm vào khối push (nếu ngược: CHẶN MỌI PUSH phiên này)

[D3] anh chốt H23-vs-H8 ──PHẢI TRƯỚC──> hmw.js edit + agents/*.md (2 vai H24)
[D4] anh chốt nhịp H24 ──PHẢI TRƯỚC──> memory-budget.json + scheduler
[D5] scope-set + anchor "Last updated" ──PHẢI TRƯỚC──> H24 detector-độ-tươi (nay: 0 coverage trên đích)
[D6] T3.4 agents/*.md (2 vai) ──ĐỒNG-THỜI──> hmw.js VALID_ROLES 12→14  → GỘP 1 owner (H24§(5) 3-việc-cùng-lúc)
[D7] Sàn-3 định-nghĩa canonical @tiep.md ──PHẢI TRƯỚC──> session-start BƯỚC 0.7 (chỉ được TRỎ, B1)
[D8] mọi edit code/cmd xong ──PHẢI TRƯỚC──> harness-11-engine.md (canonical tả cái ĐÃ làm, không cái ĐỊNH làm)
[D9] chạy THẬT xong ──PHẢI TRƯỚC──> stamp mark (H18: KHÔNG stamp run-id chưa chạy) + adap-report
[D10] push-guard: KHÔNG cần trước-các-việc-khác — chỉ cần DÍNH LIỀN lệnh push (n=3 treo vô hại tới lúc push)
```

**Race THẬT duy-nhất = worktree, KHÔNG phải git.** `git ls-files --eol` đo: **1267 `i/lf` · 25 `i/none` · 19 `i/-text` · 0 `i/crlf` · 0 `i/mixed`** → thêm `* text=auto eol=lf` **KHÔNG đổi 1 byte index** → `git add --renormalize .` = **diff rỗng** → re-smudge **không sinh commit**. Nên KHÔNG có race-commit. Nhưng ③b dùng `rm + git checkout --` trên 188 file, trong đó **6 file là đích ghi của chính session-end**: `docs/STATUS.md`, `docs/HANDOFF.md`, `docs/CLAUDE.md`, `.claude/agent-memory/{implementer-backend,reviewer}/MEMORY.md`, `.claude/agent-memory/.mfe-state.json` (+12 file `.claude/workflows/runs/**`). `git status` **hiện tại = chỉ 7 broadcast untracked** → cửa-sổ sạch.

---

## (b) BẢNG XUNG-ĐỘT FILE

| File | # hạng-mục chạm | Hạng-mục | Xử-lý |
|---|---|---|---|
| **`.claude/commands/session-end.md`** | **7** | B4-push-guard §5.2 · ④c EOL-assert (cùng khối push) · Sàn-2 sentinel-set(bước đầu)+clear(§6.4:143) · Sàn-5 hậu-kiểm cờ-vs-nội-dung · ④b wire validator · H24 counter-display · H24 spawn-site 2 vai (§L.b) | 🔴 **SERIAL, 1 owner = em-main, 1 EDIT PASS.** Fan-out = race chắc chắn |
| **`.claude/commands/session-start.md`** | **4** | Sàn-3 sửa BƯỚC 0.7(a):51 · H24 counter-display · H24 spawn-site 2 vai · H23 model-at-spawn | 🔴 SERIAL em-main. **SAU** `tiep.md` (D7) |
| **`.claude/workflows/hmw.js`** | **3** | Sàn-1 gate `walRegistered` (cạnh `checkpointApproved` :83-85) · VALID_ROLES :22-30 **12→14** · H23 note-đầu-cây (:32-42) | 🔴 **GỘP 1 EDIT** — tách = 3 lần đụng |
| **`scripts/governance-detectors.ps1`** | 2 (+1?) | H24 detector độ-tươi-tiêu-đề · H24 detector tuổi-carry · (C6 EOL-count? tùy chọn) | 🟢 **1 agent owner** (T3.1). Không ai khác chạm |
| **`docs/governance/harness-11-engine.md`** | ~5 | PHẦN N ext (Sàn 1-5) · §K/§O H24 · H23 · EOL floor · vocab | 🔴 SERIAL em-main **CUỐI** (D8) |
| **`.claude/commands/tiep.md`** | 2 | Sàn-3 ground-truth canonical · Sàn-2 sentinel-read | 🔴 em-main, **TRƯỚC** session-start |
| **`.claude/agent-memory/memory-budget.json`** | 2 | nhịp H24 · session-counter | 🟡 số = anh (D4). ⚠️ **Stop-hook auto-commit path này mỗi turn** (`wal-flush.ps1:7`) → counter phải **content-based `last_ticked_session:"S119"`**, KHÔNG blind-increment (idempotent khi resume) |
| **`.gitattributes`** | 1 | ③a | 🟢 1 owner |
| `scripts/<eol-validator>.ps1` (MỚI) | 1 | ④a | 🟢 file MỚI, disjoint |
| `.claude/agents/lead-stale.md` + `lead-gap.md` (MỚI) | 1 | H24(1) | 🟢 disjoint — **nhưng khoá cứng với hmw.js VALID_ROLES (D6)** |
| `tests/` recovery 3-lớp | 1 | Sàn-4 | 🟢 disjoint |
| `.claude/governance/ACTIVE-MARKS.md` | 1 | stamp | 🟡 anh (D9) |
| **188 file worktree** | 1 | ③b re-smudge | 🟢 **0 commit** → không đụng ai ở tầng git; đụng ở tầng **worktree-dirty** |

---

## (c) ĐỀ-XUẤT WAVE

### **W0 — PRE-FLIGHT EOL (em-main SOLO, ~5', KHÔNG fan-out, KHÔNG push)** 🔴 **PHẢI ĐẦU TIÊN**
| task | vai | file chạm | độc-lập |
|---|---|---|---|
| T0.1 `.gitattributes` ③a: `* text=auto eol=lf` + khai binary CLASS (19 `i/-text`) + `-text` fixture-byte nếu có | em-main | `.gitattributes` | — |
| T0.2 re-smudge 188 (worktree SẠCH → nhánh `rm+checkout --` hợp lệ) | em-main | 188 worktree | sau T0.1 |
| T0.3 verify: `git ls-files --eol \| grep -c 'i/lf.*w/crlf'` → **0**; `git status` vẫn **chỉ 7 untracked** | em-main | — | sau T0.2 |
| T0.4 `dotnet test` sanity (509) | em-main | — | sau T0.2 |

> Lý-do W0 đứng đầu: **cửa-sổ worktree-sạch đóng lại ngay khi bất kỳ wave nào ghi STATUS/HANDOFF/agent-memory.** Sau đó ③b buộc chuyển nhánh "rewrite byte tại chỗ", đắt + dễ sai. **Không commit ở W0** (commit `.gitattributes` local OK; push để W5).

### **W1 — INVEST (fan-out — ĐANG CHẠY, chính lane này)** · 5 lane RETURN-only · **0 file chạm** ✓

### **W2 — SPEC + OWNER-GATE (em-main SOLO)**
spec-file 3-mục → `runs/2026-07-15-S119-adap-6-broadcast/spec-*.md`. **Chặn ở đây chờ anh chốt 5 điểm mục (d).** File chạm: run-folder only.

### **W3 — EXECUTE fan-out (4 task, file-disjoint 4/4)** ✓ kiểm chéo: **0 giao**
| task | vai | file chạm | độc-lập với |
|---|---|---|---|
| T3.1 H24 ×2 detector | 🟨 implementer-backend | `scripts/governance-detectors.ps1` | T3.2/3/4 |
| T3.2 ④a validator đọc **BYTES** (`git cat-file -p` / `[IO.File]::ReadAllBytes`, **KHÔNG** `Get-Content`/`splitlines` — text-mode nuốt CR = PASS-giả) | 🟨 implementer-backend#2 | `scripts/eol-agent-validator.ps1` (MỚI) | tất cả |
| T3.3 Sàn-4 recovery-test **3 lớp** (dừng-sạch · đứt-giữa-run · **đóng-phiên-bị-cắt** + biến-thể self-heal) | 🟪 test-specialist | `tests/**` | tất cả |
| T3.4 *(chỉ khi anh duyệt)* 2 agent-file H24 | 🟦 investigator/implementer | `.claude/agents/lead-{stale,gap}.md` (MỚI) | tất cả |

> 🔴 **hmw.js KHÔNG vào W3** — nó bị T3.4 khoá (VALID_ROLES +2) + Sàn-1 + H23 = 3 hạng-mục → đẩy sang W4 em-main 1-edit.

### **W4 — SERIAL em-main (single-writer B3 — file dồn ≥3 hạng-mục)**
`T4.1 hmw.js` → `T4.2 tiep.md` → `T4.3 session-start.md` → `T4.4 session-end.md` (**1 pass gộp 7**) → `T4.5 memory-budget.json` → `T4.6 harness-11-engine.md` (cuối, D8).

### **W5 — VERIFY + PUSH (CÙNG 1 LƯỢT — bắt buộc)**
`ls-files --eol`=0 → **RESTART + spawn-probe registry** (④d: static-check KHÔNG falsifiable) → `dotnet test` → detector fire positive-control → **[khối 1 lượt]** `n=$(git log --format='%s' origin/main..HEAD | grep -c '^wal:')` + `eol=$(git ls-files --eol | grep -c 'i/lf.*w/crlf')` → cả 2 = 0 → `git push` → spawn 🟩 cicd-monitor (**1 CI run**).

### **W6 — CLOSE** adap-report · stamp mark (D9) · adap-request · send-email.

---

## (d) QUYỀN ANH (không được tự quyết)

1. 🔴 **H23(3)/H24(5) "pin model đầy-đủ ở tệp định-nghĩa" ⟂ H8 all-inherit (lệnh anh S66 "chất lượng trên chi phí")** — đo: **12/12 `model: inherit`**, `hmw.js:50` `return undefined // H8 all-inherit — KHÔNG override`. H23 §4 project-fit cho lối thoát: *"Hệ chỉ dùng MỘT model duy-nhất → (2)(3) rút gọn"* — SE all-inherit ≈ ca đó → đề-xuất **giữ H8, adopt (1)(5) [tường-minh + kỷ-luật động-từ], khai (2)(3) = rút-gọn**. **Nhưng H8 là lệnh anh → anh chốt.**
2. 🔴 **Nhịp H24** (rà-nhẹ N / rà-sâu M / jump-K / ngưỡng tuổi-carry M) — `role_boundary_note` + mark `RC-pqhuy1987-21-06-2026-01-58-01`: **con-số = quyền chủ-dự-án, AI KHÔNG tự-chỉnh**. Broadcast cũng cấm chép 6/15/3 của hub.
3. 🔴 **Roster 12→14** (2 vai H24) — chi-phí: all-inherit → 2 vai mới ăn top-tier mỗi phiên × 2 đầu-phiên. H24 cấm gộp vào tooling-auditor/harvest-curator. Anh chốt **thêm 2 / gộp / SKIP=n-a**.
4. 🔴 **57 `wal:` đã lọt origin/main** — H22 cấm rewrite đã-đẩy → **không gỡ được**. Anh chốt: **đơn/accept vĩnh-viễn** (đề-xuất: accept + ghi 1 dòng honest vào adap-report; hub tự khai 1 ca tương-tự).
5. 🔴 **Stamp mark mới?** EOL-floor + H23 + H24 = đổi CANONICAL §-rule → P6 scope-check = mark-candidate. (3 mark H-21/MTv3/H-22 **đã ký** `RC-pqhuy1987-14-07-2026-10-05-50` → chỉ đóng-dấu ledger, KHÔNG xin lại.) **P4 report-before-stamp.**
6. 🟡 **Scope-set H24 detector độ-tươi** (docs nào là "view phải theo source") — quyết-định phạm-vi, nên anh chốt cùng W2.

---

## (e) n/a HỢP-LỆ (có lý-lẽ)

| Mục | Nấc | Lý-lẽ đo được |
|---|---|---|
| EOL ③c "bọc nháy mô-tả chứa `: `" | **n-a** | **12/12 agent dùng `description: \|` block-scalar** → bộ-đọc không parse `: ` trong value. Trigger của broadcast = N/A. (Vẫn nên ④d LOAD-VERIFY — rẻ) |
| EOL ④a "positive-control = byte file-đã-chết" | **n-a → synthetic, khai nấc thấp** | agent **0 CR byte** cả blob lẫn worktree (13/13) → **KHÔNG có chứng-nhân sống để snapshot**. Broadcast: *"control thật hơn synthetic"* → SE chỉ đạt nấc synthetic-fixture, **PHẢI khai**, không được viết "verified như hub" |
| EOL ③d "RESTART rồi VERIFY" | **VẪN LÀM** | rẻ, và là bảo-đảm falsifiable duy-nhất |
| B4 điểm (b) "first-push: đếm `wal:` trên toàn `HEAD`" | **n-a — và là BẪY** | SE không first-push (`origin/main` = `3fde8af`). Nếu nhánh đó chạy → đếm **57+ → CHẶN VĨNH VIỄN**. Giữ nhánh nhưng phải khai n/a |
| H22-defect Sàn 1/5 | **KHÔNG n-a** | broadcast cho n-a khi "không có engine-fan-out" — SE **CÓ** `hmw.js` + 2 wrapper lệnh + **raw Workflow (đường thứ 2)** → phải phủ đủ 2 nửa |
| H24 §(3) "KHÔNG hook tự-bắn" | **khớp sẵn** | 🧊 ghost-wire cron đã gỡ S100 (CronList=0) → cadence-triggered = đúng nền SE |

---

## (f) RỦI-RO + CÁCH CHẶN

| # | Rủi-ro | Bằng-chứng | Chặn |
|---|---|---|---|
| **R1** 🔴 | **DEADLOCK PUSH.** B4 mẫu `[ "$n" -eq 0 ] && git push` đếm **toàn khoảng** `origin/main..HEAD`; nhưng `session-end.md:112` §5.0 chỉ squash `wal:` **LIÊN-TỤC từ HEAD**, và `:113` **cố-ý GIỮ NGUYÊN `wal:` kẹp-sâu** ("hard-safety đổi lấy không-rewrite"). 1 `wal:` sandwiched → n>0 vĩnh-viễn, §5.0 từ chối gộp → **không bao giờ push được** | `session-end.md:112-113` vs broadcast :77 | Guard = **2 nhánh**: `K`(trailing liên-tục)>0 → squash-rồi-push CÙNG LƯỢT; `K=0` nhưng `n>0` (kẹp-sâu) → **WARN + cho push** (đúng floor H22). **Hôm nay chưa cắn**: n=3 = K=3 (3/3 commit `origin/main..HEAD` đều `wal:`) — cấu-trúc thì đã hở |
| **R2** 🔴 | **④c cắm trước khi 188=0 → chặn mọi push phiên này** | 188 đang >0 | **W0 trước W4** (đúng thứ-tự wave); nếu đảo → assert = WARN ở lần đầu |
| **R3** 🔴 | **`git checkout` mù nuốt việc chưa-commit.** 6/188 = STATUS/HANDOFF/CLAUDE/agent-memory — **đích ghi của session-end** | danh-sách 188 | **W0 = trước mọi write** (status hiện: 7 untracked). Nếu lỡ trễ → **bắt buộc nhánh "rewrite byte tại chỗ"**, cấm `checkout`. Hub đã suýt mất file đúng bẫy này (③b) |
| **R4** 🟠 | **gotcha #53 return-garble (~21 lần).** W3 = 4 sub, 2 WRITE-lane | memory `feedback_agent_return_garble_recover` | W3 chạy **RUN-TRACE** (≥3 task = sàn H22 S111, `hmw.js:19`) → **WRITE-lane garble ≠ mất code** → recover bằng disk-truth (git diff + build + test), KHÔNG re-run |
| **R5** 🟠 | **gotcha #68 stale diagnostic.** `dotnet test` giữa W3 = snapshot dở | memory `feedback_stale_diagnostic_background_agent` | Chỉ tin build/test chạy **SAU** W3 kết thúc = W5 |
| **R6** 🟠 | **H24 detector độ-tươi = vừa NHIỄU vừa RỖNG.** Đo: 23 file có anchor `Last updated`, **19 = session log immutable** (sẽ flag hết = noise); `harness-11-engine.md` / `ACTIVE-MARKS.md` / `error-ledger.md` / `agents/README.md` = **KHÔNG anchor → BỎ QUA = 0 coverage trên đúng đích** | grep `Last updated` docs/ .claude/ | **D5**: scope-set + thêm anchor vào 4 doc canonical + exclude `docs/changelog/sessions/**` **TRƯỚC** khi viết detector |
| **R7** 🟠 | **Positive-control SỐNG (H24§(4)) — SE không có sẵn.** `docs/rag-setup-plan.md` Last-updated **2026-05-12** < mốc **2026-07-12** nhưng có thể **đúng-là-frozen** → false-positive, không phải bug thật. Detector "xanh từ commit đầu" = đúng anti-pattern H24 cảnh báo | mốc-phải = `RC-pqhuy1987-12-07-2026-11-43-45` | Ứng-viên thật: `harness-11-engine.md` header ghi **"Adopt S75 (2026-06-18)"** trong khi §E/§F/§G/§I/§J/§K/§M/§N thêm sau → **giữ nguyên làm chứng-nhân sống**, để detector FIRE trước mắt reviewer **rồi mới** vá. ⚠️ **Bẫy hub đã khai**: dòng đó gộp tiêu-đề+changelog **cùng 1 dòng vật-lý** → phải parse RIÊNG trường ngày-tiêu-đề, nếu không **detector tự giết positive-control của chính nó** |
| **R8** 🟡 | **Sàn-3 hook-hole.** SE **CÓ** Stop-hook auto-commit → tín-hiệu "commit chưa-đẩy" **MÙ** đúng 3 path `.claude/WAL.md` · `.claude/agent-memory/` · `.claude/workflows/runs/` | `wal-flush.ps1:7` | Bậc MẠNH phải dựa **`runs/<id>/` thiếu `*-synthesis.md`** (độc-lập git). **Đang đúng ngay bây giờ**: `runs/2026-07-15-S119-adap-6-broadcast/` = 0 synthesis = run mở |
| **R9** 🟡 | **Sàn-1 vá 1 wrapper = vỡ/bỏ-lọt wrapper khác.** SE có ≥3 đường vào engine: `/hmw` · `/fable-clone`+`/fable-real` · **raw Workflow tool** (S71: raw-Workflow **KHÔNG** qua writeGuard) | memory `feedback_harness10_run_trace` | Phủ **2 nửa**: (i) cổng-vào mỗi wrapper (ii) truyền cờ ở mọi lời-gọi nội-bộ. **raw-Workflow = kẽ CÒN-LẠI, khai công-khai** — gate input không phủ được |
| **R10** 🟡 | **Sàn-2 sentinel báo-động-giả lặp > kẽ gốc** | broadcast §Sàn-2 | Bắt buộc **self-heal 1 lần** + đường clear **LUÔN-CHẠY** kể cả no-op (§6.4:143-145 = bước cuối, đúng chỗ) |
| **R11** 🟡 | **Vocab-fork.** "vai-STALE" ⟂ detector C2/B3 **"derived-stale"** đã sống (baseline vừa đo: 15 flag `derived-stale` LOW) → C3 detector có thể tự flag | detector run @2026-07-15 | Đặt tên khác **HOẶC** ghi `docs/governance/vocab-alias-map.md`. Bài học S93: alias-map **KHÔNG** seed detector |
| **R12** 🟢 | Build vỡ vì LF trên runner | **0 `Environment.NewLine`, 0 literal `\r\n`** trong `tests/` + `src/Backend/` | Rủi-ro thấp — **nhưng runner worktree VẪN flip** (CI `git init`+fetch, `.gitattributes` áp) → **bắt buộc 1 CI GO verdict** |

---

## (g) CI IMPACT (đo tại `.gitea/workflows/deploy.yml:20-27`)

| Thay đổi | Trigger? |
|---|---|
| **`.gitattributes`** | 🔴 **CÓ** — không khớp rule nào |
| **`scripts/governance-detectors.ps1`**, `scripts/eol-validator.ps1` | 🔴 **CÓ** — `scripts/**.md` **chỉ** chặn `.md` |
| **`.claude/workflows/hmw.js`** | 🔴 **CÓ** — chỉ `.claude/workflows/runs/**` được chặn |
| `.claude/hooks/*.ps1` · `.claude/settings.json` | 🔴 **CÓ** (nếu chạm) |
| `tests/**.cs` | 🔴 **CÓ** |
| `.claude/agent-memory/memory-budget.json` | 🟢 **KHÔNG** — `:24 .claude/agent-memory/**` (khớp AS-6) |
| mọi `.md` (session-end/start/tiep/engine/ACTIVE-MARKS/broadcasts) | 🟢 **KHÔNG** — `:22 **/*.md` |
| `.claude/agents/lead-*.md` (2 vai mới) | 🟢 **KHÔNG** — `.md` |
| **re-smudge 188 file** | 🟢 **KHÔNG — và KHÔNG CẢ COMMIT.** Index đã **1267/1267 `i/lf`, 0 `i/crlf`** → worktree LF-hoá = **diff rỗng** (đúng TIP broadcast: *"đưa blob về LF nhưng `git diff` trống"* → kiểm bằng `ls-files --eol`, **KHÔNG** bằng `git diff`) |

**Kết-luận CI:** `paths-ignore` đánh trên **toàn set `before..after` của LẦN PUSH**, không per-commit → **push 1 lần = ĐÚNG 1 CI run** cho cả phiên, bất kể số commit. **Nên giữ 1 push duy-nhất @W5** (đừng push rải). Và **đừng thêm `.gitattributes` vào `paths-ignore`**: nó là item DUY-NHẤT có thể đổi hành-vi build của runner (worktree EOL flip) → cần đúng 1 GO verdict từ 🟩 cicd-monitor. *(Thêm `scripts/**.ps1` vào paths-ignore = đổi policy CI = **quyền anh**, không đề-xuất phiên này.)*

---

## ACCEPTANCE ĐO ĐƯỢC (6 dòng)

1. `git ls-files --eol | grep -c 'i/lf.*w/crlf'` → **0** (nay **188**)
2. `git status --porcelain | wc -l` sau W0 → **vẫn 7** (re-smudge không sinh diff = chứng-minh index vốn LF)
3. Restart → **spawn-probe** 12/12 (hoặc 14/14) agent sống — **KHÔNG** static-text check (④d: *"mọi kiểm static-text đều KHÔNG falsifiable"*)
4. §5.2 = **1 khối lệnh** chứa `n`(wal:) **và** `eol`(count) **và** `git push` — đọc lại file thấy 3 thứ cùng khối, không tách bước láng-giềng
5. Detector H24 độ-tươi **FIRE** trên `harness-11-engine.md` (positive-control sống) **trước** khi vá header → sau vá **hết fire**; chuỗi fire→gate-chứng-kiến→vá→hết-fire có ghi verdict
6. `dotnet test SolutionErp.slnx` → **≥509 PASS** sau W0 (chứng LF không cắn) + sau W3 (Sàn-4 = **3 verdict**, không phải gạch-đầu-dòng)

**CHƯA VERIFY:** (i) `.claude/commands/*.md` sửa giữa phiên có hot-reload không → memory `project_harness4_two_tier_model` nói **EDIT-command luôn cần restart** ⇒ **W5 phải THỰC-THI guard bằng tay**, không trông chờ file vừa sửa tự ăn; (ii) `docs/changelog/migration-todos.md` có anchor ngày hay không (grep trả `?`).