# sub-task-2 — H22 hook PS1 + FLIP policy workflow (D2+D6)

**Role:** implementer-backend (Write/Edit-capable) · **Run:** 2026-07-12-h22-wal-adopt · **Spec:** D2 (hook PS1) + D6 (FLIP RUN-TRACE policy)

## Scope (3 deliverables, isolation-clean)
1. CREATE `.claude/hooks/wal-flush.ps1` (folder mới)
2. EDIT `.claude/workflows/README.md` — FLIP 2-mode policy
3. EDIT `.claude/workflows/runs/README.md` — add `## taskList snapshot` section

Không đụng file nào khác. `git status` cho thấy nhiều file M/?? khác = việc của sibling-task trong cùng fan-out song-song (session-*.md, .gitattributes, pause.md, tiep.md, settings.json, CLAUDE.md, rules.md, harness-11-engine.md, broadcasts/*) — KHÔNG phải write của tôi.

---

## (1) `.claude/hooks/wal-flush.ps1` — port bash→PS 5.1, persist-only

Port nguyên semantics từ broadcast §3 (bash `wal-flush.sh`, dòng 82-97) sang PowerShell 5.1 dùng skeleton lead-verified. **Chỉnh cosmetic DUY-NHẤT: 2 em-dash `—` trong comment → hyphen `-`** để đạt pure-ASCII (lesson #30 — tránh mojibake .ps1). KHÔNG thêm logic ngoài skeleton.

Nội dung final (788 bytes, pure-ASCII, LF, no-BOM):
```
# wal-flush.ps1 - WAL persist-only Stop hook (H22 S111). KHONG sinh noi dung, KHONG push, KHONG block.
# Fail-open MOI nhanh: exit 0 ke ca loi (Stop-hook exit!=0 co hanh vi rieng - CAM).
# Per-path add + CAND commit (X3: add/commit da-pathspec = all-or-nothing khi 1 path vang).
try {
  $root = if ($env:CLAUDE_PROJECT_DIR) { $env:CLAUDE_PROJECT_DIR } else { "." }
  Set-Location $root
  $paths = @(".claude/WAL.md", ".claude/agent-memory/", ".claude/workflows/runs/")
  $cand = @()
  foreach ($p in $paths) {
    git add -- $p 2>$null
    git diff --cached --quiet -- $p 2>$null
    if ($LASTEXITCODE -ne 0) { $cand += $p }
  }
  if ($cand.Count -gt 0) {
    $ts = Get-Date -Format "yyyyMMddTHHmm"
    git commit -q -m "wal: flush $ts" -- $cand 2>$null | Out-Null
  }
} catch { }
exit 0
```

### Semantics-fidelity bash→PS (từng dòng khớp):
| bash §3 | PS 5.1 | Khớp |
|---|---|---|
| `cd "${CLAUDE_PROJECT_DIR:-.}" \|\| exit 0` | `$root = if($env:CLAUDE_PROJECT_DIR){...}else{"."}; Set-Location $root` (trong try/catch→exit 0) | ✅ fail-open |
| `P=".claude/WAL.md .claude/agent-memory/ .claude/workflows/runs/"` | `$paths = @(3 path y-hệt)` | ✅ 3 path |
| `git add -- "$p"` per-path | `git add -- $p 2>$null` per-path | ✅ per-path (X3 all-or-nothing tránh) |
| `git diff --cached --quiet \|\| CAND+=p` | `git diff --cached --quiet; if($LASTEXITCODE -ne 0){$cand+=$p}` | ✅ exit!=0 = có staged |
| `[ -z "$CAND" ] && exit 0` | `if($cand.Count -gt 0){commit}` | ✅ chỉ commit khi non-empty |
| `git commit -q -m "wal: flush $(date +%Y%m%dT%H%M)" -- $CAND` | `$ts=Get-Date -Format "yyyyMMddTHHmm"; git commit -q -m "wal: flush $ts" -- $cand` | ✅ (T literal trong .NET format, khớp `%Y%m%dT%H%M`) |
| `exit 0` cuối | `exit 0` cuối | ✅ fail-open mọi nhánh |

### Guard-rail đã verify (dead-ends né):
- **X2 (0 content-gen / 0 push / 0 block):** grep Set-Content/Out-File/Add-Content/echo/`>>` = NONE-OK · `git push` = NO-GIT-PUSH-OK (chỉ 'push' trong comment `KHONG push`) · `exit [1-9]` = NONE-OK (chỉ `exit 0`).
- **X8:** hook chỉ add/commit path cố-định (WAL + agent-memory + runs), message `wal: flush <ts>` KHÔNG Co-Authored-By, KHÔNG secret.
- **lesson #30:** byte-scan = 788 bytes, 0 non-ASCII; BOM = `23 20 77` (`# w`, no BOM); PSParser.Tokenize = PARSE-OK.

## (2) `.claude/workflows/README.md` — FLIP 2-mode policy

Vùng `## 2 MODE memory` (mode-table + câu chốt). 2 edit:
- **+1 row bảng** sau "Khi dùng": `| Bắt buộc? (sàn H22 S111) | mode-A hợp lệ cho fan-out NHỎ | **BẮT BUỘC khi run ≥3 task HOẶC ước chạy >5 phút** (args.run → per-agent-file sub-<role>-<i>.md) |` — 3-col khớp header, table structure intact.
- **Sửa "Khi dùng" DEFAULT cell:** "fan-out NHẸ (~2-3 phút…)" → "fan-out NHỎ (<3 task **VÀ** ước ≤5 phút; read/analyze — vd recon)".
- **Thay câu chốt** `> Mặc định DEFAULT…` → `> **Sàn H22 (S111) — FLIP policy:** mode-A return-delta-only VẪN hợp lệ cho fan-out NHỎ, NHƯNG run ≥3 task HOẶC ước chạy >5 phút = BẮT BUỘC RUN-TRACE mode… Lý-do: chết giữa run vẫn còn vật-liệu trên đĩa để /tiep nối — per-TURN ≠ per-TASK: returns mất khi chết giữa turn, file trên disk còn.`

GIỮ nguyên 3 row còn lại (Sub ghi file / Lead làm / Rủi ro) + heading. KHÔNG renumber (X10).

## (3) `.claude/workflows/runs/README.md` — add taskList snapshot section

Chèn section mới `## taskList snapshot (H22 — ghi TRƯỚC launch)` GIỮA C2 và C3 (anchor = C3 heading; KHÔNG renumber C1-C8, X10). Nội dung: template `## taskList snapshot` trong run.md (role · label · prompt-gist 1-dòng) ghi TRƯỚC launch + dòng WAL `wf:` 5-trường (`wf:<mục-đích> run= runId=<id|pending> args= done=<k>/<N> harvested=<yes|no|partial>`) ghi NGAY SAU launch + memory-pack KHÔNG snapshot (X6, re-derive tươi lúc /tiep).

---

## ACCEPTANCE (row #6+#7) — evidence
| Tiêu-chí | Verdict | Evidence |
|---|---|---|
| ps1 `exit 0` ≥1 dòng cuối | PASS | grep -c 'exit 0'=2 (comment+final); `tail -1`=`exit 0` |
| ps1 try/catch present | PASS | L4 `try {` · L18 `} catch { }` |
| ps1 per-path foreach | PASS | L9 `foreach ($p in $paths)` |
| ps1 $cand present | PASS | 4 occurrences |
| ps1 0 push / 0 content-gen / 0 block | PASS | no `git push`; no Set-Content/Out-File/echo/`>>`; no `exit [1-9]` |
| workflows/README có 'BẮT BUỘC'+'≥3'+'5 phút', mode-A-nhỏ hợp lệ | PASS | L27+L32 'BẮT BUỘC'; 'run ≥3 task'; '>5 phút'/'≤5 phút'; L32 'VẪN hợp lệ cho fan-out NHỎ' |
| runs/README 'taskList snapshot'≥1 + 'wf:'≥1 | PASS | grep -c 'taskList snapshot'=3; grep -c 'wf:'=2 |

## Verify commands run
- `PSParser.Tokenize` → PARSE-OK
- byte-scan → 788 bytes / 0 non-ASCII / no-BOM / no-CRLF
- grep sweep 3 file → tất cả tiêu-chí PASS
- `git status --porcelain` → 3 file của tôi (+ sibling-task files KHÔNG phải của tôi)
