From 24483935cb0417e7e84cbe7f9887557fbc19995d Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Fri, 24 Jul 2026 18:03:16 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Docs:=20S148=20=E2=80=94=20session-m?= =?UTF-8?q?odel=20port=20form=20hub=20+=20/check-email=204=20c=E1=BB=ADa?= =?UTF-8?q?=20+=20S=C3=A0n-3=20dual-accept?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session-model (owner chốt "đối ứng đúng chính xác như hub"): - Form hub: _context-s-.md (STOCK-map + FLOW append-only + STOCK-touched) + _pause-/_tiep- marker 5-trường + _snapshot- + _end (thay closed.md) - Port /snapshot + scripts/session_scaffold.py (near-verbatim) + scripts/session_ctx.py TRIMMED CÓ KHAI (chỉ machine-block + secrets-sweep; KHÔNG port jsonl/overhead/cap-getter vì chưa có caller = ghost-wire) - session-2 migrate sang form hub; session-1 giữ legacy (FROZEN) Sàn-3 ORPHAN-L: - DUAL-ACCEPT hub + legacy; glob pause-* KHÔNG khớp _pause-1.md nên không đếm đôi - VÁ bug có sẵn từ S146: chốt-kết ĐÓNG TRỌN thư-mục (bản cũ c=1 chỉ tha 1 pause, lệch chính câu session-end §6.3-bis vẫn nói "mọi pause") - Fault-inject 10/10 hai chiều + anti-Goodhart /check-email: - Wire 4 CỬA phiên (session-start/tiep/session-end/pause), 2 CHẾ-ĐỘ: DÒ ~5ms ở cửa dừng-nối (ràng buộc BINDING hub goi-chot §3) ⟂ KÉO ở bookend - DÒ quét 2 kênh + định tuyến: outbox/se -> /check-email · outbox/all -> /adap-apply - STAGE-2: 10 thư fan-out verify 2 tuyến 10/10 -> inbox/ai_infra/; backlog root = 0 HANDOFF re-stamp: #1 ĐÓNG (trio đã chạy S144) · #2 đổi trục · #3 anh chốt (a) + 4 mục mới (13)-(16); carry #15/#17 đóng, #16 đóng nửa (khai rõ vế còn hở) H24 tick S147->S148: counter 21->22 CLEAN Co-Authored-By: Claude Opus 4.8 --- .../feedback_h18_ghostwire_stamp_integrity.md | 3 + ...back_monitor_residual_write_containment.md | 6 +- .claude/commands/check-email.md | 50 +++- .claude/commands/pause.md | 36 ++- .claude/commands/session-end.md | 23 +- .claude/commands/session-start.md | 21 ++ .claude/commands/snapshot.md | 70 ++++++ .claude/commands/tiep.md | 43 +++- .claude/governance/.session-counter.json | 13 +- .claude/templates/session-context-template.md | 57 +++++ ...overnance-spawn-label-panel-audit-khung.md | 0 ...ensemble-cost-lens-khan-hiem-va-routing.md | 0 ...ensemble-depth-axis-deepen-on-contested.md | 0 ...t-ensemble-depth-cost-update-so-do-thuc.md | 0 ...-19-Governance-goi-chot-owner-nam-khoan.md | 0 ...ess-dong-du-vong-audit-va-eval-use-side.md | 0 ...nce-hub-tu-danh-gia-3-goc-va-chuyen-pha.md | 0 ...-title-tier-posture-home-distill-params.md | 0 ...overnance-auto-toan-vong-distill-2-tang.md | 0 ...al-quality-audit-ra-thuoc-eval-cua-eval.md | 0 docs/HANDOFF.md | 8 +- scripts/session_ctx.py | 220 ++++++++++++++++++ scripts/session_scaffold.py | 154 ++++++++++++ 23 files changed, 676 insertions(+), 28 deletions(-) create mode 100644 .claude/commands/snapshot.md create mode 100644 .claude/templates/session-context-template.md rename broadcasts/inbox/{ => ai_infra}/2026-07-18-Governance-spawn-label-panel-audit-khung.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-19-Agent-ensemble-cost-lens-khan-hiem-va-routing.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-19-Agent-ensemble-depth-axis-deepen-on-contested.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-19-Agent-ensemble-depth-cost-update-so-do-thuc.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-19-Governance-goi-chot-owner-nam-khoan.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-19-Governance-harness-dong-du-vong-audit-va-eval-use-side.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-19-Governance-hub-tu-danh-gia-3-goc-va-chuyen-pha.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-20-Governance-3-khoan-title-tier-posture-home-distill-params.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-20-Governance-auto-toan-vong-distill-2-tang.md (100%) rename broadcasts/inbox/{ => ai_infra}/2026-07-21-Governance-eval-quality-audit-ra-thuoc-eval-cua-eval.md (100%) create mode 100644 scripts/session_ctx.py create mode 100644 scripts/session_scaffold.py diff --git a/.claude/auto-memory/feedback_h18_ghostwire_stamp_integrity.md b/.claude/auto-memory/feedback_h18_ghostwire_stamp_integrity.md index 3d356a4..59d61a2 100644 --- a/.claude/auto-memory/feedback_h18_ghostwire_stamp_integrity.md +++ b/.claude/auto-memory/feedback_h18_ghostwire_stamp_integrity.md @@ -5,6 +5,7 @@ metadata: node_type: memory type: feedback originSessionId: c0d6d8d1-8e0e-46bf-ac36-ab0f45d2629f + modified: 2026-07-24T10:21:02.939Z --- 3 bài học integrity từ S100 H18-adopt (2026-07-02): @@ -15,6 +16,8 @@ metadata: 4. **Sibling-test 2 CHIỀU (S125, 2026-07-16):** mismatch hash có nhánh thứ 3 ngoài tamper/EOL — **VERIFIER của MÌNH hỏng**. Discriminator: chạy verifier trên sibling known-good. Sibling MATCH ⇒ phép đúng, file bệnh (ca S100 frontier-assessment) · sibling CŨNG FAIL ⇒ **verifier bệnh** (ca S125: PS 5.1 `Get-Content -Raw` decode UTF-8-no-BOM bằng ANSI → mojibake → false-TAMPER trên broadcast hub SẠCH; đối chứng `stamp_verify.py` hub → exit-0). Fix gốc: hash CHỈ từ bytes (`ReadAllBytes` + UTF8 decode PIN) — CẤM `Get-Content -Raw` cho hash; đã vá `check-email.md`/`send-email.md` S125. +5. **🔴 Ghost-wire class (c) — CLAIM-OF-FIX: doc khai "đã vá" một kẽ CHƯA vá, và kẽ đó KHÔNG TỒN TẠI ở chỗ nó chỉ (S148, 2026-07-24).** `C14-disposition-per-khoan.md:21` (@S144) khai *"Kẽ đã bịt: `/check-email` bước 2 chỉ đọc `outbox/se`, không nhắc `outbox/all`"*. Lead **tin bản tóm-tắt** → sửa `check-email.md` STAGE 1 đọc thêm `outbox/all`. **Cả hai vế đều sai:** (i) grep `outbox/all check-email.md` = **0 hit** ⇒ chưa hề bịt, chỉ được TUYÊN BỐ; (ii) `outbox/all` **chưa bao giờ** là việc của `/check-email` — 2 kênh 2 tool 2 sổ (`outbox/se`→`/check-email`→`_index` ⟂ `outbox/all`→`/adap-apply`→ **KHÔNG** vào `_index`, khai ở `_index.md` header dòng 7). Kiểm ngược bằng máy: **22/22** thư `outbox/se` đều có dòng index ⇒ **tool vốn không thủng**. ⇒ lead **vá một tool không hỏng, theo một lời khai không đo**. 🔴 **Kẽ THẬT nằm ở NHỊP, không ở TOOL:** không cửa phiên nào *dò* `outbox/all` ⇒ 10 broadcast fan-out nằm im **5 ngày**. Cùng lượt còn 1 báo-cáo sai cùng gốc: lead khai *"`_index` thiếu 17 dòng"* trong khi cả 17 là fan-out ⇒ **sổ đang đúng**, chỉ là lead áp nhầm luật cho kênh. + **Why:** stamp-then-edit + fabricated-citation là lỗi im lặng — chỉ lộ khi RE-COMPUTE đối chứng nguồn gốc; một làn chỉ kiểm "tóm-tắt trung-thành bản-thô" sẽ mù khi bản-thô bịa từ đầu. **How to apply:** mọi broadcast/email cross-project → re-compute `SHA256(body)` canonical trước khi áp; mismatch → **sibling-test 2-chiều TRƯỚC**: sibling-fail ⇒ sửa verifier của MÌNH, sibling-match ⇒ HELD + báo nguồn (không suy diễn "chắc CRLF", cũng không hô tamper khi chưa loại trừ tool). Mọi report → chỉ cite run-id/số ĐÃ tồn tại; chưa chạy = placeholder tường minh. Đổi config-consumer → grep key-name toàn scripts/ xem có ai đọc thật không. Liên quan: [[faultinjection-proves-teeth]], [[canonical-spec-over-broadcast]], [[agent-return-garble-recover]]. diff --git a/.claude/auto-memory/feedback_monitor_residual_write_containment.md b/.claude/auto-memory/feedback_monitor_residual_write_containment.md index 3fc08fd..584d683 100644 --- a/.claude/auto-memory/feedback_monitor_residual_write_containment.md +++ b/.claude/auto-memory/feedback_monitor_residual_write_containment.md @@ -5,7 +5,7 @@ metadata: node_type: memory type: feedback originSessionId: cc5c42e6-f9aa-404c-adf6-26079c40d118 - modified: 2026-07-22T05:34:19.739Z + modified: 2026-07-24T09:10:28.062Z --- At S50 (2026-06-07 session-end), the 2 INFORM-only monitor subs (tooling-auditor H1 + harvest-curator H2) — briefed **propose-only**, and which **reported "wrote nothing"** — were nonetheless the only plausible authors of ~7 canonical/agent-memory file writes done outside em-main: `error-ledger.md` (2 guard promotions + #57 coords), 3 `adap-reports` (nac→verified-runtime), 4 `agent-memory/*` Recent-activity, and parts of `STATUS.md` (Recently-Done block + In-Progress flip + RAG-line reconcile). mtimes clustered in the 00:00–00:05 monitor window. em-main `git diff` at the commit-gate caught every line → all accurate / benign / 0-mojibake / chunk 2415 (no RAG corruption) → adopted per AS-10 keep-if-correct. Logged blameless as **E-006** in the error-ledger. @@ -14,4 +14,6 @@ At S50 (2026-06-07 session-end), the 2 INFORM-only monitor subs (tooling-auditor **⚠️ S143 (2026-07-22) — premise correction, measured:** the sentence below assumed `Write` had been *removed* from monitor tool-grants, leaving only a Bash residual. **That premise is false at runtime.** Measured on 6 read-only roles (3× `harness-*` trio, 2× H24 auditors, `reviewer`; `tooling-auditor`/`harvest-curator`/`investigator-codebase` same shape): none declare `Write`/`Edit` in their frontmatter `tools:` — yet the **runtime roster still grants `Write, Edit`**, appended at the **end** of the tool list. The tell is positional: `implementer-backend` declares `Edit, Write` at slots 2-3 and its runtime list preserves that file order, so the other six are being **appended at spawn time**, not read from file. ⇒ a frontmatter whitelist is a **statement of intent, not an enforcement mechanism**; "chặn-bằng-thiếu-tool" in any spec is an overclaim (G-015). The residual channel is therefore wider than Bash. Mechanism unknown (hypothesis: `memory: project` needs diary writes — **unverified**, do not assert). Same two-layer shape as [[feedback_permission_grant_two_layers]]: declared-layer ⟂ granted-layer are independent, and verifying one proves nothing about the other. Reported upward in `broadcasts/outbox/ai_infra/2026-07-22-se-to-ai_infra-bao-nac-wave-dot-9-10.md` §3(b). -**How to apply:** Before ANY `/session-end` commit where monitor/sub agents ran, ALWAYS `git status` + `git diff` + chunk-count and **review every non-em-main line** — adopt-if-correct (AS-10) or revert; never blind-commit. If a sub's self-report ("wrote nothing") contradicts git-diff → **Fidelity flag** (escalate reviewer). Recommend to anh/AI_INFRA: harden monitor tool-grant (Write removal leaves Bash residual → consider a hook blocking sub-Bash-write to tracked paths) — but that is a charter-v2 infra decision. Links: [[feedback_store_memory_rebootstrap_protection]] · [[feedback_harness_123_adoption]] · [[feedback_session_end_memory_write_verify]]. +**🔴 S148 (2026-07-24) — RE-MEASURED live, count is 15 not 6:** the "6 roles" above was an S143-era undercount (S143 itself already revised it to 9 mid-entry). Fresh count from disk + this session's runtime registry: **20 agent files − 5 legitimate write-specialists (`implementer-backend`, `implementer-frontend`, `frontend-designer`, `office-document`, `test-specialist`) = 15 read-only roles that declare NO `Write`/`Edit` in frontmatter `tools:` — and the runtime grants `Write, Edit` to all 15.** Two-sided proof: `.claude/agents/reviewer.md:7` declares `[Read, Grep, Glob, Bash, mcp__rag-unified__*]` while the session's agent listing shows `…, Write, Edit`; `harness-eval`'s own description string says *"KHÔNG store_memory, KHÔNG Write/Edit"* and it is granted both. 🔸 **The number grows with the roster, it is not a worsening condition** — don't read a rising count as a regression. 🔴 **Quote a count here only if you just measured it** — this line has now been wrong twice (6 → 9 → 15) precisely because it was re-cited from memory instead of re-counted. + +**How to apply:** Before ANY `/session-end` commit where monitor/sub agents ran, ALWAYS `git status` + `git diff` + chunk-count and **review every non-em-main line** — adopt-if-correct (AS-10) or revert; never blind-commit. If a sub's self-report ("wrote nothing") contradicts git-diff → **Fidelity flag** (escalate reviewer). 🔒 **DO NOT re-propose hardening — anh decided @S148 (2026-07-24): option (a), keep as-is, NO `PreToolUse` hook blocking sub-writes to tracked paths.** So the frontmatter whitelist stays **documentation of intent**, and `git diff` at the commit-gate is the *only* real containment — it catches **after** the write, never **before**. Re-raise only if anh reopens it or the hub returns a mechanism answer (`[carry:tools-whitelist-no-teeth]` is now narrowed to the hub-confirmation leg alone). Anh's framing was *"có j audit sau"* — accept the residual risk now, catch it in audit later; that is a deliberate trade, not an oversight to fix behind his back. Links: [[feedback_store_memory_rebootstrap_protection]] · [[feedback_harness_123_adoption]] · [[feedback_session_end_memory_write_verify]]. diff --git a/.claude/commands/check-email.md b/.claude/commands/check-email.md index f5733f3..2686b58 100644 --- a/.claude/commands/check-email.md +++ b/.claude/commands/check-email.md @@ -9,10 +9,58 @@ argument-hint: ## Tham số - `$1` = from_project (BẮT BUỘC) ∈ 6 others, hoặc `all` = quét cả 6. +## Nhịp chạy — 🔴 4 CỬA PHIÊN (owner chốt S148, 2026-07-24) + +> 🔴 **Đây là NHÀ CANONICAL của "khi nào chạy `/check-email`".** 4 file lệnh cửa **chỉ TRỎ vào đây** rồi gọi lệnh; **CẤM chép luật sang** (B1 — chép 4 nơi = 4 nguồn sự-thật). + +**Anh chốt @S148:** *"mỗi session end/start luôn — vì 1 session giờ có thể kéo dài 2-3 phiên qua 2-3 ngày."* +⇒ nhịp neo vào **SỰ-KIỆN PHIÊN**, **KHÔNG** vào đồng-hồ ngày. Lý-do anh nêu là lý-do kỹ-thuật đúng: một phiên-LOGIC trải nhiều cửa-sổ nhiều ngày ⇒ ngưỡng-theo-ngày **không map** được vào nhịp làm việc thật. + +🔴 **Vì sao 4 cửa chứ không 2 — lead MỞ RỘNG từ câu chữ, khai thẳng để anh bác được:** cửa VÀO có 2 và cửa RA có 2. + +| | cửa-1 | cửa-2 | +|---|---|---| +| **VÀO** | `/session-start` (phiên mới) | `/tiep` (nối phiên-logic) | +| **RA** | `/session-end` (đóng sổ) | `/pause` (dừng chủ-động) | + +Chỉ cắm 2 cửa `session-start`/`session-end` thì **đúng ca anh đang lo VẪN LỌT**: phiên S148 vào bằng `/tiep`, phiên trước ra bằng `/pause` ⇒ **cả hai lần đều không check**. Mà chính câu *"1 session kéo 2-3 phiên"* hàm ý các phiên GIỮA vào bằng `/tiep` ⇒ **phần lớn cửa nằm ở nhánh `/tiep`**, không ở `/session-start`. Phủ 4 cửa mới đạt được điều anh nói. + +### 🔴 HAI CHẾ-ĐỘ — sửa @S148 sau khi đọc thư `goi-chot-owner-nam-khoan` khoản 3 (BINDING) + +> **Lead tự bắt vi-phạm của chính mình:** bản đầu @S148 cắm **PULL đầy-đủ** vào cả 4 cửa. Thư hub `2026-07-19-Governance-goi-chot-owner-nam-khoan` §3 là **ràng-buộc thiết-kế CỨNG**, verbatim: *"bất kỳ van/gate/phép-đo nào về sau cũng **KHÔNG ĐƯỢC** chặn hoặc làm chậm các cửa **dừng–nối–checkpoint**"*, floor: *"bước nào nặng (spawn agent, đo đạc lớn, **quét rộng**) đang nằm trong đường pause-tương-đương là **vi-phạm**: dời nó về **closeout/bookend**"*. ⇒ `/pause` + `/tiep` **là đúng 2 cửa đó**. +> 🔴 **Hub bắt ĐO chứ không đoán** (SELF-CHECK: *"Đo wall-clock một lần — điểm dừng phải rẻ"*). **Đo @S148, 3 lượt:** **dò** (list 6 repo + so id) = **66ms lạnh / 4–5ms nóng**; **kéo** (copy + hash + `python stamp_verify.py`) = python-startup **~200–500ms × số thư**. ⇒ **dò KHÔNG nặng, kéo MỚI nặng.** Nên vá đúng là **tách 2 chế-độ**, KHÔNG phải bỏ cửa — bỏ cửa sẽ mất đúng thứ anh cần (phiên giữa vào bằng `/tiep`). + +| chế-độ | cửa | làm gì | chi-phí | +|---|---|---|---| +| **DÒ** (detect-only) | `/tiep` · `/pause` | list + so id → in `thu-moi: se=X all=Y`. 🔴 **KHÔNG copy · KHÔNG hash · KHÔNG python.** X hoặc Y > 0 ⇒ nêu **1 dòng** rồi đi tiếp, để bookend kéo | ~5ms | +| **KÉO** (đầy-đủ) | `/session-start` · `/session-end` | `se>0` → **`/check-email`** STAGE 1 (copy + verify 2 tuyến + log `_index`) · `all>0` → **`/adap-apply`** | theo số thư | + +🔴 **DÒ PHẢI QUÉT CẢ HAI KÊNH — đây là kẽ thật, và nó nằm ở NHỊP chứ không ở tool:** `outbox/se` (directed, → `/check-email`) **VÀ** `outbox/all` (fan-out, → `/adap-apply`). Không có nhánh `all` thì **10 broadcast đợt adap-11 nằm im 5 ngày** (07-18→07-23) — ca đã xảy ra thật, không phải giả-định. **DÒ chỉ ĐẾM và ĐỊNH TUYẾN, không kéo** ⇒ vẫn rẻ. +🔸 **Watermark cho `outbox/all`:** bỏ qua id ≤ **`2026-07-15`** — 51 file mốc 06-02→07-15 là lớp *đã-adopt-đọc-tại-chỗ* (`/adap-apply` đọc thẳng bên AI_INFRA, **KHÔNG đòi copy** ⇒ vắng mặt trong inbox là **BÌNH THƯỜNG**, không phải nợ). Không có watermark thì mỗi lượt DÒ kêu 51 cái rồi tự bị bỏ qua. + +🔒 **Bất-biến:** việc NẶNG chỉ sống ở **bookend**. Ai thêm bước vào đường `/pause`·`/tiep` sau này **phải đo wall-clock trước**, đúng câu hỏi hub đặt: *"có làm điểm dừng đắt lên không"* — **TRƯỚC** khi bàn giá-trị của bước đó. + +**Cách chạy ở mỗi cửa — `/check-email all`:** +- 🟢 **Không có thư mới ⇒ 1 dòng, im lặng đi tiếp.** KHÔNG báo-cáo dài, KHÔNG chờ anh. +- 🔴 **FAIL-SOFT, CẤM chặn nghi-thức:** lỗi bất-kỳ (repo bên kia không có trên máy · path đổi · thiếu python) ⇒ in `check-email loi (khong chan)` rồi **ĐI TIẾP**. Cùng khuôn `nhip-no-probe.ps1`: một bước MỚI **không bao giờ được phép** làm hỏng nghi-thức đã chạy tốt. +- Có thư ⇒ chạy trọn **STAGE 1** (copy + verify hash). **STAGE 2** (move → `processed`) đi theo việc xử-lý, **KHÔNG ép** trong cùng lượt. + +**Quan-hệ với vế-4 `pull-cach` (`nhip-no-probe.ps1`) — ĐỔI VAI:** trước định làm **chuông báo** (cần ngưỡng `pull_warn_days`). Nay 4 cửa là cơ-chế CHÍNH ⇒ `pull-cach` thành **chỉ-báo sức-khoẻ của chính nghi-thức**: số ngày cứ leo trong khi phiên vẫn mở/đóng đều đặn ⇒ **nghi-thức đang bị chạy tắt**, chứ không phải hub im. 🔒 **`pull_warn_days` CỐ Ý để TRỐNG** — anh không đặt số @S148, và script cấm tự chế mặc-định. +🔸 **Giới-hạn giữ khai (đo @S148):** vế-4 mạnh với **quãng im DÀI**, yếu với **trễ-kéo NGẮN** — ca hub-gửi-giục 07-18 chỉ `N=2` mới bắt, mà `N=2` nằm ngay trên trung-vị ⇒ kêu gần như liên-tục. Đừng kỳ vọng `pull-cach` chặn tái-diễn ca đó; **4 cửa mới là thứ chặn nó.** + ## Quy trình 2-STAGE (audit qua folder) **STAGE 1 — Nhận (đọc → inbox root, PENDING):** 1. Validate `$1`. -2. READ `/broadcasts/outbox/se/*.md` (message gửi cho se). +2. READ `/broadcasts/outbox/se/*.md` (message gửi **đích danh** se). + 🔴 **CHỈ `outbox/se` — ĐÚNG, đừng "sửa" thành đọc cả `outbox/all`.** Hai kênh, hai tool, hai sổ: + + | kênh | nội dung | tool kéo | sổ theo dõi | + |---|---|---|---| + | `outbox/se/` | thư **directed** (`to: se`) | **`/check-email`** (file này) | `broadcasts/_index.md` §INBOUND | + | `outbox/all/` | **fan-out** (`to: all-fit`) | **`/adap-apply`** ([`adap-apply.md:14`](adap-apply.md) đọc **thẳng tại chỗ**) | **KHÔNG** vào `_index` — xem header `_index.md` dòng 7 | + + 🧊 **Vết sai @S148 — giữ làm bài học:** lead đọc `C14-disposition-per-khoan.md:21` (@S144) khai *"kẽ đã bịt: `/check-email` bước 2 chỉ đọc `outbox/se`, không nhắc `outbox/all`"* → **tin bản tóm-tắt** → sửa bước này thành đọc cả 2 kênh. **Sai:** `outbox/all` chưa bao giờ là việc của `/check-email`. Kiểm ngược bằng máy: **22/22** thư `outbox/se` đều có dòng `_index`, **thiếu 0** ⇒ tool này vốn **không hề thủng**. 🔴 **Hai lỗi chồng nhau, cả hai đều là *tin chữ thay vì đo*:** C14 tuyên "đã bịt" một kẽ **không tồn tại**, rồi lead vá một tool **không hỏng**. + 🔴 **Kẽ THẬT nằm ở NHỊP, không ở tool:** không cửa phiên nào **dò** `outbox/all` ⇒ 10 broadcast fan-out nằm im **5 ngày**. Vá đúng = **DÒ 2 kênh ở cửa phiên rồi ĐỊNH TUYẾN** (§"Nhịp chạy" trên), KHÔNG phải nhét kênh này vào STAGE 1 của tool kia. 3. Mỗi file CHƯA có trong inbox (so id): **COPY VERBATIM** → `broadcasts/inbox/.md` (**root = pending**). [repo MÌNH §J2] 4. **Verify đối chứng:** (a) **whole-file** `Get-FileHash` copy == nguồn (byte-identical = tuyến CHÍNH); (b) **body** recompute `SHA256(body)` == `content_sha256` khai ở frontmatter. diff --git a/.claude/commands/pause.md b/.claude/commands/pause.md index 4c7f697..f161063 100644 --- a/.claude/commands/pause.md +++ b/.claude/commands/pause.md @@ -22,7 +22,33 @@ Thực-hiện **tuần-tự 5 bước** sau; trong lúc chạy **KHÔNG nhận v ## 2.6 Ghi context-block vào thư-mục phiên-LOGIC (S146 — lane CỘNG-DỒN, cặp với `/tiep §2.6`) -- **Append MỘT khối mới** vào `.claude/sessions/session-/pause-S-.md` (`` = phiên-LOGIC **đang mở** — xem [`session-start.md`](session-start.md) BƯỚC 0.8). +🔴 **FORM HUB (owner chốt @S148 "đối ứng đúng chính xác như hub") — TÁCH marker ⟂ narrative, KHÔNG gộp 1 file như form SE cũ:** + +**(A) Marker `_pause-.md`** — `` = số TUẦN TỰ trong thư-mục (`max(_pause-*)+1`), **KHÔNG** nhãn `S`, **KHÔNG** timestamp trong tên. Đúng **5 trường, ~200B**, không thêm bớt: +``` +ts: +head-sha: +window-ordinal: +jsonl-hint: / +account-label: none +``` + +**(B) Narrative → APPEND vào `_context-s-.md`** (một file DUY NHẤT/phiên-logic, cộng dồn) — thêm đúng 1 entry `### PAUSE- ` dưới `FLOW-START`: +- 🔴 **Dòng đầu entry = `> anh: `** — chép **nguyên văn**, CẤM paraphrase. Không có tin-nhắn ⇒ ghi `(THIẾU — lý do)`, **CẤM dựng lại từ trí nhớ**. +- Rồi 3 mục cũ: **(1) quyết-định đã CHỐT** · **(2) delta còn SỐNG** · **(3) con-trỏ**. +- 🔴 **FLOW append-only, entry cũ IMMUTABLE** — CẤM rewrite, CẤM tự-tóm (chống self-summary lossy). Vượt `session_ctx_kb` ⇒ distill **SECTION CŨ** thành pointer, KHÔNG sửa entry verbatim. + +**(C) Máy-derive `§(c) STOCK-touched`** — `python scripts/session_ctx.py machine-block --session --json` → điền bảng §(c) của `_context-s-.md`. **KHÔNG điền tay** (template ràng "máy-derive @pause/snapshot"). +🔸 **Khai giới-hạn:** `changed_files` = `git diff anchor..HEAD` ⇒ **chỉ phần ĐÃ COMMIT**; việc còn dirty không hiện. Đừng đọc thành "toàn bộ việc đã làm". + +**(D) AUTO-SNAPSHOT — chạy `/snapshot` §2→§4 ngay trước bước 3** *(đóng `[carry:pause-autosnap]` #17, anh chốt @S148)*: +- ✅ **Đo trước khi wire, đúng câu hub bắt hỏi** (`goi-chot` §3 — *"có làm điểm dừng đắt lên không"* phải hỏi TRƯỚC khi bàn giá-trị): `machine-block` **~140ms** · `secrets-sweep` **~64ms** (3 lượt, tb). +- 🔴 **Chi-phí RÒNG của auto-snap chỉ là ~64ms + 1 write**, KHÔNG phải 204ms: `machine-block` **vốn đã phải chạy** ở (C) cho §(c) STOCK-touched. `/pause` cũng vốn đã commit sẵn ở bước 4. +- 🔸 **Ranh giữ khai:** hub **KHÔNG** ràng 1 pause = 1 snapshot (đo đĩa hub: `session-102` pause=3/snap=5 · `session-107` pause=2/snap=1). SE chọn auto-snap-tại-pause là **quyết-định SE**, không phải floor hub — ai thấy đắt thì gỡ, đã có số để cãi. + +🧊 **Form SE cũ `pause-S-.md` (gộp marker+narrative) = LEGACY, CẤM viết mới.** Sàn-3 ⑤ vẫn **nhận** nó (dual-accept, `tiep.md §0`) vì `session-1` còn dùng — đúng luật retire: *gỡ nhánh legacy khi tập di-sản RỖNG, giữ khi CÒN người thụ-hưởng*. + +- `` = phiên-LOGIC **đang mở** — xem [`session-start.md`](session-start.md) BƯỚC 0.8. - 🔴 **Scaffold idempotent (owner chốt @S146 "giống hub"):** thư-mục chưa có ⇒ **TẠO**; đã có ⇒ **DÙNG LẠI, CẤM đè**. Chạy lại nhiều lần vô hại. - 🔴 **NHƯNG `/pause` KHÔNG BAO GIỜ tự tăng ``** — cấp-số là **độc-quyền `/session-start`** (BƯỚC 0.8, canonical; B1 — mục này chỉ TRỎ). `/pause` chỉ tạo thư-mục cho số **đang mở**. Không có số nào đang mở (vào thẳng bằng `/tiep` rồi `/pause`, chưa từng `/session-start`) ⇒ dùng `max+1` **và ghi 1 dòng khai** trong khối: `N-cap-ngoai-session-start: (ly-do: <...>)` — để `` không bao giờ xuất-hiện mà không ai biết nó ở đâu ra. - 🔴 **ACCUMULATE — CẤM ghi đè, CẤM sửa khối cũ.** Mỗi `/pause` = **một file mới**. (Ghi đè = mất chính thứ mà lane này sinh ra để giữ.) @@ -31,6 +57,14 @@ Thực-hiện **tuần-tự 5 bước** sau; trong lúc chạy **KHÔNG nhận v - **CẤM secret** (thư-mục này sẽ được commit — cùng kỷ-luật WAL bước 3). - 🔸 Hook `wal-flush.ps1` đã phủ `.claude/sessions/` (path thứ-4, S146) ⇒ khối này **tự vào `wal:` commit** ở turn-boundary. Vẫn **PHẢI add đích-danh** ở bước 4 (đừng dựa vào hook để chốt điểm dừng — hook là lưới, không phải nghi-thức). +## 2.7 Kéo thư cross-project (cửa RA 2/2 — owner chốt S148) + +**DÒ 2 kênh** (`outbox/se` + `outbox/all` × 6 repo) — **TRƯỚC** khi ghi WAL (bước 3), để `thu-moi: se=X all=Y` kịp vào `carry:`/context-map nếu có. +🔴 **CHỈ list + so id. KHÔNG copy · KHÔNG hash · KHÔNG python** — `/pause` là **cửa dừng**, ràng-buộc BINDING của hub (`goi-chot` §3) cấm làm nó đắt lên. Kéo thật = việc của bookend. + +> 🔗 **Luật nhịp đầy-đủ = canonical [`check-email.md`](check-email.md) §"Nhịp chạy — 4 CỬA PHIÊN".** 🔴 **B1 — CHỈ TRỎ + GỌI, CẤM chép luật.** +> 🔴 **KHÔNG chặn điểm dừng:** lỗi ⇒ `check-email loi (khong chan)` → đi tiếp bước 3. `/pause` tồn-tại để **dừng an-toàn**; một bước kéo thư hỏng **không được phép** giữ anh lại. + ## 3. Cập-nhật `.claude/WAL.md` đúng schema - **`chain:`** — đầy-đủ mọi mục trạng-thái (`[x]` đã-xong · `[!]` đang-dở · `[ ]` chưa-làm), **KÈM BẰNG-CHỨNG** là một lệnh / đường-dẫn / hash chứng-minh trạng-thái đó (KHÔNG mô-tả suông). - **`next:`** — đúng **một mệnh-lệnh cụ-thể**: làm gì tiếp theo. diff --git a/.claude/commands/session-end.md b/.claude/commands/session-end.md index c67714f..f952179 100644 --- a/.claude/commands/session-end.md +++ b/.claude/commands/session-end.md @@ -21,6 +21,14 @@ Em main PHẢI echo **TOÀN BỘ nội dung command body này** (đầy đủ Ph > **Vì sao ở BƯỚC 0, không muộn hơn:** phiên bị cắt **giữa lúc đóng** là ca nguy-hiểm nhất — WAL đã flush một phần, chưa push, trông như sạch. Đặt dấu TRƯỚC mọi flush ⇒ phiên kế **thấy `closing:` treo** và biết là bị cắt (Sàn-3 bậc-MẠNH ③, `tiep.md` §0.b). > **Net-zero:** §6.4 vốn **LUÔN chạy** ⇒ thêm 1 dòng xoá = 0 chi-phí. Acceptance: chạy no-op trọn-vẹn ⇒ `grep -c '^closing:' .claude/WAL.md` = **0**; cắt giữa chừng ⇒ phiên kế **thấy** dấu. +## 📋 BƯỚC 0.5 — Kéo thư cross-project (cửa RA 1/2 — owner chốt S148) + +**DÒ 2 kênh** rồi **KÉO đầy-đủ** theo định-tuyến (`se>0` → `/check-email all` · `all>0` → `/adap-apply`) — **TRƯỚC Phase 1 FLUSH**. + +> 🔗 **Luật nhịp đầy-đủ = canonical [`check-email.md`](check-email.md) §"Nhịp chạy — 4 CỬA PHIÊN".** 🔴 **B1 — CHỈ TRỎ + GỌI, CẤM chép luật.** +> 🔴 **Vì sao TRƯỚC flush, không sau:** closeout **ghi sổ** (STATUS · HANDOFF · session-log · carry). Thư về sau khi sổ đã ghi thì **lỡ mất kỳ này** và nằm chờ tới cửa vào kế. Kéo trước ⇒ thư mới kịp vào đúng bản ghi của phiên. +> 🔴 **KHÔNG chặn closeout:** lỗi ⇒ `check-email loi (khong chan)` → đi tiếp Phase 1. Sàn-2 sentinel ở BƯỚC 0 đã đặt TRƯỚC bước này ⇒ cắt giữa chừng vẫn phát-hiện được. + ## Phase 1 — FLUSH (sub-agent memory) **Điều kiện:** Chỉ xử lý con đã spawn trong session. KHÔNG spawn mới chỉ để flush (agent đã update MEMORY khi return). @@ -360,13 +368,16 @@ git push origin main # → git.baocaogiaoduc.vn/vietrep ### 6.3-bis 🔒 Chốt-kết `_end` — đóng băng phiên-LOGIC (owner chốt @S146 "giống hub") -> Floor manifest `101e69d6` §1: dấu-mốc thứ-4 = **"Chốt-kết (`_end`) — dấu đóng băng khi phiên-LOGIC thực sự khép lại"**. SE form = `closed.md`. Chân này **trước S146 CHƯA wire** ⇒ cân-sổ ORPHAN-L chỉ có nửa vào, không có nửa khoá (kêu oan ca *"pause rồi đóng thẳng không qua `/tiep`"*). Nay đóng. +> Floor manifest `101e69d6` §1: dấu-mốc thứ-4 = **"Chốt-kết (`_end`) — dấu đóng băng khi phiên-LOGIC thực sự khép lại"**. 🔴 **SE form = `_end` kể từ S148** (owner chốt *"đối ứng đúng chính xác như hub"*); form cũ `closed.md` = **LEGACY, CẤM viết mới** — `session-1` còn dùng nên Sàn-3 vẫn nhận. Chân này **trước S146 CHƯA wire** ⇒ cân-sổ ORPHAN-L chỉ có nửa vào, không có nửa khoá (kêu oan ca *"pause rồi đóng thẳng không qua `/tiep`"*). Nay đóng. -- **Ghi `.claude/sessions/session-/closed.md`** cho `` đang mở — **FROZEN, ghi MỘT lần, CẤM sửa về sau**. Nội dung tối-thiểu **4 trường**: - `phien-logic: L` · `cua-so: S..S` (các nhãn vật-lý thuộc phiên-logic này) · `closeout-commit: ` · `ket-qua: <1-3 dòng arc đã đóng>`. -- **Cân-sổ (khoá vòng ORPHAN-L):** sau khi ghi, **mọi `pause-*.md` trong thư-mục coi như ĐÃ ĐỐI-ỨNG** — `closed.md` đóng vai đối-ứng cho khối `pause` cuối chưa có `resume-*`. ⇒ [`tiep.md §0`](tiep.md) block **⑤ ORPHAN-L** hết kêu oan. -- 🔴 **Idempotent:** `closed.md` đã tồn-tại ⇒ **NO-OP, CẤM ghi đè** (phiên-logic đã khép; closeout chạy lại không được "mở lại rồi khép lại"). -- 🔴 **Thư-mục `session-/` KHÔNG tồn-tại ⇒ NO-OP im-lặng** — phiên này không mở phiên-logic nào (vd vào thẳng bằng `/tiep`, chưa từng `/session-start`). **CẤM tự tạo thư-mục chỉ để ghi `closed.md`** — tạo-rồi-đóng-ngay là **sổ rỗng giả**, đúng loại ĐẠT-ảo mà fail-safe NO-OP khắp harness đang cấm. +- **Ghi `.claude/sessions/session-/_end`** cho `` đang mở — 🔴 **TÊN `_end`, KHÔNG đuôi `.md`** (form hub, owner chốt @S148 *"đối ứng đúng chính xác"*). **FROZEN, ghi MỘT lần, CẤM sửa về sau**. Khuôn `key: value`, tối-thiểu: + `summary-frozen-s:` (1 dòng arc đã đóng) · `pointer:` (session-log/SUMMARY) · `carry:` (`a · b · c`) · `pending-anh:` · `pointers:` (run-folder) · `h24-tick: counter=` · `markers: _pause=

· _tiep= · _snapshot=`. + 🔴 **Trường `markers:` không phải trang-trí** — nó là **bản chụp cân-sổ tại lúc đóng**; sau khi đóng, detector bỏ qua thư-mục nên đây là **chứng duy nhất** còn lại về số lần dừng/nối. + 🧊 Form SE cũ = `closed.md` (4 trường prose) — **LEGACY, CẤM viết mới**; `session-1` còn dùng nên Sàn-3 vẫn nhận (dual-accept). +- **Cân-sổ (khoá vòng ORPHAN-L):** ghi xong ⇒ **thư-mục ĐÓNG TRỌN, MỌI pause coi như đã đối-ứng** — [`tiep.md §0`](tiep.md) ⑤ **bỏ qua hẳn** thư-mục đã chốt-kết. + 🔴 **Vá @S148 (fault-inject bắt):** bản cũ code `c=1` rồi so `p > r + c` ⇒ chỉ tha **ĐÚNG MỘT** pause chưa khớp ⇒ phiên đã đóng mà có **≥2** pause dư vẫn **KÊU OAN** — lệch đúng câu doc này vẫn luôn nói ("mọi pause"). Lệch sống từ S146, không lộ vì SE mới có `p=1`. +- 🔴 **Idempotent:** `_end` (hoặc `closed.md` legacy) đã tồn-tại ⇒ **NO-OP, CẤM ghi đè** (phiên-logic đã khép; closeout chạy lại không được "mở lại rồi khép lại"). +- 🔴 **Thư-mục `session-/` KHÔNG tồn-tại ⇒ NO-OP im-lặng** — phiên này không mở phiên-logic nào (vd vào thẳng bằng `/tiep`, chưa từng `/session-start`). **CẤM tự tạo thư-mục chỉ để ghi `_end`** — tạo-rồi-đóng-ngay là **sổ rỗng giả**, đúng loại ĐẠT-ảo mà fail-safe NO-OP khắp harness đang cấm. - **Add đích-danh** ở §5.1 (thư-mục phiên nằm trong hook-4-path nên hook cũng nuốt, nhưng **đừng dựa hook để chốt** — cùng lý-lẽ `pause.md §2.6`). 🔸 **Meld-forward (floor hub, miếng nền thứ-3) — CHƯA kiểm tại SE:** hub ràng *"commit mang thư-mục phiên được **nhập-tiến về trước** khi squash, KHÔNG bị bỏ rơi"*. §5.0/§5.2 của SE squash `wal:`-trailing bằng `reset --soft` ⇒ nội-dung **ở lại INDEX** nên **về lý là không mất**; nhưng **chưa có phép đo riêng** cho `.claude/sessions/`. ⇒ khai **CHƯA-ĐO**, không claim kín. diff --git a/.claude/commands/session-start.md b/.claude/commands/session-start.md index 93552d8..9491409 100644 --- a/.claude/commands/session-start.md +++ b/.claude/commands/session-start.md @@ -89,6 +89,20 @@ Em main đọc `.claude/WAL.md` (sổ mạch-việc-dở H22 — ghi-đè ≤40 - 🔴 **Ai cấp `` (khác "ai tạo thư-mục" — đừng lẫn):** **CHỈ `/session-start`** mới **MỞ SỐ MỚI** (`max( hiện có) + 1`). `/pause` · `/tiep` **KHÔNG BAO GIỜ tự tăng ``** — chúng chỉ **tạo thư-mục cho `` ĐANG MỞ** nếu nó chưa có trên đĩa (ca "cửa đầu chưa kịp scaffold"). Không có `` nào đang mở ⇒ dùng ` = max+1` **và ghi 1 dòng khai** trong khối đó là số được cấp ngoài `/session-start`. - 🧊 **Vá mâu-thuẫn @S146:** bản trước ghi *"Ai mở `` mới: **CHỈ** `/session-start`"* trong khi [`pause.md §2.6`](pause.md) ghi *"chưa có thì đây là cửa đầu ⇒ **tạo** `session-/`"* ⇒ **hai file nói ngược nhau về quyền tạo**. Gốc lỗi = **gộp hai khái niệm khác nhau** (*cấp SỐ* ⟂ *tạo THƯ-MỤC*) vào một câu. Nay tách đôi: **cấp số = độc-quyền `/session-start`** · **tạo thư-mục = idempotent, ai gặp trước thì tạo**. Hub chỉ ràng vế thứ hai. +**(a-bis) 🔴 DANH-MỤC FILE trong thư-mục — form HUB, owner chốt @S148 (*"đối ứng đúng chính xác như này"*, ảnh `session-109`; đối-chứng đĩa hub `AI_INFRA/.claude/sessions/session-102..109`):** + +| file | vai | ai ghi (canonical — B1, KHÔNG chép schema về đây) | +|---|---|---| +| `_context-s-.md` | **narrative durable**, 1 file/phiên-logic · §(a) STOCK-map pointer + §(b) **FLOW append-only** (`### PAUSE-` + dòng `> anh:` VERBATIM) · 🔴 CẤM rewrite/tự-tóm | append @[`pause.md §2.6`](pause.md) · đọc @[`tiep.md §2.6`](tiep.md) | +| `_pause-.md` | **marker máy** ~200B, 5 trường (`ts`·`head-sha`·`window-ordinal`·`jsonl-hint`·`account-label`) | [`pause.md §2.6`](pause.md) | +| `_tiep-.md` | marker máy, **cùng 5 trường** | [`tiep.md §2.6`](tiep.md) | +| `_snapshot-.md` | chụp-nhanh không-dừng | ⏳ **CHƯA CÓ ở SE** — cần lệnh `/snapshot` `[carry:snapshot-cmd]` + `[carry:pause-autosnap]` | +| `_end` | **chốt-kết, KHÔNG đuôi `.md`** · `markers:` chụp cân-sổ lúc đóng | [`session-end.md §6.3-bis`](session-end.md) | + +- 🔴 **`` đánh số TUẦN TỰ** trong thư-mục (`max+1`) — **KHÔNG** nhãn `S`, **KHÔNG** timestamp trong tên. Thứ-tự đọc được từ tên, ts nằm *trong* file. +- 🔴 **TÁCH marker ⟂ narrative** là điểm khác lớn nhất so với form SE cũ (gộp cả hai vào `pause-S-.md`). Marker cho MÁY đọc, narrative cho NGƯỜI/phiên-sau đọc. +- 🧊 **Form cũ = LEGACY, CẤM viết mới**; Sàn-3 ⑤ vẫn **nhận** (dual-accept, [`tiep.md §0`](tiep.md)) vì `session-1` còn dùng — đúng luật retire *"giữ khi CÒN người thụ-hưởng"*. + **(b) Đo TRƯỚC khi tin — `.gitignore` (đo @S146, KHÔNG suy):** ```bash git check-ignore -q .claude/sessions/session-1 ; echo "exit=$?" # → exit=1 @@ -103,6 +117,13 @@ git check-ignore -q .claude/sessions/session-1 ; echo "exit=$?" # → exit=1 - ✅ **`session_ctx_kb` (trần ngân-sách context-phiên) = 64 — OWNER CHỐT TƯỜNG-MINH @S146.** Canonical = [`memory-budget.json`](../agent-memory/memory-budget.json) → `session_ctx_kb` (+ `_session_ctx_kb_owner_set`); **B1 — KHÔNG chép số ra đây**, dòng này chỉ TRỎ. Đơn-vị **KB**, code đọc **nhân 1024**. 🔴 **Vẫn là GHOST-WIRE, đừng đọc thành "đã wire":** đo @S146 **0 script đọc khoá** (hit duy-nhất trong `scripts/` là **comment** `nhip-no-probe.ps1:42`; đối-chứng `pull_warn_days` **có** reader thật `:190`). Có số ≠ có hành-vi. Ai wire sau phải **đọc khoá**, không hardcode. +## 📋 BƯỚC 0.9 — Kéo thư cross-project (cửa VÀO 1/2 — owner chốt S148) + +**DÒ 2 kênh** rồi **KÉO đầy-đủ** theo định-tuyến: `se>0` → `/check-email all` · `all>0` → `/adap-apply`. + +> 🔗 **Luật nhịp đầy-đủ (4 cửa · fail-soft · quan-hệ với `pull-cach`) = canonical [`check-email.md`](check-email.md) §"Nhịp chạy — 4 CỬA PHIÊN".** 🔴 **B1 — mục này CHỈ TRỎ + GỌI, CẤM chép luật sang đây.** +> 🔴 **KHÔNG chặn bootstrap:** không có thư ⇒ 1 dòng đi tiếp; lỗi ⇒ `check-email loi (khong chan)` rồi đi tiếp. Kéo **TRƯỚC Phase 1 READ** là cố ý — thư mới có thể đổi chính kế-hoạch phiên mà Phase 1 sắp dựng. + ## Phase 1 — READ (load context) Đọc theo thứ tự, KHÔNG skip: diff --git a/.claude/commands/snapshot.md b/.claude/commands/snapshot.md new file mode 100644 index 0000000..ccb165d --- /dev/null +++ b/.claude/commands/snapshot.md @@ -0,0 +1,70 @@ +--- +description: Checkpoint KHÔNG-dừng-phiên — ghi _snapshot chốt trạng-thái + commit, KHÔNG stop/bookend (session-model, adopt S148) +--- +# /snapshot — chụp-nhanh không-dừng (SE port của hub `snapshot.md`, adopt S148 2026-07-24) + +Checkpoint **NHẸ** giữa phiên (**≤1 turn**): **1 Write + 1 commit**. **KHÔNG stop · KHÔNG bookend · KHÔNG flush agent-memory · KHÔNG hỏi thêm.** Xong là làm việc tiếp ngay. + +> 🔗 Cặp với `/pause` (dừng hẳn) và `/tiep` (nối lại). `/snapshot` **KHÔNG** phải điểm dừng — nó là **dấu mốc giữa đường**. +> 🧭 Đóng `[carry:snapshot-cmd]` (#15) — anh chốt @S148 *"làm đi không cần hỏi hub"*. + +## 0. GUARD — thư-mục có thể CHƯA activate +Resolve sID ``: đọc field `session:` trong `.claude/WAL.md` → mất thì lấy `.claude/sessions/session-*/` **mtime max** → vẫn mù thì **hỏi anh 1 dòng**. +🔴 `.claude/sessions/session-/` **CHƯA tồn-tại** ⇒ **SKIP ÊM**: in đúng 1 dòng `sessions/ chua activate — /snapshot no-op` rồi DỪNG. **KHÔNG mkdir, KHÔNG chết.** *(Cấp số `` là độc-quyền `/session-start` — [`session-start.md` BƯỚC 0.8](session-start.md).)* + +## 1. KHI NÀO fire (3 trigger — AI tự phán) +- **(i)** Có quyết-định/kết-luận vừa chốt **chưa nằm file nào** (còn trong transcript). +- **(ii)** **NGAY TRƯỚC** một workflow-launch diện-WAL: `taskList ≥ 3` **hoặc** ước > 5 phút — chốt state trước khi fan-out. +- **(iii) accumulation-gate:** đếm commit **substantive** tích-luỹ, bỏ qua nhiễu hook. + - **anchor = `{subject·ts}` của mốc NON-`wal:` gần nhất** mà `_snapshot` TRƯỚC đã pin (đọc `anchor` trong `_snapshot-.md`). 🔴 **CẤM lấy HEAD trần** — HEAD lúc snapshot thường là commit `wal:` do hook flush mỗi turn, và closeout-squash sẽ **rewrite chính sha đó** ⇒ neo vào nó là neo vào thứ sắp biến mất. Neo `{subject·ts}` sống sót squash. + - `count = git log --format=%s ..HEAD | grep -vc '^wal:'` + - **fire khi `count ≥ K`.** `K` đọc **LIVE** từ `.claude/agent-memory/memory-budget.json` key `snapshot_trigger_k`. + 🔸 **Khai thật @S148: key này CHƯA có trong file SE** ⇒ áp **default-if-absent = 6** đúng theo spec hub. Đây là **số của hub**, không phải anh chốt — anh muốn khác thì đặt key, đừng sửa số ở đây (B1). + - **fallback** (anchor không resolve được sau rewrite / chưa có `_snapshot` nào): `git log --since=`; **0 `_snapshot` nào** ⇒ snapshot ĐẦU do trigger (i)/(ii) lái, **KHÔNG** do (iii) — chống đếm-quá lúc folder vừa sinh. + +## 2. Ghi `_snapshot-.md` +`k = (số file `_snapshot-*.md` hiện có trong `session-/`) + 1`. +🔴 `_snapshot` **tự chứa essence** — **KHÔNG** double-write vào `_context` FLOW (FLOW = **@pause-only**). + +Derive machine-block TRƯỚC: `python scripts/session_ctx.py machine-block --session --json` + +```` +# _snapshot- — SESSION-MODEL checkpoint (sID ) + +ts: +head-sha: +head-subject: +head-ts: +label: <1 dòng — checkpoint này chốt cái gì> +snapshot-ordinal: + +## essence (≤3 dòng — chốt cái CHƯA nằm file) +- +- +- + +## machine-block +```json + +``` +```` + +**Trường BẮT BUỘC đủ:** `ts` · `head-sha` · `head-subject` + `head-ts` · `label` · `snapshot-ordinal` · `essence ≤3 dòng` · `machine-block`. +🔴 `head-subject`+`head-ts` = **chống sha-rewrite-rot**: sau squash sha đổi, `{subject·ts}` vẫn resolve lại được. +🔸 **Giới-hạn giữ khai (đo @S148):** `machine-block.changed_files` derive từ `git diff anchor..HEAD` ⇒ **chỉ thấy phần ĐÃ COMMIT**. Việc đang dirty trong cây (vd `docs/` chưa commit) **KHÔNG** hiện ra. Giữ đúng ngữ-nghĩa hub để sau này re-pull bằng diff; đừng đọc `changed_files` thành "toàn bộ việc đã làm". + +## 3. Secrets-sweep gate (TRƯỚC commit — BẮT BUỘC) +`python scripts/session_ctx.py secrets-sweep --session ` +- **exit 1 ⇒ CHẶN commit**: in `file:line` mọi hit, xử-lý/redact rồi mới commit. +- ✅ **Đã fault-inject @S148 2 chiều:** CHẶN đúng ca có `sk-…` · cho qua ca sạch · **0 dương-giả** với `ask-me`/`task-list` (token-anchored `\b`). +- ⚠️ **ACCEPTED-GAP khai thẳng:** sweep **pattern-bounded** (4 mẫu `voyage·sk-·gitea_pat·cfut_`) ⇒ **KHÔNG** phủ secret lớp khác (vd mật-khẩu DB trong tin-nhắn verbatim). Đây là **BLOCK bảo-thủ trước commit**, KHÔNG phải chứng-minh sạch tuyệt-đối. + +## 4. Commit (1 commit) +- `git add .claude/sessions/session-/_snapshot-.md` — 🔴 **CHỈ file đích danh, CẤM `git add -A`/`.`** (rủi ro rò secret). +- `git commit -m "wal: snapshot s _snapshot-"` — ASCII-clean (tránh em-dash mangle dưới PowerShell). **KHÔNG `--no-verify`. KHÔNG push.** +- 🔸 **Meld-forward @closeout — CHƯA ĐO tại SE:** hub ràng commit mang `.claude/sessions/` phải được **nhập-tiến** vào mốc non-`wal:` liền sau khi squash, KHÔNG bị DROP như `wal:` thuần. SE squash bằng `reset --soft` ⇒ **về lý không mất**, nhưng **chưa có phép đo riêng** — trùng `[carry:meld-forward-unmeasured]` (#19). Đừng claim kín. + +## 5. Xong +Báo **1 dòng**: `snapshot- ghi @ (count=/K, trigger-) — committed, tiếp việc` +rồi **1 dòng nhịp-nợ**: `powershell.exe -ExecutionPolicy Bypass -File scripts/nhip-no-probe.ps1` (lỗi → `probe-loi (khong chan)`). +🔴 **KHÔNG stop · KHÔNG bookend · KHÔNG DỪNG HẲN** — quay lại việc đang làm ngay. diff --git a/.claude/commands/tiep.md b/.claude/commands/tiep.md index 365ecba..1e99005 100644 --- a/.claude/commands/tiep.md +++ b/.claude/commands/tiep.md @@ -86,21 +86,32 @@ done git log --name-only --format='%s' origin/main..HEAD | grep -q '.claude/sessions/' # (b) cửa ĐĨA — git-independent, miễn-nhiễm hook (mirror ①: đọc đĩa, KHÔNG đọc git) -# Cân-bằng-sổ: mỗi `pause-*` phải có ĐÚNG một đối-ứng (`resume-*` do /tiep ghi, hoặc closed.md do đóng-L). +# Cân-bằng-sổ: mỗi pause phải có ĐÚNG một đối-ứng (tiep do /tiep ghi, hoặc chốt-kết do đóng-L). +# 🔴 DUAL-ACCEPT 2 FORM (S148) — cùng khuôn dual-accept của ① ở trên: +# HUB (chuẩn từ S148, owner chốt "đối ứng đúng chính xác"): _pause-.md · _tiep-.md · _end (KHÔNG đuôi) +# LEGACY (di-sản ≤S147): pause-*.md · resume-*.md · closed.md +# 🔴 TÍNH-CHẤT CỨU MẠNG: glob `pause-*` KHÔNG khớp `_pause-1.md` (tên bắt đầu bằng `_`) +# ⇒ 2 form KHÔNG đè nhau ⇒ cộng thẳng là an-toàn, không đếm đôi. Đã fault-inject 2 chiều @S148. for d in .claude/sessions/session-*/; do [ -d "$d" ] || continue - p=$(ls "$d"pause-*.md 2>/dev/null | wc -l); [ "$p" -eq 0 ] && continue - r=$(ls "$d"resume-*.md 2>/dev/null | wc -l) - c=0; [ -f "$d/closed.md" ] && c=1 - [ "$p" -gt $((r + c)) ] && echo "ORPHAN-L: $d (pause=$p resume=$r closed=$c)" + # 🔴 CHỐT-KẾT ĐÓNG TRỌN THƯ-MỤC, không phải "+1" (vá @S148 — fault-inject bắt). + # session-end.md §6.3-bis nói rõ: sau khi ghi chốt-kết thì MỌI pause "coi như ĐÃ ĐỐI-ỨNG". + # Bản cũ viết `c=1` rồi `p > r + c` ⇒ chỉ tha ĐÚNG MỘT pause chưa khớp ⇒ phiên đã đóng mà + # có ≥2 pause dư vẫn KÊU OAN. Doc và máy lệch nhau từ S146; dữ-liệu SE quá nhỏ (p=1) nên chưa lộ. + { [ -f "$d/_end" ] || [ -f "$d/closed.md" ]; } && continue + p=$(( $(ls "$d"_pause-*.md 2>/dev/null | wc -l) + $(ls "$d"pause-*.md 2>/dev/null | wc -l) )) + [ "$p" -eq 0 ] && continue + r=$(( $(ls "$d"_tiep-*.md 2>/dev/null | wc -l) + $(ls "$d"resume-*.md 2>/dev/null | wc -l) )) + [ "$p" -gt "$r" ] && echo "ORPHAN-L: $d (pause=$p tiep=$r, chua chot-ket)" done ``` -- ✅ **Đối-ứng `resume-*` CÓ người ghi thật** — `/tiep` §2.6 dưới (cùng file này) ⇒ vòng `pause ↔ tiep` **tự đóng**, không phải cơ-chế treo. -- ✅ **`closed.md` ĐÃ WIRE @S146** (owner chốt *"giống hub"* — `_end` chốt-kết của manifest `101e69d6`): nghi-thức ghi nằm ở [`session-end.md`](session-end.md) **§6.3-bis** (FROZEN · 4 trường · idempotent NO-OP · NO-OP im-lặng nếu 0 thư-mục). ⇒ **cân-sổ nay ĐỦ CẢ HAI vế**: `pause` đối-ứng bằng `resume-*` (`/tiep §2.6`) **hoặc** `closed.md` (đóng-L) ⇒ ca *"pause rồi đóng thẳng không qua `/tiep`"* **hết kêu oan**. +- ✅ **Đối-ứng `_tiep-.md` CÓ người ghi thật** — `/tiep` §2.6 dưới (cùng file này) ⇒ vòng `pause ↔ tiep` **tự đóng**, không phải cơ-chế treo. *(legacy `resume-*` vẫn được đếm — dual-accept ở máy trên.)* +- ✅ **Chốt-kết `_end` ĐÃ WIRE** (owner chốt @S146 *"giống hub"*, đổi tên `closed.md`→`_end` @S148 cho khớp form hub — manifest `101e69d6`): nghi-thức ghi ở [`session-end.md`](session-end.md) **§6.3-bis** (FROZEN · idempotent NO-OP · NO-OP im-lặng nếu 0 thư-mục). ⇒ **cân-sổ đủ CẢ HAI vế**: `pause` đối-ứng bằng `_tiep-` (`/tiep §2.6`) **hoặc** `_end` (đóng-L) ⇒ ca *"pause rồi đóng thẳng không qua `/tiep`"* **hết kêu oan**. + 🔴 **@S148 mạnh thêm một nấc:** `_end` nay **ĐÓNG TRỌN thư-mục** (detector `continue` hẳn), không còn là "+1". Fault-inject bắt: phiên đã đóng mà dư ≥2 pause thì bản cũ **vẫn KÊU OAN** — lệch đúng câu *"MỌI pause coi như đã đối-ứng"* mà `session-end §6.3-bis` vẫn luôn nói. Doc đúng, máy sai, sống từ S146. 🧊 *Nấc cũ (tới S145): "`closed.md` CHƯA wire — VC-2 còn chờ anh duyệt", kèm hệ-quả "chỉ ca pause-rồi-đóng-thẳng mới thiếu đối-ứng". **Cả hai nay SAI** — giữ 1 dòng làm vết.* 🔴 **Bài học lấy từ chính chỗ này (H24 `lead-view-auditor` bắt @S146):** MÁY ở ngay trên (`:89` `:94` `:95`) **đã đọc `closed.md` thật** từ lúc block này ra đời, mà **PROSE vẫn khai "chưa wire"** ⇒ đây là **lật-ngược của ghost-wire**: **wired-but-declared-unwired** — và nó nằm đúng dưới nhãn *"chống ghost-wire H18"*. ⇒ **Ghost-wire soi được 2 CHIỀU**: khai-có-mà-không-chạy **và** chạy-thật-mà-khai-chưa. -- 🟢 **Hướng hỏng = AN-TOÀN:** quên ghi `resume-*` ⇒ sổ lệch ⇒ **KÊU ⇒ HỎI LẠI ANH**. Sai về phía *hỏi thừa*, không về phía *im lặng bỏ việc* — đúng thiên-hướng của Sàn-3. +- 🟢 **Hướng hỏng = AN-TOÀN:** quên ghi `_tiep-.md` ⇒ sổ lệch ⇒ **KÊU ⇒ HỎI LẠI ANH**. Sai về phía *hỏi thừa*, không về phía *im lặng bỏ việc* — đúng thiên-hướng của Sàn-3. ### 🟡 BẬC TRUNG-BÌNH — nhiều file bẩn **NGOÀI** 4 path hook persist @@ -143,10 +154,20 @@ Tín-hiệu **YẾU NHẤT**, dùng cuối. 🔴 **`porcelain` rỗng KHÔNG ph - **Dòng-nợ (SAU tick — n-3):** gọi `powershell.exe -ExecutionPolicy Bypass -File scripts/nhip-no-probe.ps1` in dòng-nợ 4-vế (vế-1 nhịp-kiểm đọc post-tick). 🔴 **probe KHÔNG chặn điểm dừng**: lỗi → in `probe-loi (khong chan)` → đi tiếp. > 🔗 **Predicate C7 đầy-đủ (probe được phép chạy ở điểm dừng) = canonical [`harness-11-engine.md`](../../docs/governance/harness-11-engine.md) §N.8.** 🔴 **B1 — dòng này CHỈ TRỎ, CẤM chép predicate sang đây** (chép 2 nơi = 2 nguồn sự-thật, đúng thứ B1 sinh ra để chặn). -## 2.6 Đọc context-map phiên-LOGIC + ghi biên-nhận `resume-*` (S146 — cặp với `/pause` bước 2.6) +## 2.5b Kéo thư cross-project (cửa VÀO 2/2 — owner chốt S148) -- **Đọc:** nếu có `.claude/sessions/session-/` → đọc **TẤT CẢ** `pause-*.md` theo thứ-tự thời-gian = **bối-cảnh cộng-dồn** của phiên-logic (quyết-định đã chốt · delta còn sống · con-trỏ run/commit). 🔴 **Đây là lane CỘNG-DỒN, khác WAL** — WAL là sổ **mạch-sống ghi-đè ≤40 dòng** (liếc-một-cái-là-biết); context-map là **tích-luỹ, không ghi đè**. Đọc context-map **KHÔNG** thay việc chạy `verify:` — ground-truth vẫn thắng (§3). -- **Ghi biên-nhận — 🔴 THỰC-HIỆN SAU §3** (đọc ở đây, ghi sau khi reconcile xong; **bắt buộc**): tạo `.claude/sessions/session-/resume-S-.md` — **1 khối ngắn**: nối từ `pause-*` nào · lệch gì so `chain:` (nếu có) · `next:` đang thực-thi. +**DÒ 2 kênh** (`outbox/se` + `outbox/all` × 6 repo) — **NGAY SAU dòng-nợ** (vế-4 `pull-cach` vừa in xong ⇒ đọc số rồi dò là đúng thứ-tự nhân-quả). In `thu-moi: se=X all=Y`. +🔴 **CHỈ list + so id. KHÔNG copy · KHÔNG hash · KHÔNG python** (đo @S148: dò ~5ms · kéo ~200–500ms/thư). `X|Y > 0` ⇒ nêu **1 dòng** rồi đi tiếp §2.6; **kéo thật để bookend làm** (`se`→`/check-email` · `all`→`/adap-apply`). + +> 🔗 **Luật nhịp đầy-đủ = canonical [`check-email.md`](check-email.md) §"Nhịp chạy — 4 CỬA PHIÊN".** 🔴 **B1 — CHỈ TRỎ + GỌI, CẤM chép luật.** +> 🔴 **KHÔNG chặn recovery:** lỗi ⇒ `check-email loi (khong chan)` → đi tiếp §2.6. **Cửa này quan-trọng NHẤT trong 4** — *"1 session kéo 2-3 phiên"* nghĩa là các phiên GIỮA đều vào bằng `/tiep`, nên bỏ cửa này là bỏ phần lớn lưu-lượng. + +## 2.6 Đọc `_context-s-.md` + ghi marker `_tiep-.md` (S146 · form hub @S148 — cặp với `/pause §2.6`) + +- **Đọc (form HUB, chuẩn @S148):** có `.claude/sessions/session-/` → đọc **`_context-s-.md`** — đó là **bối-cảnh cộng-dồn** (§(a) STOCK-map pointer + §(b) FLOW append-only: mọi entry `### PAUSE-` theo thứ-tự, kèm dòng `> anh:` verbatim). Marker `_pause-*.md`/`_tiep-*.md` là **5 trường máy**, đọc để biết `window-ordinal` + `head-sha`, KHÔNG chứa narrative. + 🧊 **Legacy ≤S147:** thư-mục không có `_context-s-.md` ⇒ đọc **TẤT CẢ** `pause-*.md` theo thứ-tự thời-gian (form cũ gộp marker+narrative). Nhận-để-đọc, **CẤM viết mới** kiểu đó. 🔴 **Đây là lane CỘNG-DỒN, khác WAL** — WAL là sổ **mạch-sống ghi-đè ≤40 dòng** (liếc-một-cái-là-biết); context-map là **tích-luỹ, không ghi đè**. Đọc context-map **KHÔNG** thay việc chạy `verify:` — ground-truth vẫn thắng (§3). +- **Ghi biên-nhận — 🔴 THỰC-HIỆN SAU §3** (đọc ở đây, ghi sau khi reconcile xong; **bắt buộc**): tạo **`_tiep-.md`** (`` = `max(_tiep-*)+1`) — đúng **5 trường ~200B**, y khuôn `_pause-.md` (xem [`pause.md §2.6`](pause.md)): `ts` · `head-sha` · `window-ordinal` · `jsonl-hint` · `account-label`. + 🔴 **Marker là MÁY, không phải bài luận** — kết-quả reconcile (lệch gì so `chain:`, `next:` đang chạy) thuộc về **`_context-s-.md`**, không nhồi vào marker. *(Bản S148 đầu ghi `resume-S-.md` 2,5KB narrative — sai form, đã gỡ.)* - 🔴 **Vì sao bắt buộc:** biên-nhận này là **đối-ứng cân-sổ** của tín-hiệu **ORPHAN-L ⑤** (§0). Thiếu nó ⇒ sổ lệch ⇒ lần sau **KÊU** ⇒ hỏi lại anh. Hỏng về phía *hỏi thừa* = an-toàn; nhưng ghi đủ thì sàn mới **im đúng**. - 🔸 `session-` **KHÔNG mở mới ở `/tiep`** — `/tiep` **nối** phiên-logic đang mở. Mở `` mới = việc của `/session-start` (xem `session-start.md` BƯỚC 0.8). diff --git a/.claude/governance/.session-counter.json b/.claude/governance/.session-counter.json index 08b2c8e..68ad556 100644 --- a/.claude/governance/.session-counter.json +++ b/.claude/governance/.session-counter.json @@ -11,10 +11,10 @@ }, "_tick_invariant_note": "Tick invariant (hub dede7ec5 Delta-1, verbatim): moi LAN-CHOT +1; mot cap dung-noi tang DUNG +1, khong +2, khong +0. SE form = tick-at-entry-gate idempotent-per-label (hub Delta-2 recovery-gate +1 = permitted form) - NET +1/session-label EQUIV hub +1/cap on CLOSED pairs; an OPEN pair is transiently +0 until its entry gate fires (hub blessed, 9a35405b block-1 phep dung-noi). Guard song-con = label-convention (session-start 2.1.8: new conversation = new S label, NEVER reuse). history[] is append-unbounded BY DESIGN => absence of a marker = never-happened (safe semantics); IF a FIFO cap is ever added, eviction MUST be handled explicitly (absence-vi-bi-day != absence-vi-chua-xay-ra - log-BOUNDED design-note dede7ec5).", "_seed_honesty": "Seeded UNTICKED on purpose. counter=0 and last_ticked_* = null mean 'no tick has ever happened', which is the truth at S121 - the ritual that performs the tick lands in W3. Seeding a fake first tick here would make the very first cadence reading a lie, and H24 exists to catch exactly that kind of invented number.", - "counter": 21, - "last_ticked_session": "S147", - "last_ticked_head": "5f6c3becfb4ce958c7edad7d9f1058cb7289491e", - "last_ticked_at": "2026-07-23", + "counter": 22, + "last_ticked_session": "S148", + "last_ticked_head": "581544200b4773db810808c063f4245fe54e004c", + "last_ticked_at": "2026-07-24", "last_audit": { "light_at_counter": 17, "deep_at_counter": 17, @@ -78,6 +78,11 @@ "at": "2026-07-23", "session": "S147", "event": "squash-benign (product/UAT session, tick @/pause - tick DAU cua S147 vi vao bang /tiep dau phien KHONG tick, da defer). Tick S146->S147: counter 20->21, old last_ticked_head 474199b3 (S146) -> new 5f6c3be. Phan-loai TRUOC tick: (a) git cat-file -t 474199b3 = commit => object CON TON TAI; (b) git merge-base --is-ancestor 474199b3 HEAD = exit 1 => KHONG reachable; (c) counter doc 20 == luu 20 => KHONG lui. => SQUASH-BENIGN => ghi vet + DI TIEP, KHONG alarm owner. Nguyen-nhan: S147 code-push reset 5 wal:-commit + docs-closeout squash 1 wal: nen head luc tick S146 roi khoi lich su - drift KY-VONG, khong tamper; nhan-phien cu 'S146' con nguyen = corroborate 2 nguon. OVERDUE sau tick: light 21-17=4 < 6, deep 21-17=4 < 15 => ca hai CHUA toi han (moc ke: light ~23, deep ~32). JUMP: class_repeat cao nhat gap-owner-specifics=2 < 3 => khong jump. => KHONG co H24 audit phien nay." + }, + { + "at": "2026-07-24", + "session": "S148", + "event": "CLEAN tick (KHONG squash-benign) qua /tiep noi mach S147. Nhan-phien MOI S148 theo label-convention S135 (conversation moi = nhan moi ke ca vao bang /tiep; reuse S147 se keo OR-guard session-clause NO-OP => tiep KHONG tick => phep-7 +0, cam). Tick S147->S148: counter 21->22, head 5f6c3be -> 5815442 (wal: pause local, chua push). Phan-loai TRUOC tick tren last_ticked_head cu 5f6c3be: (a) counter doc 21 == luu 21 => KHONG lui; (b) git cat-file -t 5f6c3be = commit => object CON TON TAI; (c) git merge-base --is-ancestor 5f6c3be HEAD = exit 0 => CON REACHABLE => tick SACH binh-thuong, KHONG squash-benign, KHONG fail-loud. Vi sao khac S147: S147 tick @/pause roi closeout squash nhac dau-tick; lan nay chua co closeout nao chay sau tick nen 5f6c3be van nam thang tren duong toi HEAD (HEAD = 5f6c3be + 1 commit wal: pause). OVERDUE sau tick: light 22-17=5 < 6, deep 22-17=5 < 15 => ca hai CHUA toi han (moc ke: light ~23 = NGAY PHIEN SAU, deep ~32). JUMP: class_repeat cao nhat gap-owner-specifics=2 < 3 => khong jump. => KHONG co H24 audit phien nay." } ] } \ No newline at end of file diff --git a/.claude/templates/session-context-template.md b/.claude/templates/session-context-template.md new file mode 100644 index 0000000..1eeba21 --- /dev/null +++ b/.claude/templates/session-context-template.md @@ -0,0 +1,57 @@ +# _context-s-{{N}} — SESSION-MODEL narrative durable + + + +> **sID (LOGIC-session) = {{N}}.** 1 phiên-logic (bootstrap → work → sweep) trải nhiều window vật-lý nối bằng `/pause`+`/tiep`. Window = 0-ID governance; **sID = đơn-vị LOGIC**. Folder pin: `.claude/sessions/session-{{N}}/`. +> ts-mốc khởi-tạo = **{{TS}}** (nguồn: `{{TS_SOURCE}}`) — mốc git-committer, KHÔNG phải wall-clock tuỳ-tiện. +> +> 🔴 **CẤM rewrite / tự-tóm block FLOW** (chống self-summary lossy). FLOW = append-only @`/pause`-only; vượt cap `session_ctx_kb` → distill SECTION CŨ thành pointer/gist, TUYỆT ĐỐI KHÔNG sửa entry verbatim đã ghi. + +--- + +## (a) STOCK-map — reference-not-copy + +> Bảng pointer tới nguồn durable. **KHÔNG copy nội-dung** (chống drift). Điền @bootstrap; row session-specific append dưới các row stable. + +| Tên | Path | 1-dòng | +|---|---|---| +| WAL | `.claude/WAL.md` | mạch-việc-dở máy-state (H-22) — nối bằng `/tiep` | +| AI-context | `CLAUDE.md` | AI agent context SOLUTION_ERP (scope + quick-rules) | +| Trạng-thái | `docs/STATUS.md` | canonical mọi con-số (mig · bảng · test · roster) | +| Bàn-giao | `docs/HANDOFF.md` | brief 5 phút + bảng số chờ-anh + carry | +| Sổ đếm H24 | `.claude/governance/.session-counter.json` | counter + nhịp lead-self-audit | +| Thư cross-project | `broadcasts/_index.md` | INBOUND directed (`outbox/se`) — fan-out `outbox/all` KHÔNG vào đây | +| _(session-specific)_ | _(path)_ | _(1-dòng — append @bootstrap phiên này)_ | + +--- + +## (b) FLOW — append-only @pause-only + +> 🔴 **CẤM rewrite FLOW.** Mỗi `/pause` APPEND đúng 1 entry theo SCHEMA dưới (đặt DƯỚI dòng `FLOW-START`). Entry cũ = immutable. FLOW = SOURCE durable của "đang-đến-đâu"; bảng-đầu-việc = VIEW derive từ đây (single-source). + + + + + +_(chưa có PAUSE — entry đầu append @`/pause` đầu tiên của phiên)_ + +--- + +## (c) STOCK-touched — máy-derive + +> Bảng file-đã-đụng trong phiên, **máy-derive** từ `git diff --name-only ..HEAD` @pause/snapshot. KHÔNG điền tay. + +| File | Δ | Ghi-chú | +|---|---|---| +| _(máy-derive @pause/snapshot)_ | | | diff --git a/broadcasts/inbox/2026-07-18-Governance-spawn-label-panel-audit-khung.md b/broadcasts/inbox/ai_infra/2026-07-18-Governance-spawn-label-panel-audit-khung.md similarity index 100% rename from broadcasts/inbox/2026-07-18-Governance-spawn-label-panel-audit-khung.md rename to broadcasts/inbox/ai_infra/2026-07-18-Governance-spawn-label-panel-audit-khung.md diff --git a/broadcasts/inbox/2026-07-19-Agent-ensemble-cost-lens-khan-hiem-va-routing.md b/broadcasts/inbox/ai_infra/2026-07-19-Agent-ensemble-cost-lens-khan-hiem-va-routing.md similarity index 100% rename from broadcasts/inbox/2026-07-19-Agent-ensemble-cost-lens-khan-hiem-va-routing.md rename to broadcasts/inbox/ai_infra/2026-07-19-Agent-ensemble-cost-lens-khan-hiem-va-routing.md diff --git a/broadcasts/inbox/2026-07-19-Agent-ensemble-depth-axis-deepen-on-contested.md b/broadcasts/inbox/ai_infra/2026-07-19-Agent-ensemble-depth-axis-deepen-on-contested.md similarity index 100% rename from broadcasts/inbox/2026-07-19-Agent-ensemble-depth-axis-deepen-on-contested.md rename to broadcasts/inbox/ai_infra/2026-07-19-Agent-ensemble-depth-axis-deepen-on-contested.md diff --git a/broadcasts/inbox/2026-07-19-Agent-ensemble-depth-cost-update-so-do-thuc.md b/broadcasts/inbox/ai_infra/2026-07-19-Agent-ensemble-depth-cost-update-so-do-thuc.md similarity index 100% rename from broadcasts/inbox/2026-07-19-Agent-ensemble-depth-cost-update-so-do-thuc.md rename to broadcasts/inbox/ai_infra/2026-07-19-Agent-ensemble-depth-cost-update-so-do-thuc.md diff --git a/broadcasts/inbox/2026-07-19-Governance-goi-chot-owner-nam-khoan.md b/broadcasts/inbox/ai_infra/2026-07-19-Governance-goi-chot-owner-nam-khoan.md similarity index 100% rename from broadcasts/inbox/2026-07-19-Governance-goi-chot-owner-nam-khoan.md rename to broadcasts/inbox/ai_infra/2026-07-19-Governance-goi-chot-owner-nam-khoan.md diff --git a/broadcasts/inbox/2026-07-19-Governance-harness-dong-du-vong-audit-va-eval-use-side.md b/broadcasts/inbox/ai_infra/2026-07-19-Governance-harness-dong-du-vong-audit-va-eval-use-side.md similarity index 100% rename from broadcasts/inbox/2026-07-19-Governance-harness-dong-du-vong-audit-va-eval-use-side.md rename to broadcasts/inbox/ai_infra/2026-07-19-Governance-harness-dong-du-vong-audit-va-eval-use-side.md diff --git a/broadcasts/inbox/2026-07-19-Governance-hub-tu-danh-gia-3-goc-va-chuyen-pha.md b/broadcasts/inbox/ai_infra/2026-07-19-Governance-hub-tu-danh-gia-3-goc-va-chuyen-pha.md similarity index 100% rename from broadcasts/inbox/2026-07-19-Governance-hub-tu-danh-gia-3-goc-va-chuyen-pha.md rename to broadcasts/inbox/ai_infra/2026-07-19-Governance-hub-tu-danh-gia-3-goc-va-chuyen-pha.md diff --git a/broadcasts/inbox/2026-07-20-Governance-3-khoan-title-tier-posture-home-distill-params.md b/broadcasts/inbox/ai_infra/2026-07-20-Governance-3-khoan-title-tier-posture-home-distill-params.md similarity index 100% rename from broadcasts/inbox/2026-07-20-Governance-3-khoan-title-tier-posture-home-distill-params.md rename to broadcasts/inbox/ai_infra/2026-07-20-Governance-3-khoan-title-tier-posture-home-distill-params.md diff --git a/broadcasts/inbox/2026-07-20-Governance-auto-toan-vong-distill-2-tang.md b/broadcasts/inbox/ai_infra/2026-07-20-Governance-auto-toan-vong-distill-2-tang.md similarity index 100% rename from broadcasts/inbox/2026-07-20-Governance-auto-toan-vong-distill-2-tang.md rename to broadcasts/inbox/ai_infra/2026-07-20-Governance-auto-toan-vong-distill-2-tang.md diff --git a/broadcasts/inbox/2026-07-21-Governance-eval-quality-audit-ra-thuoc-eval-cua-eval.md b/broadcasts/inbox/ai_infra/2026-07-21-Governance-eval-quality-audit-ra-thuoc-eval-cua-eval.md similarity index 100% rename from broadcasts/inbox/2026-07-21-Governance-eval-quality-audit-ra-thuoc-eval-cua-eval.md rename to broadcasts/inbox/ai_infra/2026-07-21-Governance-eval-quality-audit-ra-thuoc-eval-cua-eval.md diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 1f6e642..4edd314 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -5,14 +5,16 @@ **🆕 Last updated:** 2026-07-23 (**S147** — product/UAT session, KHÁC chuỗi governance S135–S146. **2 việc anh giao SHIP PROD:** ① vá bug đính-kèm "Bảng so sánh" không sửa được khi phiếu Duyệt NCC bị **Trả lại** — gate nút theo `(isAdmin||isDrafter) && isEditablePhase(phase)` thay `readOnly` màn-hình, cắm `:2013` không `:421`, cả 2 app (reviewer né 3 bẫy: `:421` HoSoLink-bleed · SupplierAttachmentsCell Mig28-F3 carve-out · BE-guard vỡ S78 → descope+no-BE-guard) · ② **gom 3 cặp dự án trùng dấu-cách trên PROD** (`FLOCK 01`/`FLOCK 03`/`CAL 01`→no-space; 5 phiếu+5 NS repoint, 3 ẩn mềm ĐẢO ĐƯỢC, qua `ssh vietreport-vps` integrated-auth `-E` không chạm password) + vá gốc chuẩn-hóa `Code` (Trim-lưu + kiểm-trùng bỏ-dấu-cách, +4 test `ProjectCodeNormalizationTests`). Anh dặn "sau này tối ưu kỹ lại" = first-pass. 2 commit `7d39d8d`+`4510cea`, **CI #407 PASS 5m36s**, bundle rotate ×2 verify 2-nguồn (curl em-main khớp cicd-monitor). **#53 garble ×3** (invest-① + review-② + cicd) resume 3/3 sạch. State: **Mig 67 · 89 bảng · 532 test (45D+487I, +4) · bundle admin `B2j1LQ-v`/user `C3wbJegk`.** → log `2026-07-23-1730-S147-pe-attach-fix-project-dedup.md`.) > ↓ **S146 governance + 20 carry MỞ + NEXT items bên dưới GIỮ NGUYÊN** — S147 là product-work, KHÔNG đụng chuỗi governance/session-model ↓ +**🔧 Re-stamp @S148 (2026-07-24 — GIỮA PHIÊN, CHƯA closeout):** flush bảng số bên dưới cho khớp **ĐĨA** sau khi `/tiep` phát-hiện nó stale. **#1 trio first-run → ĐÓNG** (chạy thật @S144, bằng-chứng `runs/2026-07-22-S144-trio-first-run/` + commit `8b6df0a`); dòng cũ **sống sai 4 phiên** vì bảng thuộc khối S141–S143 mà S144→S147 bồi thêm ở **TRÊN** rồi không re-stamp — lead trình lại cho anh một việc đã xong. **#3 tools-whitelist → anh CHỐT (a) giữ nguyên**, số `6 vai` → **`15 vai`** đo tươi. 🔒 **Slot số GIỮ NGUYÊN, cấm tái-dụng** slot đã đóng (anh trả lời bằng số). ⇒ **Còn 10/12 mục chờ anh: 2·4·5·6·7·8·9·10·11·12** — trong đó **#2 `pull_warn_days` anh CHƯA trả lời** (chỉ chốt "a" cho #3). **Ngoài bảng:** UAT nghiệm-thu ① S147 chưa có tin. + **Last updated (kỳ trước):** 2026-07-23 (**S146** — `/tiep` nối S145 → áp `spec-v2` 5 axis (A/C+E/D; B landed S145) → owner chốt **3 câu session-model** (scaffold idempotent · `closed.md` WIRE · V2 **auto**) + `session_ctx_kb=64` tường-minh. **LIVE-VERIFY hook 4-path PASS** (`da7ee8a`). **session-model chạy trọn vòng đầu tiên**: `pause-S146` → `resume-S146` → ORPHAN-L cân sổ. **H24 force-fire** (P=8): 2 vai + `h24-audit`, **cả 3 garble #53** — ghi-đĩa-sớm cứu được, ghi-muộn mất trắng. **7 FLAG** → vá hết + re-verify. Governance-only, 0 prod-code.) -**🆕 Carry MỚI S146 (6) — 🔴 gộp vào khối dưới ⇒ TỔNG 20, KHÔNG phải 14:** (15) `/snapshot` — lệnh **chụp-nhanh không-dừng** của hub floor (manifest `101e69d6` §1, lệnh thứ-3), SE **thiếu hẳn** `[carry:snapshot-cmd]` · (16) `_context` tầng **STOCK** + nén-thành-con-trỏ (SE mới có tầng *flow*) `[carry:context-stock]` — 🔸 đây là chỗ `session_ctx_kb=64` sẽ có **reader thật**, hết ghost-wire · (17) `/pause` **tự chạy 1 chụp-nhanh + nén** rồi mới dừng `[carry:pause-autosnap]` · (18) `/tiep` đọc **snapshot mới nhất** + phòng **cửa-sổ-sập** `[carry:tiep-window-crash]` · (19) **meld-forward** `.claude/sessions/` qua squash — **CHƯA ĐO** `[carry:meld-forward-unmeasured]` · (20) **bookend đủ 5/5 vòng?** — nay V1+V3 hai-đầu-thật · V2 auto-theo-nhịp · V4 on-signal · V5 liên-tục `[carry:bookend-5-5]`. +**🆕 Carry MỚI S146 (6) — 🔴 gộp vào khối dưới ⇒ TỔNG 20, KHÔNG phải 14:** (15) ~~`/snapshot`~~ ✅ **ĐÓNG @S148** — port từ hub `snapshot.md`; SE có [`.claude/commands/snapshot.md`](../.claude/commands/snapshot.md) + `_snapshot-1.md` THẬT đã ghi bằng chính lệnh đó (chống ghost-wire) `[carry:snapshot-cmd]` · (16) `_context` tầng **STOCK** — 🟡 **ĐÓNG MỘT NỬA @S148**: tầng STOCK **CÓ RỒI** (`§(a) STOCK-map` trong template + `_context-s-2.md`, 6 row pointer reference-not-copy) ✅; nhưng vế *"`session_ctx_kb=64` sẽ có **reader thật**"* **VẪN HỞ** — SE **cố ý KHÔNG port** `get_cap_bytes` của hub vì chưa có caller (port không caller = đúng ghost-wire mình đang chống) ⇒ `session_ctx_kb` **vẫn là ghost-wire**, giữ carry cho vế đó `[carry:context-stock]` · (17) ~~`/pause` tự chạy chụp-nhanh~~ ✅ **ĐÓNG @S148** — wire ở `pause.md §2.6(D)`, **đo trước khi wire** đúng câu hub bắt hỏi: chi-phí RÒNG **~64ms** (`machine-block` ~140ms vốn đã phải chạy cho §(c) STOCK-touched) `[carry:pause-autosnap]` · (18) `/tiep` đọc **snapshot mới nhất** + phòng **cửa-sổ-sập** `[carry:tiep-window-crash]` · (19) **meld-forward** `.claude/sessions/` qua squash — **CHƯA ĐO** `[carry:meld-forward-unmeasured]` · (20) **bookend đủ 5/5 vòng?** — nay V1+V3 hai-đầu-thật · V2 auto-theo-nhịp · V4 on-signal · V5 liên-tục `[carry:bookend-5-5]`. > 🔴 **Vì sao phải nằm ở ĐÂY:** H24 `lead-omission-auditor` bắt @S146 — 6 mục này từng **tách đôi** giữa WAL `carry:` (5/6) và session-log (4/6), **không sổ nào đủ**; nguồn đủ chỉ ở `sessions/session-1/pause-S146-*.md` = **artifact theo-cửa-sổ, KHÔNG phải sổ backlog**. `/session-end §6.4` **reset WAL** ⇒ 2 mục sẽ **bốc hơi**. Và cả 6 **chưa có tag `[carry:*]`** nên detector H24-2 chỉ soi được 6/14 — nay đã gắn tag. **Last updated (kỳ trước):** 2026-07-22 (S141–S143 — **Wave adap đợt-9/10 ĐÓNG TRỌN (7 thư) + closeout OUTWARD đã push `2757e41`/`3f4310f` — governance-only: 0 prod-code/0 mig, test 528 giữ, bundle FROZEN. Roster 14→17.** S141 vòng-1 `wf_3e1ade40-704` CHẾT giữa chừng → reconcile-từ-ĐĨA + đóng orphan partial-honest 5/13; S142 vòng-2 CẮT-GỌT `wf_1e936b16-30c` 2/2 lane 0-garble land trio `harness-{eval,refine,audit}` + contract-v2 ①a + stamp-decode + annex phép-8 + sweep 13 site; **S143 `/tiep` closeout OUTWARD anh gật**: adap-report 7/7 · email hub `6c94873f72e0` selftest exit 0 · STAGE-2 0-pending cross-check 2 chiều · squash K=8 push 0-`wal:`-lọt (12==12) · vá skill-matrix 15→17 · **+vế-4 `pull-cach`** cho `nhip-no-probe` (fault-inject 7 ca 2 chiều). **Contract v2 chạy runtime LẦN ĐẦU** — và trong CÙNG phiên chạy **cả 2 nhánh lành**: `OK-reachable` (tick 16→17) rồi `squash-benign` (sau closeout). 🔴 **Nấc = `executed-file/verified-pending-restart` — trio CHƯA spawn lần nào.** **Vòng audit anh gật "làm hết": `reviewer` FAIL 1C/4M/4m/1nit + `lead-view` 3 FLAG + `lead-omission` 3 FLAG/3 INFORM → lead re-đo độc-lập từng cái, vá 9/9.** CRITICAL = **E-012/AS-16 `bằng-chứng-tự-huỷ-sau-squash`**: 7 report + email neo sha `wal:` chưa-push, squash phá đúng sha đó ⇒ re-anchor `2757e41` + **errata `e43484cef10f`** gửi hub. Kèm: "TRIPLE" chọn enum DỄ (2 site liệt-kê-TÊN lọt AC4 grep-theo-SỐ) · `TOTAL 46 "==baseline"` sai (ghi=45, live=47) · 4 site khai "NO Write/Edit" như cơ-chế chặn → sửa (nay có **nhân-chứng first-person**: `reviewer` frontmatter 0 Write/Edit mà **đã Write+Edit thật**; **9 vai** không phải 6). **3/3 sub garble #53** cả 3 resume, 0 mất dữ-liệu nhờ ghi-đĩa (sổ đếm ×20/S118 → **×23/S143**). `flag_classes` **10→11** (+`gap-incident-unrecorded` anh duyệt). Test **528 kế-thừa** (0 file `src/`·`fe-*`·`tests/` bị đụng cả 5 commit — verify bằng diff, KHÔNG khai từ trí-nhớ), bundle FROZEN. 5 commit: `2757e41`·`3f4310f`·`36cefcc`·`39cc060`·`c4903dd`.) -**Carry MỞ re-stamp (14):** (1) carry-age design-question `[carry:carry-age-vacuous]` — vacuous-GỐC đã chết @S140, anh xem đóng? (2) hook-vs-budget-cap — **S143 đo tươi: `investigator-codebase` 26.624B vượt soft-cap** (`l1_over_30kb=false`, chưa chạm trần auto-inject); probe đếm `tran-bo-nho 1 over-cap` `[carry:hook-vs-budget-cap]` (3) UAT S117 incognito `[carry:uat-s117]` (4) PE phân-quyền số-phiếu+user `[carry:pe-phanquyen]` (5) BVAAU `[carry:bvaau]` (6) quyết-định Bình/Lê `[carry:binh-le-decision]` (7) Trà/Bùi re-login `[carry:tra-bui-relogin]` (8) account trùng `[carry:account-trung]` (9) UAT freeze-NS S133 `[carry:uat-s133-budget-freeze]` (10) UAT lũy-kế tạm-tính S134 `[carry:uat-s134-luyke]` (11) 🆕 **tools-whitelist KHÔNG chặn ghi runtime** — 6 vai read-only bị append `Write/Edit`; cơ-chế CHƯA kiểm; đã báo hub §3(b), chờ hub xác nhận + anh quyết có hardening không `[carry:tools-whitelist-no-teeth]` (12) 🆕 **ngưỡng `pull_warn_days`** — vế-4 đang INFORM trần, em CỐ Ý không tự đặt số (quyền anh) `[carry:pull-warn-threshold]` (13) 🔁 **session-model adopt** — draft `docs/governance/session-model-se-draft.md`; (a) hub phát mẫu ✅ + (b) khuôn bộ-ba SUB ✅ **đã mở khoá trong wave này**, chỉ còn **(c) anh duyệt bảng VC-1/2/3**; CẤM wire trước (c) `[carry:session-model-adopt]` — 🔴 **carry này BỊ RƠI khỏi khối S143 bản đầu** (khai 13 trong khi đúng là 14; lead-omission-auditor bắt @S143 class `gap-carry-dropped`), nay trả về (14) chờ-HUB reply: `b9ef5ede` (session-model **5 câu**) + `6c94873f` (báo-nấc wave 9/10) + errata `[carry:hub-review-wave-1807]`. ✅ **ĐÓNG @S143 (1):** `[carry:dangling-head]` — hub APPROVE khuôn ①a (`58e28bae`), SE land contract v2 + chạy runtime 1 lần nhánh `OK-reachable`; 3 ca lịch-sử S127/S133/S139 reclassify đúng `squash-benign`. -**🔴 NEXT anh (đánh số — trả lời bằng số):** (1) ⚑ **trio first-run** — cần anh **restart CLI** rồi gật, em chạy spawn-probe 3/3 TUẦN TỰ (đây là thứ duy nhất chặn nấc lên `verified-runtime`); (2) ⚑ ngưỡng `pull_warn_days` — đặt số hay giữ INFORM trần `[carry:pull-warn-threshold]`; (3) ⚑ tools-whitelist no-teeth — chờ hub xác nhận cơ-chế, anh có muốn em thử hardening (hook chặn sub-write path tracked) không `[carry:tools-whitelist-no-teeth]`; (4) UAT lũy-kế tạm-tính S134 `[carry:uat-s134-luyke]`; (5) UAT freeze-NS S133 `[carry:uat-s133-budget-freeze]`; (6) ⚑ D1 nháp-không-cộng-tiền — gật giữ hay đổi (1 predicate); (7) hook-vs-budget-cap — chốt hướng (S143: inv-cb 26.6K over-cap) `[carry:hook-vs-budget-cap]`; (8) session-model — duyệt bảng VC-1/2/3 `[carry:session-model-adopt]`; (9) close-sweep cụm 7-carry đồng-tuổi S117/S118; **⬇️ 3 mục dưới CHÌM từ `spec:68` suốt S141→S143 (0 hit trên cả 4 bề mặt owner — `gap-decision-sunk` @S143), nay kéo lên:** (10) ⚑ **ratify ranh `refine ⟂ lead-omission`** — 🔴 ranh này **đã wire** vào `harness-refine.md:22-30` TRƯỚC khi được anh/hub ratify; anh gật giữ hay sửa ranh; (11) ⚑ **5 câu session-model** (mới mang 1/5 lên bảng): `session_ctx_kb`=64? · map N↔L + folder-form (`sessions/session-` hub vs `S` SE) · VC từng dòng · hook 3→4-path? · prereq unblock — 🔴 `session_ctx_kb` **0 hit** mọi bề mặt dù em khai "❌ treo"; (12) ⚑ **consent-form trio dài hạn** — mỗi phiên gật hay thành luật đứng (hub `58e28bae` Câu-3 đá về SE, câu hỏi còn nguyên cho anh). +**Carry MỞ re-stamp (14):** (1) carry-age design-question `[carry:carry-age-vacuous]` — vacuous-GỐC đã chết @S140, anh xem đóng? (2) hook-vs-budget-cap — **S143 đo tươi: `investigator-codebase` 26.624B vượt soft-cap** (`l1_over_30kb=false`, chưa chạm trần auto-inject); probe đếm `tran-bo-nho 1 over-cap` `[carry:hook-vs-budget-cap]` (3) UAT S117 incognito `[carry:uat-s117]` (4) PE phân-quyền số-phiếu+user `[carry:pe-phanquyen]` (5) BVAAU `[carry:bvaau]` (6) quyết-định Bình/Lê `[carry:binh-le-decision]` (7) Trà/Bùi re-login `[carry:tra-bui-relogin]` (8) account trùng `[carry:account-trung]` (9) UAT freeze-NS S133 `[carry:uat-s133-budget-freeze]` (10) UAT lũy-kế tạm-tính S134 `[carry:uat-s134-luyke]` (11) **tools-whitelist KHÔNG chặn ghi runtime** — **15 vai** read-only bị append `Write/Edit` (đo tươi @S148; "6" là số S143-era **đã stale** — roster phình 3 vai kiểm @S145, KHÔNG phải xấu đi); ✅ **anh QUYẾT @S148: (a) giữ nguyên, KHÔNG hardening** ⇒ carry **THU HẸP** còn DUY-NHẤT vế chờ hub xác nhận cơ-chế §3(b), **KHÔNG còn vế chờ anh** `[carry:tools-whitelist-no-teeth]` (12) 🆕 **ngưỡng `pull_warn_days`** — vế-4 đang INFORM trần, em CỐ Ý không tự đặt số (quyền anh) `[carry:pull-warn-threshold]` (13) 🔁 **session-model adopt** — draft `docs/governance/session-model-se-draft.md`; (a) hub phát mẫu ✅ + (b) khuôn bộ-ba SUB ✅ **đã mở khoá trong wave này**, chỉ còn **(c) anh duyệt bảng VC-1/2/3**; CẤM wire trước (c) `[carry:session-model-adopt]` — 🔴 **carry này BỊ RƠI khỏi khối S143 bản đầu** (khai 13 trong khi đúng là 14; lead-omission-auditor bắt @S143 class `gap-carry-dropped`), nay trả về (14) chờ-HUB reply: `b9ef5ede` (session-model **5 câu**) + `6c94873f` (báo-nấc wave 9/10) + errata `[carry:hub-review-wave-1807]`. ✅ **ĐÓNG @S143 (1):** `[carry:dangling-head]` — hub APPROVE khuôn ①a (`58e28bae`), SE land contract v2 + chạy runtime 1 lần nhánh `OK-reachable`; 3 ca lịch-sử S127/S133/S139 reclassify đúng `squash-benign`. +**🔴 NEXT anh (đánh số — trả lời bằng số):** (1) ~~⚑ trio first-run~~ ✅ **ĐÓNG — đã chạy @S144 (2026-07-22)**: restart ĐÃ xảy ra + bộ-ba chạy THẬT 3/3 TUẦN TỰ, nấc `executed-file`→**`verified-runtime`**; bằng-chứng đĩa `runs/2026-07-22-S144-trio-first-run/` (eval 10.907 B · refine 14.321 B · audit 11.251 B + `trio-synthesis.md`), commit `8b6df0a`. 🔴 **Dòng cũ SỐNG SAI 4 phiên (S144→S147)** vì bảng số này thuộc khối S141–S143 mà các phiên sau bồi thêm ở TRÊN **không re-stamp bảng** ⇒ @S148 lead đọc bảng thay vì đọc đĩa, **trình lại cho anh một việc đã xong** (class `view-stale-status`, lọt vì chưa tới nhịp H24 — light 5/6). Bài: bảng chờ-anh là **view dẫn-xuất**, khẳng-định trạng-thái = phải chạm đĩa 1 lệnh (S121). 🔒 **Số 1 GIỮ CHỖ, CẤM tái-dụng cho việc khác** — anh trả lời bằng số, tái-dụng slot = câu trả lời trúng nhầm việc (vắng-mặt-trông-giống-ổn S146). Vế CÒN MỞ của trio = **#12** (thể-thức consent dài hạn), KHÁC first-run; (2) ~~⚑ ngưỡng `pull_warn_days`~~ ✅ **ANH ĐỔI TRỤC @S148 (2026-07-24)** — *"mỗi session end/start luôn, vì 1 session giờ có thể kéo dài 2-3 phiên qua 2-3 ngày"* ⇒ **KHÔNG đặt ngưỡng-theo-NGÀY**, nhịp neo vào **SỰ-KIỆN PHIÊN**. Đã wire `/check-email all` vào **4 CỬA** (`session-start` BƯỚC 0.9 · `tiep` §2.5b · `session-end` BƯỚC 0.5 · `pause` §2.7), luật canonical 1 nhà ở `check-email.md` §"Nhịp chạy — 4 CỬA PHIÊN" + 4 con-trỏ (B1). 🔴 **Trước đó `check-email` wire 0/4 cửa** — sống hoàn-toàn nhờ lead nhớ chạy tay; đó là gốc ca hub-gửi-giục `2026-07-18-nudge-pull`. **Dogfood @S148: chạy thật, 0 thư mới, fail-soft kích-hoạt THẬT** (repo `dyd` không có trên máy → bỏ qua, không nổ). 🔒 `pull_warn_days` **CỐ Ý để TRỐNG** — đổi vai thành **chỉ-báo sức-khoẻ nghi-thức** (số leo trong khi phiên vẫn mở/đóng đều ⇒ nghi-thức bị chạy tắt). Khai giới-hạn: vế-4 mạnh với im-DÀI, yếu với trễ-kéo-NGẮN (ca 07-18 chỉ N=2 bắt được, mà N=2 = kêu liên-tục) `[carry:pull-warn-threshold]`; (3) ~~⚑ tools-whitelist no-teeth~~ ✅ **ANH CHỐT (a) GIỮ NGUYÊN @S148 (2026-07-24)** — **KHÔNG** hardening (không thêm hook `PreToolUse` chặn sub-write vào path tracked). ⇒ whitelist `tools:` trong frontmatter = **tài-liệu mô-tả ý-định, KHÔNG phải hàng-rào**; backstop THẬT vẫn là **git-diff commit-gate** (bắt SAU khi ghi, KHÔNG chặn TRƯỚC — khai thẳng, đừng đọc thành "đã kín"). 🔴 **Số đo tươi @S148 = 15 vai** (KHÔNG phải "6" của dòng carry cũ, cũng không phải "9" @S143): 20 vai trên đĩa − 5 write-specialist = **15 vai khai `tools:` KHÔNG Write/Edit mà runtime cấp Write+Edit cả 15** (chứng 2 chiều: `reviewer.md:7` frontmatter 0 Write/Edit ⟂ registry phiên S148 có `Write, Edit`; `harness-eval` mô-tả tự viết "KHÔNG Write/Edit" vẫn bị cấp). Số phình **theo roster lớn lên**, KHÔNG phải tình-hình xấu đi. **CÒN MỞ 1 vế:** chờ hub xác nhận cơ-chế này có cố-ý không (đã báo §3(b)) `[carry:tools-whitelist-no-teeth]`; (4) UAT lũy-kế tạm-tính S134 `[carry:uat-s134-luyke]`; (5) UAT freeze-NS S133 `[carry:uat-s133-budget-freeze]`; (6) ⚑ D1 nháp-không-cộng-tiền — gật giữ hay đổi (1 predicate); (7) hook-vs-budget-cap — chốt hướng (S143: inv-cb 26.6K over-cap) `[carry:hook-vs-budget-cap]`; (8) session-model — duyệt bảng VC-1/2/3 `[carry:session-model-adopt]`; (9) close-sweep cụm 7-carry đồng-tuổi S117/S118; **⬇️ 3 mục dưới CHÌM từ `spec:68` suốt S141→S143 (0 hit trên cả 4 bề mặt owner — `gap-decision-sunk` @S143), nay kéo lên:** (10) ⚑ **ratify ranh `refine ⟂ lead-omission`** — 🔴 ranh này **đã wire** vào `harness-refine.md:22-30` TRƯỚC khi được anh/hub ratify; anh gật giữ hay sửa ranh; (11) ⚑ **5 câu session-model** (mới mang 1/5 lên bảng): `session_ctx_kb`=64? · map N↔L + folder-form (`sessions/session-` hub vs `S` SE) · VC từng dòng · hook 3→4-path? · prereq unblock — 🔴 `session_ctx_kb` **0 hit** mọi bề mặt dù em khai "❌ treo"; (12) ⚑ **consent-form trio dài hạn** — mỗi phiên gật hay thành luật đứng (hub `58e28bae` Câu-3 đá về SE, câu hỏi còn nguyên cho anh). **⬇️ 4 mục MỚI thêm @S148 — (13) từ UAT S147, (14)–(16) lòi ra khi đọc 10 thư fan-out tồn đọng:** (13) **UAT nghiệm-thu ① S147** — bạn kia mở phiếu **Trả lại** → màn "Danh sách" → "Bảng so sánh giá" → nút X + tải lên hiện lại cho người soạn; **chưa có tin**; (14) ⚑ **nhãn model lead (O-5, thư `goi-chot` khoản 1)** — `~/.claude/settings.json:11` = `claude-opus-4-8[1m]` **từ 07-20**, nhưng ô canonical [`STATUS.md`](STATUS.md) §Sub-agents vẫn kể **"Fable 5"** là lead ⇒ 2 mặt lệch nhau 4 ngày; **nhãn = quyền anh, em không tự đoán**; (15) ⚑ **ngưỡng "điểm-dừng phải RẺ" (thư `goi-chot` khoản 3 — ràng-buộc BINDING)** — hub chốt luật nhưng **số là của anh**; em đã đo hộ @S148: **dò ~5ms · kéo ~200–500ms/thư** ⇒ anh đặt mốc bao nhiêu thì gọi là "đắt"; (16) ⚑ **khuôn nhãn spawn 4 thành-phần — ĐẾN HẠN** (`C14` xếp 1.3/1.4 `DEFER — áp từ phiên kế`; S144 là phiên trước ⇒ **S148 là phiên đến hạn**), hiện mới phủ **1/4** loại spawn (mới cổng review; còn nhịp-2-đầu-phiên · việc-thường · workflow-task). **NEXT em (phiên kế):** 🔴 **KHÔNG watch `broadcasts/inbox` nữa** — inbox chỉ có thư ĐÃ kéo nên nó mù với thư chưa kéo (đúng lỗi S143); thay bằng **đọc vế-4 `pull-cach` trong dòng-nợ** rồi `/check-email` khi số tăng bất thường. Trio first-run ngay sau anh restart+gật; **H24 deep tới hạn** (S143 đã chạy sớm 1 nhịp theo direction anh — xem kết-quả trong log phiên); monthly combined audit **2026-08-01** (kèm full re-sync measured-block); MFE GOODHART-WARN strikes 21→23 theo dõi; nếu anh đổi D1 → predicate `ComputePendingAsync` + test C3 flip + deploy. **Prev S140:** 2026-07-18 (S140 — **Direction owner "đóng đủ 5 vòng + session-model" → 5 vòng ĐÓNG phần SE tự chủ TRONG 1 PHIÊN — governance-only: 0 prod-code/0 mig, test 528 giữ ×2, bundle FROZEN `BVCukFP5`/`DoxyEOxh`, lead Fable 5 owner-choice.** Vòng-2 trả nợ force-fire (cặp H24 light#3 @counter14) · Vòng-4 sleep 5-gist 183KB→69KB `aa88d01` (Fidelity-gate CÓ RĂNG 4P+1F→re-distill; sleep-INFORM chuỗi-3 GIẢI) · Vòng-5 `624c378d` executed-runtime (probe 5-nhãn) · Phép-8 đo LẦN ĐẦU fleet (FAIL-as-written/PASS-with-tailoring-DP-3) · Session-model draft VC-1/2/3 + email hub `b9ef5ede5679` (gate GO-WITH-FIXES; 4 câu hỏi + nhắc wave-1807) · Hở duy nhất = khuôn bộ-ba SUB vòng-3 chờ hub. H2 GATE-PASS-0 + H1 PASS_WITH_FLAGS/3-đều-xử + §L.a 0-RCA. → log `2026-07-18-1300-S140-nam-vong-dong-session-model.md`.) **Carry MỞ re-stamp (13):** (1) carry-age design-question `[carry:carry-age-vacuous]` — 🎯 S140: carry-age FIRE THẬT lần đầu (hook streak 9) ⇒ vacuous-GỐC chết hẳn, anh xem đóng carry này? (2) hook-cap cụm D6 — S140: harvest+tooling TỰ-compact về dưới target (19.7→14.9K · 18.1→15.9K md5-verified); còn inv-cb/cicd/test-spec theo dõi `[carry:hook-vs-budget-cap]` (3) UAT S117 incognito `[carry:uat-s117]` (4) PE phân-quyền số-phiếu+user `[carry:pe-phanquyen]` (5) BVAAU `[carry:bvaau]` (6) quyết-định Bình/Lê `[carry:binh-le-decision]` (7) Trà/Bùi re-login `[carry:tra-bui-relogin]` (8) account trùng `[carry:account-trung]` (9) UAT freeze-NS S133 `[carry:uat-s133-budget-freeze]` (10) UAT lũy-kế tạm-tính S134 `[carry:uat-s134-luyke]` (11) dangling-head — anh chọn 1a @S139, chờ HUB khuôn chung `[carry:dangling-head]` (12) chờ-HUB reply wave 18-07 — NAY 2 email chờ: 3-khoản cũ (`21b71a1e`+`fc2b218d`) + **5-vòng/session-model MỚI (`b9ef5ede` — 4 câu hỏi mẫu áp dụng + phép-8 data-point)** `[carry:hub-review-wave-1807]` (13) 🆕 **session-model adopt** — draft `docs/governance/session-model-se-draft.md` (nhãn 2-tầng + bảng VC-1/2/3) chờ: (a) hub phát mẫu, (b) khuôn bộ-ba SUB, (c) anh duyệt bảng VC; CẤM wire trước đủ 3 điều kiện `[carry:session-model-adopt]`. diff --git a/scripts/session_ctx.py b/scripts/session_ctx.py new file mode 100644 index 0000000..4c34a4f --- /dev/null +++ b/scripts/session_ctx.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +"""session_ctx.py -- session-model derive helpers (SE port, S148 2026-07-24). + +SE PORT of AI_INFRA scripts/session_ctx.py -- 🔴 TRIMMED ON PURPOSE, KHAI RO: +hub's original (~26 KB) also carries jsonl transcript parsing, anh-message containment +verification, token-overhead accounting and a context-cap getter. SE ports only the TWO +capabilities its rituals actually call: + + machine-block -> derive {anchor, changed_files, run_id} for /snapshot + /pause + secrets-sweep -> pre-commit Category-5 gate over a session dir + +NOT ported (do NOT claim these exist here): verify-containment / overhead / cap-getter / +window-jsonl resolution. Porting them without a caller would be ghost-wire -- the exact +class SE keeps getting bitten by. Add one ONLY when a ritual actually needs it. + +Fidelity pin: field semantics + CLI shape follow hub so a future hub change can be +re-pulled by diff. Behaviour differences from hub are marked `SE-DELTA`. + +stdlib-only . Python 3.11+ . UTF-8 no-BOM . LF . ASCII-only source. +""" +from __future__ import annotations + +import argparse +import datetime +import json +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +DEFAULT_SESSIONS_ROOT = ROOT / ".claude" / "sessions" + +# Commits authored by the Stop-hook (wal-flush.ps1) -- never a valid anchor. +_WAL_SUBJECT_RE = re.compile(r'^wal:') +# runId as written into WAL by the hmw run-trace convention. +_RUNID_RE = re.compile(r'\b(wf_[A-Za-z0-9_-]+)') + +# Category-5 secret prefixes. ACCEPTED-GAP: pattern-bounded, token-anchored -- this is a +# conservative pre-commit BLOCK, NOT a proof of cleanliness (a DB password in an anh-msg +# verbatim would pass). Say so; do not sell it as complete. +SECRET_PATTERNS = ('voyage', 'sk-', 'gitea_pat', 'cfut_') +_SECRET_RES = [(p, re.compile(r'\b' + re.escape(p), re.I)) for p in SECRET_PATTERNS] + + +# --------------------------------------------------------------------------- +# git helpers +# --------------------------------------------------------------------------- +def _git(args, repo_root): + """Run `git -C `; return stdout. Fail-loud: a bad range is a real + error for a derive that must be reproducible.""" + try: + r = subprocess.run(['git', '-C', str(repo_root)] + list(args), + capture_output=True, text=True, encoding='utf-8', errors='replace') + except (OSError, subprocess.SubprocessError) as e: + raise SystemExit(f"[session_ctx] git failed to launch: {e}") + if r.returncode != 0: + raise SystemExit(f"[session_ctx] git {' '.join(args)} -> rc={r.returncode}: {r.stderr.strip()}") + return r.stdout + + +def _parse_log_line(line): + parts = line.split('\x1f') + return {'sha': parts[0], 'subject': parts[1], 'ts': parts[2]} if len(parts) == 3 else None + + +def find_anchor(repo_root, max_scan=100): + """Nearest NON-wal named commit walking git log from HEAD -> {sha, subject, ts} or None. + + Why not HEAD: the Stop-hook auto-commits `wal:` at every turn boundary, and closeout + squash rewrites those shas. Anchoring on a named commit survives the rewrite; anchoring + on HEAD rots (sha AND subject/ts both move).""" + out = _git(['log', f'-n{max_scan}', '--format=%H%x1f%s%x1f%cI'], repo_root) + for line in out.splitlines(): + rec = _parse_log_line(line) + if rec is None or _WAL_SUBJECT_RE.match(rec['subject']): + continue + return rec + return None + + +def _resolve_ref(ref, repo_root): + """Resolve an explicit anchor ref (sha/tag/HEAD~n) to {sha, subject, ts}.""" + out = _git(['log', '-n1', '--format=%H%x1f%s%x1f%cI', ref], repo_root).strip() + return _parse_log_line(out.splitlines()[0]) if out else None + + +def _iso_utc(epoch): + return datetime.datetime.fromtimestamp(epoch, datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%S') + + +def last_snapshot_mtime(session_dir): + """mtime of the most recent _snapshot-* file (fallback --since anchor). None if absent.""" + snaps = [s for s in Path(session_dir).glob('_snapshot-*') if s.is_file()] + return max(s.stat().st_mtime for s in snaps) if snaps else None + + +def live_run_id(repo_root=None, wal_path=None): + """Live runId from .claude/WAL.md (LAST match = most-recently-appended). None if absent.""" + wal = Path(wal_path) if wal_path else (Path(repo_root or ROOT) / ".claude" / "WAL.md") + if not wal.exists(): + return None + matches = _RUNID_RE.findall(wal.read_text(encoding='utf-8', errors='replace')) + return matches[-1] if matches else None + + +# --------------------------------------------------------------------------- +# (a) machine-block +# --------------------------------------------------------------------------- +def machine_block(session_n, sessions_root=None, repo_root=None, anchor=None): + """Derive {session, anchor{subject,ts}, changed_files, changed_count, run_id}. + + `anchor` = explicit ref override (pin it for reproducibility); default = nearest non-wal. + Anchor is identified by {subject, ts}, NOT sha -- sha does not survive closeout squash.""" + repo_root = Path(repo_root or ROOT) + sessions_root = Path(sessions_root) if sessions_root else DEFAULT_SESSIONS_ROOT + session_dir = sessions_root / f"session-{session_n}" + + a = _resolve_ref(anchor, repo_root) if anchor is not None else find_anchor(repo_root) + used_fallback = False + if a is not None: + files_out = _git(['diff', '--name-only', f'{a["sha"]}..HEAD'], repo_root) + anchor_id = {'subject': a['subject'], 'ts': a['ts']} + else: + used_fallback = True + mt = last_snapshot_mtime(session_dir) + files_out = (_git(['log', f'--since={_iso_utc(mt)}', '--name-only', '--format='], repo_root) + if mt is not None else '') + anchor_id = {'subject': None, 'ts': None} + + changed = sorted({f.strip() for f in files_out.splitlines() if f.strip()}) + return { + 'session': session_n, + 'anchor': anchor_id, + 'anchor_from_fallback_since': used_fallback, + 'changed_files': changed, + 'changed_count': len(changed), + 'run_id': live_run_id(repo_root), + } + + +def render_machine_block(mb): + """Deterministic text rendering for embedding into a _context FLOW entry.""" + a = mb['anchor'] + lines = [''] + lines.append(f'anchor: "{a["subject"]}" @ {a["ts"]}' if a['subject'] is not None + else 'anchor: (fallback --since mtime of last _snapshot-*)') + lines.append(f'runId: {mb["run_id"] or "(none)"}') + lines.append(f'changed-files ({mb["changed_count"]}, git diff --name-only anchor..HEAD):') + lines.extend(f' {f}' for f in mb['changed_files']) + return '\n'.join(lines) + + +# --------------------------------------------------------------------------- +# (b) secrets-sweep +# --------------------------------------------------------------------------- +def secrets_sweep(session_dir): + """Scan the WHOLE write-set of a session dir for Category-5 patterns. + Returns list of hits {file, line, pattern, snippet}. See ACCEPTED-GAP at module top.""" + session_dir = Path(session_dir) + hits = [] + if not session_dir.exists(): + return hits + for p in sorted(session_dir.rglob('*')): + if not p.is_file(): + continue + try: + text = p.read_text(encoding='utf-8', errors='replace') + except OSError: + continue + for lineno, line in enumerate(text.splitlines(), 1): + for pat, rx in _SECRET_RES: + if rx.search(line): + hits.append({'file': str(p), 'line': lineno, 'pattern': pat, + 'snippet': line.strip()[:120]}) + return hits + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- +def main(argv=None): + ap = argparse.ArgumentParser(prog='session_ctx.py', + description='session-model derive helpers (SE port, trimmed).') + sub = ap.add_subparsers(dest='cmd', required=True) + + mb = sub.add_parser('machine-block', help='derive anchor + changed-files + runId') + mb.add_argument('--session', type=int, required=True, metavar='N') + mb.add_argument('--anchor', default=None, help='explicit anchor ref (default: nearest non-wal)') + mb.add_argument('--json', action='store_true', help='emit JSON (default: rendered text)') + + ss = sub.add_parser('secrets-sweep', help='Category-5 pre-commit gate over a session dir') + ss.add_argument('--session', type=int, required=True, metavar='N') + + args = ap.parse_args(argv) + + if args.cmd == 'machine-block': + block = machine_block(args.session, anchor=args.anchor) + print(json.dumps(block, ensure_ascii=False, indent=2) if args.json + else render_machine_block(block)) + return 0 + + if args.cmd == 'secrets-sweep': + session_dir = DEFAULT_SESSIONS_ROOT / f"session-{args.session}" + hits = secrets_sweep(session_dir) + if not hits: + print(f"[secrets-sweep] session-{args.session}: 0 hit " + f"({len(SECRET_PATTERNS)} pattern, pattern-bounded -- KHONG phai chung-minh sach)") + return 0 + # SE-DELTA: hub prints hits then exits 1; same contract, message localised. + print(f"[secrets-sweep] session-{args.session}: {len(hits)} HIT -- CHAN commit", file=sys.stderr) + for h in hits: + print(f" {h['file']}:{h['line']} [{h['pattern']}] {h['snippet']}", file=sys.stderr) + return 1 + + return 2 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/scripts/session_scaffold.py b/scripts/session_scaffold.py new file mode 100644 index 0000000..4ff0341 --- /dev/null +++ b/scripts/session_scaffold.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""session_scaffold.py -- scaffold 1 logic-session folder + _context skeleton. + +SE PORT of AI_INFRA scripts/session_scaffold.py (adopted S148, 2026-07-24, owner +"doi ung dung chinh xac nhu hub"). Near-verbatim: the logic is repo-agnostic; only +the template CONTENT differs (SE STOCK-map rows). Kept stdlib-only + same CLI so a +future hub change can be re-pulled by diff instead of re-derived. + +Tach LOGIC-session khoi VAT-LY window: tao `.claude/sessions/session-/` + +`_context-s-.md` tu template `.claude/templates/session-context-template.md`. + +Design pins: + - path HARD-PIN: `.claude/sessions/session-/` (KHONG configurable -- uniform cross-repo). + - ts-moc = `git log -1 --format=%cI` (committer ISO-8601 cua HEAD), KHONG datetime.now + tuy-tien; nguon-ts ghi thang vao file (audit). Fallback datetime.now CHI khi git down, + tag ro "FALLBACK". + - Idempotent: folder ton tai -> bao + KHONG de `_context` (chi tao neu thieu). + - Cap so KHONG phai viec cua script nay -- caller truyen vao. Doc-quyen cap so = + /session-start (session-start.md BUOC 0.8); /pause va /tiep chi tao folder cho dang mo. + +stdlib-only . Python 3.11+ . UTF-8 no-BOM . LF. +""" +from __future__ import annotations + +import argparse +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path + +SESSIONS_SUBPATH: tuple[str, ...] = (".claude", "sessions") +TEMPLATE_SUBPATH: tuple[str, ...] = (".claude", "templates", "session-context-template.md") + + +def repo_root() -> Path: + """Repo root via `git rev-parse --show-toplevel`; fallback = parent-of-scripts/.""" + try: + out = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, text=True, check=True, + ) + root = out.stdout.strip() + if root: + return Path(root) + except (subprocess.CalledProcessError, FileNotFoundError, OSError): + pass + return Path(__file__).resolve().parent.parent + + +def head_ts() -> tuple[str, str]: + """Return (ts, source). Prefer HEAD committer ISO-8601, tagged with short-sha for audit.""" + try: + out = subprocess.run( + ["git", "log", "-1", "--format=%cI%x09%h"], + capture_output=True, text=True, check=True, + ).stdout.strip() + if out: + ts, _, sha = out.partition("\t") + ts, sha = ts.strip(), sha.strip() + if ts: + return ts, f"git log -1 --format=%cI @ HEAD {sha}" + except (subprocess.CalledProcessError, FileNotFoundError, OSError): + pass + return ( + datetime.now(timezone.utc).isoformat(timespec="seconds"), + "datetime.now(UTC) FALLBACK (git unavailable -- non-deterministic)", + ) + + +def render(template_text: str, n: int, ts: str, ts_source: str) -> str: + """Fill placeholders with str.replace (NOT str.format) so literal `{`/`}` in the + template's JSON schema example survive untouched.""" + return ( + template_text + .replace("{{N}}", str(n)) + .replace("{{TS}}", ts) + .replace("{{TS_SOURCE}}", ts_source) + ) + + +def _rel(p: Path, root: Path) -> str: + try: + return p.relative_to(root).as_posix() + except ValueError: + return str(p) + + +def main(argv: list[str] | None = None) -> int: + ap = argparse.ArgumentParser( + prog="session_scaffold.py", + description="Scaffold 1 logic-session folder + _context skeleton " + "(.claude/sessions/session-/). Idempotent; safe to re-run.", + ) + ap.add_argument("--session", type=int, required=True, metavar="N", + help="logic-session ID N (folder = session-, regex ^session-\\d+$)") + ap.add_argument("--dry-run", action="store_true", help="print planned actions, write NOTHING") + args = ap.parse_args(argv) + + n: int = args.session + if n < 0: + print(f"ERROR: --session must be a non-negative integer (got {n})", file=sys.stderr) + return 2 + + root = repo_root() + session_dir = root.joinpath(*SESSIONS_SUBPATH) / f"session-{n}" + context_path = session_dir / f"_context-s-{n}.md" + template_path = root.joinpath(*TEMPLATE_SUBPATH) + + if not template_path.is_file(): + print(f"ERROR: template not found: {template_path}", file=sys.stderr) + return 2 + + ts, ts_source = head_ts() + folder_exists = session_dir.exists() + context_exists = context_path.exists() + + if args.dry_run: + print(f"[dry-run] session N = {n}") + print(f"[dry-run] ts (moc) = {ts}") + print(f"[dry-run] ts-source = {ts_source}") + print(f"[dry-run] template = {_rel(template_path, root)}") + print(f"[dry-run] session folder = {_rel(session_dir, root)} " + f"({'EXISTS' if folder_exists else 'would mkdir'})") + print(f"[dry-run] context file = {_rel(context_path, root)} " + f"({'EXISTS -> would SKIP (idempotent)' if context_exists else 'would write'})") + print("[dry-run] NOTHING written.") + return 0 + + created_folder = False + if not folder_exists: + session_dir.mkdir(parents=True, exist_ok=True) + created_folder = True + + if context_exists: + print(f"[idempotent] {_rel(context_path, root)} EXISTS -- NOT overwriting.") + if created_folder: + print(f"[note] folder {_rel(session_dir, root)} was missing -> created " + f"(unusual: context present without folder).") + return 0 + + template_text = template_path.read_text(encoding="utf-8") + rendered = render(template_text, n, ts, ts_source) + with open(context_path, "w", encoding="utf-8", newline="\n") as fh: + fh.write(rendered) + + print(f"[created] folder = {_rel(session_dir, root)} ({'new' if created_folder else 'existed'})") + print(f"[created] context = {_rel(context_path, root)}") + print(f"[created] ts(moc) = {ts} (source: {ts_source})") + print("[note] NOT committed (commit theo nghi-thuc /pause | /snapshot | /session-end).") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())