diff --git a/.claude/commands/fable-real.md b/.claude/commands/fable-real.md index 5d1c6c5..ddb7911 100644 --- a/.claude/commands/fable-real.md +++ b/.claude/commands/fable-real.md @@ -28,7 +28,7 @@ Mỗi lần chạy engine-đắt PHẢI kết-tinh 1 spec theo pipeline **propos 1. **Engine propose-only** — vai chạy top-model đề-xuất [đề-bài · cách làm · checklist]. Engine KHÔNG tự ghi. 2. **Lead verify** — lead soi + chỉnh nội-dung propose. 3. **Lead ghi** file `.claude/workflows/runs//spec--.md` — **3 heading cố-định:** ① Tính-năng/đề-bài · ② Cách implement · ③ Checklist (vai + deliverable + acceptance **ĐO ĐƯỢC**). -4. **Worker thực-thi THEO spec** — spec bơm **QUA ARGS/prompt** (worker KHÔNG tự Read file). +4. **Worker thực-thi THEO spec** — spec bơm qua args = **CON-TRỎ** file spec (`runs//spec-*.md`, bản lead-đã-verify) + digest ngắn / AC-ids; worker Read **ĐÚNG file con-trỏ** (B6 chốt-2 args-mỏng); CẤM tự lục **spec/nguồn** ngoài con-trỏ (file làm-việc của lane vẫn Read bình thường). - 🔒 **Honest-note (c):** file spec do **LEAD ghi SAU khi verify** — engine KHÔNG tự ghi. - 🔒 **Honest-note (d):** run **chấm/verify CŨNG sinh spec-file** (kế-hoạch xử issue = "cách làm" cần kết-tinh). @@ -40,6 +40,7 @@ Mỗi lần chạy engine-đắt PHẢI kết-tinh 1 spec theo pipeline **propos ## 🔴 Cờ `args.run` — RUN-TRACE (Sàn-1 H22, cơ-khí-hoá S121 W2) Lead **PHẢI** @P1 scaffold `.claude/workflows/runs//` (TRACKED, kèm `run.md` + `## taskList snapshot`) rồi truyền `args.run = { name:'', dir:'.claude/workflows/runs/' }` khi gọi Workflow `hmw`. 🔸 **Khai thật — ở lệnh NÀY gate hiếm khi cắn:** `/fable-real` = **single deep-pass 1 lane** ⇒ `taskList.length = 1` < 3 ⇒ `hmw.js` **KHÔNG throw**. Nhưng cờ vẫn **PHẢI** truyền, vì §H21① buộc run này **kết-tinh spec-file** vào `runs//` ⇒ cần run-folder làm **dấu-vết artifact** cho `/tiep` + harvest; và nếu có lúc lệnh này fan-out ≥3 thì gate cắn ngay. Đừng đọc "gate không cắn" thành "khỏi cần cờ". +> 🔴 **LUẬT non-JSON-args (per `6c2eb2b1` — B6 chốt-3):** `args` (kể cả `args.run`, `args.taskList`, `args.spec`) LUÔN là **object JSON thật** khi gọi Workflow `hmw` — lead **KHÔNG** tự `JSON.stringify` args. Dòng normalize trong `hmw.js` (`typeof args === 'string' ? JSON.parse(args)`) chỉ là **LƯỚI-CUỐI** đỡ ca tầng-máy tự stringify object, **KHÔNG phải giấy phép** để lead tự stringify args khi truyền. ## 🔴 Guard (§K engine — bất-biến) - **Single deep-pass RETURN-only**; em-main **single-writer** (B3) ghi **1 MEMORY entry** đúng vai đó + **gắn nhãn engine đã dùng** (hmw SCHEMA `memoryDelta` KHÔNG field engine → em-main stamp @P3 harvest). diff --git a/.claude/commands/sleep-recovery-memory-l2.md b/.claude/commands/sleep-recovery-memory-l2.md index 1bbc3d7..5fdd358 100644 --- a/.claude/commands/sleep-recovery-memory-l2.md +++ b/.claude/commands/sleep-recovery-memory-l2.md @@ -49,7 +49,7 @@ Hiện-hành = `investigator-codebase · investigator-api · implementer-backend | Đường | Cơ chế | |---|---| | **Tay** | Anh gõ `/sleep-recovery-memory-l2 ` bất kỳ lúc nào | -| **Auto-check (INFORM-only)** | `/session-start` + `/session-end` đọc `last_sleep_at` trong `.claude/agent-memory/memory-budget.json` → nếu `today − last_sleep_at ≥ 7` (ngày) → **gợi-ý** chạy command (INFORM anh, **KHÔNG tự-chạy autonomous**). Anh consent → chạy. | +| **Auto-check (INFORM-only)** | `/session-start` + `/session-end` đọc `last_sleep_at` trong `.claude/agent-memory/memory-budget.json` → nếu `today − last_sleep_at ≥ 7` (ngày) → **gợi-ý** chạy command (INFORM anh, **KHÔNG tự-chạy autonomous**). Anh consent → chạy. 🔸 **Kèm hiển-thị byte (M-10 / DP-5):** khi INFORM fire, 2 site runtime (`/session-start §2.1.2` + `/session-end §L.b(c)`) in kèm **tổng byte `archive/.md` chưa có `.gist.md`** (match PREFIX kỳ; baseline 17-07: kỳ `2026-07` ≈ 183KB/5 file) để anh nhìn số **rồi tự đặt ngưỡng byte** nếu muốn chuyển hẳn (không bịa số) — **INFORM 7-ngày GIỮ** (quyền owner cục-bộ, B1 §3.4; cấm-là-cấm-hub-phát-chuẩn). ⚠️ Việc **WIRE** byte nằm ở 2 site đó (**LANE-A**), **KHÔNG** ở command này — dòng này chỉ là **MÔ-TẢ** (tránh ghost-wire "defined-but-unread", H18). | > 🔴 **State-file `last_sleep_at` có 1 home DUY NHẤT:** `.claude/agent-memory/memory-budget.json` (root-key `last_sleep_at`, cạnh `tiers`/`measured`). KHÔNG ghi ở `_INDEX.md` / nơi khác. *(SE đặt budget-file trong `agent-memory/`, KHÁC AI_INFRA gốc đặt `.claude/memory-budget.json` — đây là tailor path SE.)* Field `last_sleep_at` **đã thêm** (null baseline, S72) vào budget-file SE — auto-check **đã wired** ở `/session-start §2.1.2` + `/session-end §L.b(c)` (đọc field → INFORM gợi-ý nếu null/≥7d). Lần sleep đầu lead set `= today` (Phase 4.4). Lead = single-writer field này (B3). @@ -74,7 +74,7 @@ Spawn `harvest-curator` đọc từng verbatim file target → đề-xuất **b Lead distill proposal thành gist theo 4 bước: 1. **4-field giữ-khung (SE):** mỗi entry gốc = spawn-record **`VIỆC · KẾT-LUẬN(+commit/file:line) · BÀI-HỌC · BẤT-NGỜ`**. Nén = rút-gọn câu-chữ, **KHÔNG bỏ field**. *(Đây là form SE — tương-đương `task · verdict · learned · surprise` bản gốc.)* 2. **GỘP:** cụm entry cùng-chủ-đề → 1 entry tổng-hợp (cite ngày/session gốc + **pointer back-resolve** vào verbatim). -3. **reflection-synthesis:** rút **3 tới 5 entry mỗi file** (meta-insight cấp cao hơn liệt-kê thô), KHÔNG vượt 5. +3. **reflection-synthesis (đặt ĐẦU tệp, viết TRƯỚC chi-tiết):** rút **3 tới 5 ý tổng-hợp mỗi file** (meta-insight cấp cao hơn liệt-kê thô, KHÔNG vượt 5) → đặt khối **TỔNG-HỢP 3–5 ý ở ĐẦU gist**, viết **TRƯỚC** phần chi-tiết/phụ-lục (đọc-nhanh thấy meta-insight trước, không phải lội hết liệt-kê thô). 🔴 **grep-ngược:** mọi **số/mốc** (ngày · S-nnn · Mig-N · commit-SHA · gotcha#) nêu trong khối tổng-hợp **PHẢI** grep-ngược được về **phụ-lục/nguyên-bản** (trong CÙNG gist hoặc verbatim nguồn) — tổng-hợp KHÔNG chứa số/mốc không truy được về nguồn (chống bịa-số ở lớp meta). 4. **importance-tag drop:** **drop `thấp` TRƯỚC** khi cần cắt độ-dài. `cao`/`vừa` giữ. ratio nén = **báo-cáo KHÔNG phải target** (KHÔNG cắt để đạt con-số). 🔴 **Pointer-style SE (khớp gist hiện-có):** mỗi dòng kết bằng back-resolve `→ substring:""` grep-UNIQUE vào verbatim file đã-tên — git-SHA / Mig-name / Run#NNN / unique-phrase keyed (ngày bị collide). **NO line-hint** (additive append làm xê dòng). @@ -88,6 +88,7 @@ Lead distill proposal thành gist theo 4 bước: **(a) coverage-diff DETERMINISTIC (lead scan, rẻ, LUÔN chạy):** - Mọi **`{surprise · guard · file:line · root-cause · gotcha#}`** xuất-hiện trong verbatim **PHẢI** xuất-hiện (hoặc đánh `N/A` có-chủ-đích) trong gist. *(SE thêm `gotcha#` so với 4-token gốc — khớp rule `memory-budget.json` l2_gist: "every surprise/guard/file:line/root-cause/gotcha# in verbatim must survive".)* - Cơ-chế: grep các loại token trên ↔ đối-chiếu gist (Grep tool). Thiếu bất-kỳ → **FAIL** → bổ vào gist TRƯỚC khi tiếp. +- 🔴 **cross-file bleed guard (nén NHIỀU tệp cùng lượt):** khi 1 lượt xử >1 verbatim (vd `all`, hoặc nhiều `.md` của cùng agent) → mỗi token trích-dẫn trong một gist **PHẢI** grep về **ĐÚNG verbatim CỦA TỆP ĐÓ** — token của tệp A **KHÔNG** được trôi sang gist tệp B. Cơ-chế: coverage-diff chạy **per-file đóng-kín** (gist kỳ X của agent Y ↔ **CHỈ** verbatim kỳ X của agent Y), KHÔNG gộp chung 1 pool token liên-tệp. Bleed = **FAIL** (signal gán nhầm nguồn còn tệ hơn thiếu signal — đọc-lại sẽ tin sai xuất-xứ). - **ratio nén = báo-cáo** (đo để biết), KHÔNG phải target/floor — KHÔNG ép gist nhỏ-đi để đạt ratio đẹp. **(b) Fidelity gate (reviewer escalate):** @@ -98,6 +99,7 @@ Lead distill proposal thành gist theo 4 bước: 1. **Tool-ghi:** `Write` (file mới) / `Edit` — 🔴 **G-009: KHÔNG** PowerShell `Add-Content` / bash here-string. UTF-8 no-BOM. 2. Ghi `archive/.gist.md` (file MỚI). **KHÔNG** chạm `.md` verbatim (additive). + - 🔴 **assert additive per-file (n-9, chuẩn `291550ff` hai-assert):** sau ghi, mọi verbatim nguồn phải **byte-identical trước/sau** — `git diff --stat` trên các `.md` verbatim = **RỖNG** (chỉ `.gist.md` MỚI + `memory-budget.json` `last_sleep_at` được phép hiện trong diff). Hai-assert = (1) verbatim byte-identical (chỗ này) + (2) gist coverage-diff PASS (Phase 3a). Verbatim khác 1 byte → **DỪNG**, điều-tra (đã lỡ đè nguồn = vi-phạm B3/§F1). 3. **G-009 post-scan (BẮT BUỘC sau ghi):** Grep mojibake (`§` · `â†` · `ðŸ` · `KHÃ` — seq cụ-thể, né bare-`Ã`) + dollar-expansion (`/usr/bin/bash`) trên file vừa ghi → **phải sạch**. Bẩn → de-corrupt qua Write tool. 4. Cập-nhật `.claude/agent-memory/memory-budget.json` field `last_sleep_at = ` (root-key; **thêm mới nếu chưa có**). B3 single-writer, Write/Edit. 🔴 KHÔNG hand-edit `measured_bytes` (chỉ script `scripts/measure-agent-memory.ps1` được sửa số đo) — chỉ chạm `last_sleep_at`. diff --git a/.claude/governance/.session-counter.json b/.claude/governance/.session-counter.json index 0a61faf..f174ffb 100644 --- a/.claude/governance/.session-counter.json +++ b/.claude/governance/.session-counter.json @@ -10,14 +10,14 @@ "writer": "Lead is single-writer. Wired at /session-start (W3 owns the ritual; W2 owns this file + its shape)." }, "_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": 9, - "last_ticked_session": "S135", - "last_ticked_head": "a4ac81a7718e7b8841aa2f35282dc7856dfadd69", + "counter": 10, + "last_ticked_session": "S136", + "last_ticked_head": "a7bafd5575d4d66b4a39ca7d26d1bb8f8b9a82f8", "last_ticked_at": "2026-07-17", "last_audit": { "light_at_counter": 3, "deep_at_counter": 3, - "_note": "Counter value at which the last light/deep self-audit actually ran. S124 (2026-07-15): FIRST-EVER real run of both H24 vai (owner-triggered dogfood at counter=3) — full 2-vai coverage (view + omission), 0-garble. Credited as BOTH light+deep (thorough audit) so next light~counter 9, next deep~counter 18. lead-view: 9 flag; lead-omission: 4 flag+1 INFORM (carry-age structurally vacuous — escalated owner)." + "_note": "Counter value at which the last light/deep self-audit actually ran. S124 (2026-07-15): FIRST-EVER real run of both H24 vai (owner-triggered dogfood at counter=3) - full 2-vai coverage (view + omission), 0-garble. Credited as BOTH light+deep (thorough audit) so next light~counter 9, next deep~counter 18. lead-view: 9 flag; lead-omission: 4 flag+1 INFORM (carry-age structurally vacuous - escalated owner)." }, "class_repeat": { "_note": "Rolling tally per flag class from lead_self_audit.flag_classes (memory-budget.json). Key = class name, value = consecutive-audit repeat count. S124 first audit = SEED (fired-once), not repeats yet (per lead-view-auditor honest-note). jump_on_class_repeat=3.", diff --git a/.claude/workflows/hmw.js b/.claude/workflows/hmw.js index db69ad8..6fb941b 100644 --- a/.claude/workflows/hmw.js +++ b/.claude/workflows/hmw.js @@ -95,6 +95,8 @@ const SCHEMA = { } // S4b — args có thể tới OBJECT hoặc JSON-STRING (harness đôi khi stringify object args) → normalize defensive +// 🔴 LUẬT (per 6c2eb2b1 — B6 non-JSON-args): args LUÔN là object JSON thật — dòng normalize NGAY DƯỚI là +// LƯỚI-CUỐI cho ca tầng-máy tự stringify, KHÔNG phải giấy phép để em-main tự stringify args khi gọi. const A = (typeof args === 'string') ? JSON.parse(args) : (args || {}) if (!A || !Array.isArray(A.taskList) || A.taskList.length === 0) { diff --git a/.claude/workflows/runs/2026-07-17-S135-adap-khung-nhip-nam-vong/spec-adap-khung-nhip-17-07-2026.md b/.claude/workflows/runs/2026-07-17-S135-adap-khung-nhip-nam-vong/spec-adap-khung-nhip-17-07-2026.md index 05e250b..b09e8d6 100644 --- a/.claude/workflows/runs/2026-07-17-S135-adap-khung-nhip-nam-vong/spec-adap-khung-nhip-17-07-2026.md +++ b/.claude/workflows/runs/2026-07-17-S135-adap-khung-nhip-nam-vong/spec-adap-khung-nhip-17-07-2026.md @@ -70,7 +70,7 @@ Adap-report mới (wave) + cập nhật/append adap-report liên quan + email hu | ⚑DP-3 | force-fire qua **consent-gate** (INFORM bắt buộc + đề-xuất) | luật cứng owner-era `:94/:203` + hẹn hub "owner-triggered"; reviewer phán carve-out HỢP LỆ — báo hub "PASS-với-carve-out" | đổi 1 câu (vi) thành auto-run | | ⚑DP-4 | reconcile `fable-real.md:31` spec-pointer + dogfood wave này | B6 chốt-2 = function-floor BẮT BUỘC; scope cấm vào **spec/nguồn** (n-2) | revert 1 dòng, quay prompt-embed | | ⚑DP-5 | giữ INFORM 7d + byte-display **wire ở LANE-A 2 site runtime** (M-10); anh đặt ngưỡng sau | đổi số = quyền anh; ngày-lịch cục-bộ hợp lệ B1 §3.4; tránh ghost-wire | anh cho số → đổi 1 điều-kiện INFORM | -| — | light-run-now KHÔNG chạy (moot: OVERDUE tự fire @counter 9 sau wire+restart, consent-gate giữ) | tick S136 → 9 ≥ 3+6 | anh gật → spawn cặp H24 ngay | +| — | light-run-now KHÔNG chạy (moot: OVERDUE tự fire sau wire+restart, consent-gate giữ) | tick S136 → **10** ≥ 3+6 *(reviewer n-2 @S136: bản đầu ước 9 — thiếu đếm tick S135 @pause; OVERDUE:light vẫn fire 10≥9, logic không đổi)* | anh gật → spawn cặp H24 ngay | ## ③ Checklist (vai + deliverable + acceptance ĐO ĐƯỢC) @@ -85,7 +85,7 @@ AC-B: (1) probe chạy 2 lần → output byte-y-hệt (phép 5) + 0-mutation (` **LANE-C — residual + doc + sleep-cmd (Items 6 + 7 + 8-phần-cmd, TRỪ `:206` và INFORM-byte đã sang LANE-A):** sửa `memory-budget.json` (:65 :90 :121) + append 2 note adap-report san-su-that + `hmw.js` comment-luật + `fable-real.md` (:31 reconcile + câu luật) + `sleep-recovery-memory-l2.md` (2 chốt + optional assert + bảng Trigger mô tả). **0 file trùng LANE-A/B.** AC-C: (1) `grep -rn "draft 2026-07-15"` ngoài {runs/, broadcasts/, docs/governance/adap-reports (dòng `:19` GIỮ lịch-sử + note mới), **`.claude/agent-memory/**` (diary lịch-sử — CẤM sửa để làm xanh grep)**} = **0 site sống** (baseline 2 site sống + 1 diary-mention); (2) `grep -c "e0bb5bbf" memory-budget.json` = 2 và `:121`-vùng chứa `e50a4bd0` **và caveat `chua ai duoc ban`** (M-3); (3) *(→ AC-A(7))*; (4) adap-report san-su-that có 2 append (đóng-ask + flip-4c **executed-inferred**, hedge M-8) additive; (5) `hmw.js` + `fable-real.md` mỗi file có câu luật object-thật (viết relative n-1); (6) `fable-real.md:31`-vùng = bản reconcile spec-pointer (scope n-2); (7) sleep cmd chứa `cross-file bleed` + `grep nguoc`/`grep-ngược` (+ optional per-file assert); (8) JSON valid sau sửa (`ConvertFrom-Json` pass) + budget.json giữ ASCII-không-dấu. -**Post-merge (LEAD, không lane):** reviewer pre-commit (vai `reviewer`, worker-tier opus+max) → **commit wave = `git add` ĐÍCH-DANH** {`.claude/commands/*.md` ×5 sửa · `scripts/nhip-no-probe.ps1` · `scripts/governance-detectors.ps1` · `.claude/workflows/hmw.js` · `.claude/governance/.session-counter.json` · `.claude/agent-memory/memory-budget.json` · `docs/governance/adap-reports/*` · run-folder} — đa số NGOÀI hook-3-path; **acceptance: sau commit `git status --porcelain` các path này RỖNG** (M-7) → **RESTART Claude Code bắt buộc** (command `.md` no-hot-reload, `pause.md:9`) → phiên kế: đo phép 7 (1 chu-kỳ pause–tiep → counter +1 = 9) + phép 1 cửa-sổ mới + light-OVERDUE fire @9 (consent-gate) → Item 9 báo hub kèm số + STAGE-2. +**Post-merge (LEAD, không lane):** reviewer pre-commit (vai `reviewer`, worker-tier opus+max) → **commit wave = `git add` ĐÍCH-DANH** {`.claude/commands/*.md` ×6 sửa *(reviewer n-1 @S136: tiep·pause·session-end·session-start [LANE-A, đã vào HEAD qua wal:] + fable-real·sleep-recovery [LANE-C] — bản đầu ghi ×5 thiếu đếm; glob vẫn bắt đủ)* · `scripts/nhip-no-probe.ps1` · `scripts/governance-detectors.ps1` · `.claude/workflows/hmw.js` · `.claude/governance/.session-counter.json` · `.claude/agent-memory/memory-budget.json` · `docs/governance/adap-reports/*` · run-folder} — đa số NGOÀI hook-3-path; **acceptance: sau commit `git status --porcelain` các path này RỖNG** (M-7) → **RESTART Claude Code bắt buộc** (command `.md` no-hot-reload, `pause.md:9`) → phiên kế: đo phép 7 (1 chu-kỳ pause–tiep → counter +1 = 9) + phép 1 cửa-sổ mới + light-OVERDUE fire @9 (consent-gate) → Item 9 báo hub kèm số + STAGE-2. **Baseline mốc so (T6 đo 17-07, lead verify + reviewer re-đo 4/4 khớp):** counter=8/S133 (tick S133 uncommitted — governance-drift, M-7 bịt) · phép-1 FAIL: +3 tick / 9 nhãn-phiên / 7 `wal: pause` cửa-sổ `(e9124fc..a6cd0a2]` · phép-3 PASS 3/3 union-per-label · phép-4 PASS 14/14 · orphan=1 (S135 self-open) · H1/H2 last-vết `72f68d1` (S133/S134 closeout KHÔNG spawn cặp = vòng-1 tự-verify FAIL sống) · un-gisted 2026-07 ≈183KB/5 file · `last_sleep_at=2026-07-10` · `e0bb5bbf` trong budget.json = 0 (post-wave 2) · diary-mention `investigator-codebase/MEMORY.md:23` (lịch-sử, giữ nguyên). diff --git a/.claude/workflows/runs/2026-07-17-S136-laneB-remainder/run.md b/.claude/workflows/runs/2026-07-17-S136-laneB-remainder/run.md index e277c9e..5580910 100644 --- a/.claude/workflows/runs/2026-07-17-S136-laneB-remainder/run.md +++ b/.claude/workflows/runs/2026-07-17-S136-laneB-remainder/run.md @@ -15,4 +15,4 @@ - [x] 0. hmw 1-task LANE-B remainder (wf_c3a56fef-520, ~20min, return CLEAN 0-garble) — 2 detector H25 appended + fault-inject 9/9 PASS + real-repo AC-B(5) - [x] 1. lead verify: H25=4 · parse 0-err · pure-ASCII 0 · detector re-run output khớp lời khai 100% · FP W3 re-verified git độc-lập 3/3 (72f68d1 chứa HANDOFF + session-log ADDED, 9e874ca parent=72f68d1) · sub-task-0.md gốc S135 NGUYÊN 8187B (không đè) · isolation clean → `implement-synthesis.md` -- [!] 2. reviewer pre-commit (sub-reviewer-0.md folder này) → commit wave đích-danh M-7 +- [x] 2. reviewer pre-commit → **GO_WITH_FIXES 0C/0M/2n** (sub-reviewer-0.md folder này; tự re-run detector + tự dựng fault-tree riêng + FP W3 confirm độc-lập + tick contract OK + M-7 add-list đủ + honesty clean) → 2 fix n áp vào spec (annotation ×6 cmd + tick→10) → commit wave đích-danh diff --git a/.claude/workflows/runs/2026-07-17-S136-laneB-remainder/sub-reviewer-0.md b/.claude/workflows/runs/2026-07-17-S136-laneB-remainder/sub-reviewer-0.md index 11ba651..00b2898 100644 --- a/.claude/workflows/runs/2026-07-17-S136-laneB-remainder/sub-reviewer-0.md +++ b/.claude/workflows/runs/2026-07-17-S136-laneB-remainder/sub-reviewer-0.md @@ -1,47 +1,78 @@ # sub-reviewer-0 — ADVERSARIAL PRE-COMMIT REVIEW (S136 laneB-remainder + full wave) > Vai: reviewer (READ-ONLY code; Write only to THIS trace file). Anti-#53: ghi-đĩa-trong-lúc-làm. -> VERDICT ở CUỐI file (cập nhật liên tục). Nếu return garble → file này là nguồn cứu. +> VERDICT ở CUỐI file. Nếu return garble → file này là nguồn cứu. ## Scope - Wave adap khung-nhịp-năm-vòng S135→S136, 7 item AI_INFRA. -- Diff: (a) committed origin/main..HEAD (LANE-A cmd + counter) + (b) working-tree M (LANE-C + LANE-B detector + probe ??). - Spec canonical: runs/2026-07-17-S135-.../spec-adap-khung-nhip-17-07-2026.md §② Items 1-8 + §③ AC-A/B/C. --- -## PROVISIONAL VERDICT (updated as I go) -VERDICT: (pending — investigation in progress) +## ★ FINAL VERDICT +**VERDICT: GO_WITH_FIXES 0C/0M/2n** — COMMIT MAY PROCEED (0 Critical, 0 Major; 2 nice-to-have là spec-annotation drift, KHÔNG chặn commit). Wiring/code/detector đều đúng; đã live-verify + independent teeth-proof. --- -## A. Detector H25 code audit (Category 2 — core) — governance-detectors.ps1:900-1091 +## A. Detector H25 code audit (Item 4+5) — governance-detectors.ps1:900-1091 +### A1. H25-closeout-ritual (Item 4) — MATCHES spec +- Regex L930 `^\[CLAUDE\] Docs: S\d+.*(?:closeout|session-end)` held-in-var, applied to git SUBJECTS only (L943) → 0 self-match / citation-trap avoided. +- Window `$prev.Hash..$cur.Hash` (L967) = git HALF-OPEN (prev,cur] = spec "(closeout-trước-nó..nó]". OK. +- NEW-session `--diff-filter=A -- docs/changelog/sessions/` (L974). OK. 4 books L978-981 (STATUS/HANDOFF forward-slash literal; agent-memory `^\.claude/agent-memory/` forward-slash = git canonical). OK. +- (iv) skip-no-predecessor L962; non-git-tree skip L933/952. OK. +- HONEST-LIMIT comment L916-921 documents S132-tail FP + do-not-special-case + surface-to-owner. Matches spec + S81 honesty. +### A2. H25-role-notebook (Item 5) — MATCHES spec +- roster minus README L1009-10; greedy 2-step parse L1028-29 (safe direction, won't false-map); has-run = mapped OR diaryExists L1041; 0-byte anti-Goodhart L1047 (0-byte→flag); UNMAPPED INFORM-only L1059-63. OK. +### A3. Summary/C4 L1069-85 self-match assert; exit 0 L1091. OK. +**Verdict: structurally MATCHES Items 4+5. No lệch-thật. 1 lệch-chấp-nhận-được = S132-tail FP (documented, fails-safe-direction).** -### A1. H25-closeout-ritual (Item 4) — line-by-line vs spec -- Regex `$CloseoutSubjectRx = '^\[CLAUDE\] Docs: S\d+.*(?:closeout|session-end)'` (L930). Spec wrote capturing `(closeout|session-end)`; code uses non-capturing `(?:...)` — functionally identical for -match. OK. -- Held-in-variable + only applied to git SUBJECTS (L943), never to scanned files → 0 self-match / citation-trap avoided. OK (matches feedback_citation_trap lesson). -- Window = `$prev.Hash..$cur.Hash` (L967) → git range is HALF-OPEN (prev, cur]. Matches spec "(closeout-trước-nó .. nó]". OK. -- `git log --name-only --pretty=format: $range` (L970) union of all touches. OK. -- NEW-session = `--diff-filter=A ... -- docs/changelog/sessions/` (L974) non-empty. Matches spec "sửa file cũ ≠ có log mới". OK. -- 4 books check L978-981: - - STATUS: `$union -contains 'docs/STATUS.md'` — git emits forward-slash paths → literal matches. OK. - - HANDOFF: same. OK. - - NEW-session: `$newSess.Count -eq 0`. OK. - - agent-memory: `$union -match '^\.claude/agent-memory/'` — forward-slash (git canonical), matches. OK. -- (iv) skip-no-predecessor: L962 `if ($i+1 -ge $closeouts.Count)` → skip msg, no crash. OK. -- Non-git-tree: L933 `Test-Path (Join-Path $RepoRoot '.git')` → skip w/ reason L952. OK. -- HONEST-LIMIT comment L916-921 = documents the S132-tail FP + says do-not-special-case + surface to owner. Matches spec Item 4 intent + honesty (S81). +## B. LIVE runtime verification (không tin evidence — tự chạy lại) +- **AC-B5 detector real-repo:** RE-RAN. Output = sub-task-0 claim ĐÚNG 100%: closeouts 73, 2 PASS (99fc373 S134 [ok], e53a693 S133 [ok]) + 1 FLAG (9e874ca S132 missing 2/4 = HANDOFF+NEW-session). role-notebook roster=14 diaries-ok=14 flagged=0 UNMAPPED=27. self-match=0. TOTAL FLAGS 46. +- **AC-B1 probe determinism:** RE-RAN ×2 → byte-identical `NHIP-NO: kiem light 7/6 deep 7/15 OVERDUE:light | tran-bo-nho 0 over-cap | run-chua-gom 0`, exit 0 cả 2. +- **AC-B2 nonexistent RepoRoot:** `probe-loi (khong chan)` + exit 0. OK (không chặn). +- **Probe 3-vế đo độc-lập:** vế-1 counter=10 last_audit=3 light_every=6 → a=7≥6 OVERDUE:light; deep 7/15 ok. ✓. vế-2 cap=25600, cả 14 MEMORY.md < cap (max investigator-codebase 23420) → 0 over-cap. ✓. vế-3 mọi folder có run.md đều có *-synthesis.md (S136 có implement-synthesis.md 3572B) → 0 orphan. ✓ (2 folder S117 KHÔNG run.md → skip đúng contract). -### A2. H25-role-notebook (Item 5) — line-by-line vs spec -- roster = agents/*.md minus README (L1009-1010). OK. -- greedy 2-step parse L1028-1029: `^sub-(.+)-\d+\.md$` then `^sub-(.+)\.md$`. Greedy `.+` — analyzed edge cases (reviewer-1-verdict → UNMAPPED; investigator-codebase-0 → mapped). Safe direction (won't false-map roster). OK. -- has-run = mapped-sub OR diaryExists (L1041). On real repo all 14 have diaries → prefix-map irrelevant to flag result, only affects UNMAPPED INFORM list. -- 0-byte anti-Goodhart L1047: `if ($diaryExists -and $bytes -gt 0){ok} else{flag}` → 0-byte → flag. OK. -- UNMAPPED INFORM-only L1059-1063. OK (S122 measure-content-not-name). +## C. INDEPENDENT TEETH-PROOF (temp git tree -RepoRoot, S95 — KHÔNG tin fault-inject của worker) +Tự dựng cây tạm + inject gap, chạy detector: +- **closeout FLAG:** fake S11 closeout (chỉ chạm foo.txt) → FLAGGED "missing 4 of 4 books". TEETH ✓ (không always-pass). +- **role 0-byte anti-Goodhart:** truncate lead-view-auditor diary → 0-byte → FLAGGED "0-byte (empty notebook)", diaries-ok=1 flagged=1. TEETH ✓. +- **role MISSING:** delete reviewer diary → FLAGGED "diary is MISSING", flagged=2. TEETH ✓. +- PASS-direction đã chứng trên repo thật (W1/W2 [ok], role 14/14). ⇒ **Cả 2 detector có RĂNG 2 chiều, độc-lập xác nhận khớp matrix worker (c-i/r-i/r-iv).** -### A3. Summary/C4 self-exclusion L1069-1085 — asserts 0 self-match + 0 leaked-excluded. exit 0 L1091. OK. +## D. FP W3 — phán ĐỘC-LẬP (task item 3) +Git verify: `9e874ca` (S132 closeout-tail) parent = `72f68d1`; window (72f68d1..9e874ca] = CHỈ 9e874ca; `72f68d1` (lower-bound bị LOẠI) chứa `sessions/2026-07-17-S131-S132-...md` **ADDED (--diff-filter=A)** + touch HANDOFF+STATUS. +- **(i) Classification ĐÚNG:** W3 = FALSE POSITIVE thật (tail-adjacency: ritual của batch S131-S132 nằm trọn ở commit lower-bound bị loại). Fails-SAFE-direction (over-flag, không under-flag). NOT silenced (đúng S95/Goodhart). Đồng-thuận worker + lead (implement-synthesis L20 khớp git-check của tôi). +- **(ii) Per-label folding = đúng-hướng NHƯNG mở lỗ:** fold cùng-nhãn-batch GIẢM FP tail-adjacency, nhưng MỞ lỗ false-NEGATIVE: nhiều phiên gộp 1 nhãn `S-S` → phiên sau "ăn ké" ritual của phiên đầu ⇒ phiên giữa trốn ritual mà window vẫn PASS. Worker+lead KHÔNG tự-áp + treo owner = ĐÚNG (reshape đo để xanh = cấm). FP hiện-tại ship được vì: documented in-code + fails-safe + fix là owner-mechanism-decision. -**Detector code verdict so far: structurally MATCHES spec Items 4+5. Now verifying RUNTIME (AC-B5) + FP W3 + fault-inject spot-run.** +## E. Tick S136 (task item 4) — ĐÚNG _contract.tick +counter=10, last_ticked_session=S136, last_ticked_head=a7bafd5. a7bafd5 REACHABLE từ HEAD 9d32f89 → ancestor-check PASS, **KHÔNG fail-loud oan**. OR-guard idempotent: S135→9, S136→10 (2 nhãn-phiên phân-biệt = +2 từ baseline 8). Monotonic (no regress, fail_loud_on_regress không kích). Pointer §2.1.8 canonical TỒN-TẠI (L181-193 OR-guard đầy đủ) — pointer từ tiep/pause/session-end resolve THẬT (không dangling). +- **n-2 (nice-to-have):** spec DP-table :73 ghi "tick S136 → 9 ≥ 3+6" nhưng ACTUAL counter=10 (spec under-count: KHÔNG tính S135 tick như nhãn-phiên riêng). Logic ĐÚNG; chỉ narrative-estimate lệch 1. OVERDUE:light vẫn fire (10≥9). Post-restart phép-7 (S137 pause-tiep) vẫn +1 đúng (OR-guard per-label). Cosmetic. ---- -(continuing...) +## F. M-7 add-list (task item 5) +Working-tree wave files: fable-real.md, sleep-recovery-memory-l2.md, .session-counter.json, hmw.js, adap-report, governance-detectors.ps1, ?? nhip-no-probe.ps1, run-folder S136. TẤT được cover bởi glob add-list spec §③. LANE-A cmd (tiep/pause/session-end/session-start) Đà committed qua `0363495 wal: pause` (dogfood pause.md:34 add-list mới — counter+file-dở). counter.json có S136-tick delta uncommitted → glob/explicit listing bắt. **0 stray file ngoài add-list.** +- **n-1 (nice-to-have):** spec §③:88 ghi "`.claude/commands/*.md` ×5 sửa" = UNDERCOUNT — thật 6 cmd đổi (tiep/pause/session-end/session-start + fable-real/sleep-recovery). Glob `*.md` bắt hết 6 nên KHÔNG rớt IF dùng glob; nhưng nếu lead enumerate theo con-số "5" thì rớt 1. Đề-xuất: sửa annotation →×6 hoặc xác-nhận dùng glob (M-7 acceptance `git status --porcelain` rỗng vẫn thoả vì 4 file đã committed + 2 file glob-bắt). + +## G. Honesty-presentation (task item 6) — S81 PASS +- sub-task-2.md L16: nhãn tường-minh "🔴 LEAD SCRIBE ON-BEHALF ... Phần dưới do lead ghi SAU KHI verify-đĩa 8/8, KHÔNG phải lời worker" + phân disk-truth vs lost-narrative (đúng feedback_return_terminates + #53). VERDICT nhãn "lead re-run AC-C @S136". KHÔNG worker-self-attest giả. +- implement-synthesis.md L3 "VERIFIED bởi lead (không phải lời worker)". +- Flip-4c nấc = "executed-inferred" (🔴 KHÔNG "verified"), giữ 2 caveat (S119). Hedge đúng. Nấc tick-wire = executed/verified-pending-restart (phép-7 đo SAU restart — cmd .md no-hot-reload). KHÔNG claim vượt bằng-chứng. + +## H. 6-category rollup +- Cat1 Wire (detector/probe/cmd wiring, không BE-endpoint): PASS — live-verify tất. +- Cat2 Schema: N/A (0 migration). PASS. +- Cat3 Security: N/A (script read-only exit 0, 0 auth). PASS. +- Cat4 Code quality: PS5.1 pure-ASCII (60003B, 0 byte>127), parse 0-err, isolation LANE clean, no --no-verify. PASS. +- Cat5 Test/teeth: fault-inject 9/9 + independent teeth-proof 3-ca (mine). PASS. +- Cat6 Writing (adap-report outward): 2 append full-grammar VN đủ dấu. PASS. (cmd .md internal-compressed OK.) + +## Nice-to-have (non-blocking, optional) +- **n-1:** spec §③:88 "×5 cmd .md" → thật ×6 (annotation drift; glob mitigates). Đề-xuất sửa ×6 / confirm glob. +- **n-2:** spec DP-table :73 "tick S136→9" → actual 10 (narrative off-by-1; logic đúng). Cosmetic. +- **info:** lead tạo implement-synthesis.md + update run.md @17:49-50 CONCURRENT với review (moving-target nhẹ); implementation content ổn-định, file review của tôi intact. Không phải lỗi, chỉ ghi-nhận ordering. +- **info (DP-2 accepted-noise):** tick head=a7bafd5 (wal:flush) — closeout kế squash wal-commit → phiên kế-kế có thể fail-loud oan (đã khai spec DP-2, có vết, không mất số). + +## Positives đáng nhớ (resisted scrutiny) +- Pointer-integrity: 3 tick-point CON-TRỎ §2.1.8 (không chép logic) + target TỒN-TẠI thật — AC-A(4) risky-case CLEAN. +- Detector teeth độc-lập-chứng 2 chiều; FP classified-not-silenced (Goodhart-resist). +- exclusion-set AC-C(1) đúng nghĩa: 2 site sống fixed, excluded-zone (diary/adap:19/runs) giữ đúng. +- Probe 3-vế số đúng hết khi tự đo lại (cap 25600, 7/6 arithmetic, orphan 0). diff --git a/docs/governance/adap-reports/2026-07-16-Governance-vong-tu-bao-tri-thu-nam-san-su-that-nhan-da-do.md b/docs/governance/adap-reports/2026-07-16-Governance-vong-tu-bao-tri-thu-nam-san-su-that-nhan-da-do.md index df56516..6b7d7aa 100644 --- a/docs/governance/adap-reports/2026-07-16-Governance-vong-tu-bao-tri-thu-nam-san-su-that-nhan-da-do.md +++ b/docs/governance/adap-reports/2026-07-16-Governance-vong-tu-bao-tri-thu-nam-san-su-that-nhan-da-do.md @@ -81,3 +81,20 @@ PS> Select-String -Path .claude\agents\reviewer.md -Pattern "TRƯỢT" - **(b) Văn-xuôi `docs/` ngoài scope sweep.** Kiểm-kê chỉ phủ `scripts/*.ps1` + `hmw.js` + `memory-budget.json`. Một nhãn "đã-đo" nằm trong prose tài-liệu sẽ KHÔNG bị bắt bởi đợt này — hub §5 cũng đóng chủ-đích lối văn-xuôi (đo thử thấy gần-toàn cảnh-báo-giả), nên đây là giới-hạn CÓ-CHỦ-ĐÍCH, không phải lỗ-hổng vô-tình. - **(c) F11 — citation-trap gen-2 (S123):** câu cửa-duyệt ở `reviewer.md:46` **CHỨA CHÍNH các nhãn nó săn** (`measured/empirical/calibrated/đã-đo`) — đây là **mention-not-use** (nhắc tên marker để định-nghĩa luật, KHÔNG phải một hằng-số thật mang nhãn). Nếu mai sau SE port một **máy-quét khớp-mẫu tự-động**, dòng `:46` này sẽ tự-khớp và tự-flag chính nó (đúng bài citation-trap S123: doc định-nghĩa anti-pattern tất-yếu chứa mẫu đó). ⇒ Khi đó **BẮT BUỘC kèm exemption-note** cho `reviewer.md:46` (discriminator = enclosure use ⟂ mention), giống 3 site `calibration-interim` đã có miễn-trừ visible. Ghi trước ở đây để lần port scanner không tái con bệnh. - **(d) OWNER-GATED — 2 quyết-định của anh (`owner-gated-proposals-dot-16-07.md`):** (ii) C2 `budget:89` lối-thoát rename `measured_token_estimate`→`token_floor_estimate` (sạch nhất, điều-kiện: grep toàn repo → nếu >1 ref thì update cùng commit; consumer script = 0 đã khai) **vs.** exemption-note sibling (ít-chạm). (iv) `budget:65` thêm đuôi provenance nguồn hằng-số `/3.0`. Cả hai cùng file `memory-budget.json` → gộp 1 lần anh trả lời. Worker CHƯA đụng 2 site này (an-toàn vùng owner-authority khi anh vắng). + +## 6. Cập-nhật trạng-thái 2026-07-17 (append additive — wave khung-nhịp-năm-vòng S135; KHÔNG rewrite §1–§5) + +> Hai đính-chính trạng-thái, **ghi thêm** (không sửa dòng cũ — giữ lịch-sử). Nguồn: run `2026-07-17-S135-adap-khung-nhip-nam-vong`, LANE-C Item 6. Bối-cảnh mở khoá: bản **"Phương-pháp đếm token"** nay đã **PUBLISHED** (`content_sha256 e0bb5bbf`, bản `2026-07-15-Governance-phuong-phap-dem-token-tien-de-vong4`) — không còn là bản nháp / NO-GO — nên hai residual dưới đây khép lại được. + +### (i) Đóng-ask do-token — ask KHÉP (lật trạng-thái §3.4 / dòng `:19`) + +Dòng `:19` (**giữ nguyên làm lịch-sử**) khai *"ask do-token của SE **giữ MỞ** tới khi letter land chính-thức"*. **Trạng-thái nay đã LẬT (2026-07-17):** method-letter đã **published** (`e0bb5bbf`), nên ask do-token **KHÉP** đúng broadcast bản-thứ-năm mục **2a** (nhãn "đã-đo" phải neo nguồn **published / chạy-được**, không neo bản nháp). Hai site nguồn `memory-budget.json:65` (`token_estimate`) + `:90` (`_owner_override_note`) đã **thay anchor** từ bản-nháp-NO-GO sang bản `published …vong4 (e0bb5bbf)`, đồng-thời **hạ nhãn**: notice cắt-ngắn của Read = **cận-trên bảo-toàn** (heuristic ký-tự/2, đếm-thừa ~1.4–2× **KHÔNG cố-định**) — **KHÔNG** phải `real-N` / `đã-đo`; token thật nằm trong **DẢI** `[sàn char/4 .. trần notice]`. Bằng-chứng đo tại chỗ: kiểm `memory-budget.json` **hết sạch cụm anchor bản-nháp** (0 hit, baseline 2 site sống) · `grep-count "e0bb5bbf" memory-budget.json` = **2** · `ConvertFrom-Json` PASS · ASCII-không-dấu giữ nguyên. **Nấc: executed.** (Errata blameless kèm gửi hub: residual #1 của bản-thứ-năm đặt SAI landing-site — hub trỏ `crystallized-backfill.ps1` nhưng `git log -S` rỗng toàn lịch-sử ⇒ ps1 **NO-CHANGE**; 2 site thật là `budget:65/:90`, nay đã vá.) + +### (ii) Flip-note 4c (câu cửa-duyệt `reviewer.md:46`) — nấc NẠP lên `executed-inferred` (🔴 KHÔNG phải `verified`) + +§2 khai 4c = **executed → verified-pending** (agent `.md` **no hot-reload** ⇒ chờ restart + spawn-test reviewer). **Cập-nhật theo SUY-LUẬN (KHÔNG đo trực-tiếp):** điều-kiện runtime-proof **Đà HỘI ĐỦ theo suy-luận** — +> (a) commit S129 (`949ec33`-era, ~23:16) → các phiên **S130+ mở app mới** ⇒ restart **IMPLIED** (⚠️ **KHÔNG đo trực-tiếp** giờ khởi-động CLI); +> (b) `reviewer` **spawn thật ≥2 lần** (S133/S134) qua `/fable-real`, precommit-gate `wf_8bb5abee`; +> (c) corroborate S135: `/fable-real reviewer` **tự-quan-sát** persona của chính mình **có chứa dòng `:46`** câu cửa-duyệt — nhưng đây là **self-report, hedge** (sub tự khai, KHÔNG phải quan-trắc-viên độc-lập). + +⇒ Nấc nâng `verified-pending` → **`executed-inferred`**. 🔴 **Hai caveat GIỮ NGUYÊN (không xoá — S119 "giữ kết-luận thì giữ cả caveat"):** (1) "câu cửa-duyệt được **EXERCISE** trong một verdict thật" **chưa chứng riêng** (spawn-đi-qua persona ≠ nhánh-măng fire trên một diff mang nhãn); (2) nấc `verified` **vẫn thuộc AI_INFRA `/adap-audit`** cross-repo — SE **KHÔNG tự khai** `verified`. diff --git a/scripts/governance-detectors.ps1 b/scripts/governance-detectors.ps1 index 3fe1f55..540f7fa 100644 --- a/scripts/governance-detectors.ps1 +++ b/scripts/governance-detectors.ps1 @@ -897,6 +897,172 @@ else { } } +# --------------------------------------------------------------------------- +# H25-closeout-ritual (GAP-2) : did the last 3 session-close commits each leave a +# full ritual trace? A close that skips STATUS / HANDOFF / a NEW session-log / an +# agent-memory delta is silent governance drift (the memory-loss class). +# +# WINDOW, not per-commit (T4a, measured): the ritual delta does NOT sit inside the +# closeout commit alone. The Stop-hook 'wal: flush' carries agent-memory deltas in +# EARLIER commits, and STATUS/HANDOFF are often bumped a commit or two before the +# close. Scoring the closeout commit by itself FAILs healthy data 2/3. So each close +# is scored over the UNION of every commit since the PREVIOUS close: the half-open +# range (prev-close .. this-close], via git log --name-only. +# +# The NEW-session book uses --diff-filter=A (an ADDED path): editing an old session +# log is not opening this session's log. The other three books accept any touch +# (STATUS/HANDOFF/diaries are appended, not recreated). +# +# HONEST LIMIT (surfaced, NOT silenced): a tail-close commit whose previous close is +# its own parent AND the same session batch yields a 1-commit window; that batch's +# real ritual lives in the excluded lower-bound commit, so the tail reads incomplete +# = a FALSE POSITIVE. The canonical spec baseline calls for per-session-label folding +# of same-batch closes; this build is per-commit as handed. Do NOT special-case it to +# green a report -- classify the flag and let the owner choose the grouping. +# +# git-based, so it honours -RepoRoot (fault-injection runs on a temp git tree). A +# RepoRoot that is not a git work-tree SKIPs with a reason; a probe never crashes. +# --------------------------------------------------------------------------- +Write-Section 'H25-closeout-ritual (GAP-2)' + +# Subject shape of a session-close commit. Held in a variable (used by -match on git +# SUBJECTS, never on any scanned file) so no prose copy exists to self-trip a matcher. +$CloseoutSubjectRx = '^\[CLAUDE\] Docs: S\d+.*(?:closeout|session-end)' + +$closeouts = @() +$gitTree = Test-Path (Join-Path $RepoRoot '.git') +if ($gitTree) { + try { + $logRaw = & git -C $RepoRoot log --format='%H|%s' --max-count=400 + } catch { $logRaw = $null } + if ($logRaw) { + foreach ($ln in $logRaw) { + if ([string]::IsNullOrWhiteSpace($ln)) { continue } + $parts = $ln -split '\|', 2 + if ($parts.Count -lt 2) { continue } + if ($parts[1] -match $CloseoutSubjectRx) { + $lab = if ($parts[1] -match 'S(\d+)') { 'S' + $Matches[1] } else { 'S?' } + $closeouts += [pscustomobject]@{ Hash = $parts[0]; Short = $parts[0].Substring(0, 7); Label = $lab } + } + } + } +} + +if (-not $gitTree) { + Write-Host ' [skip] RepoRoot is not a git work-tree - cannot score closeouts' -ForegroundColor DarkGray +} +elseif ($closeouts.Count -eq 0) { + Write-Host ' [skip] no commit matches the closeout subject shape - nothing to score' -ForegroundColor DarkGray +} +else { + $take = [Math]::Min(3, $closeouts.Count) + Write-Host (" closeouts found: {0} ; scoring {1} most-recent (union-window per close)" -f $closeouts.Count, $take) + for ($i = 0; $i -lt $take; $i++) { + $cur = $closeouts[$i] + if ($i + 1 -ge $closeouts.Count) { + Write-Host (" [skip] {0} {1}: no earlier close to open the window (oldest close in history) - not scored, not flagged" -f $cur.Short, $cur.Label) -ForegroundColor DarkGray + continue + } + $prev = $closeouts[$i + 1] + $range = ("{0}..{1}" -f $prev.Hash, $cur.Hash) + + $union = @() + try { $u = & git -C $RepoRoot log --name-only --pretty=format: $range } catch { $u = $null } + foreach ($p in $u) { if (-not [string]::IsNullOrWhiteSpace($p)) { $union += $p.Trim() } } + + $newSess = @() + try { $ns = & git -C $RepoRoot log --diff-filter=A --name-only --pretty=format: $range '--' 'docs/changelog/sessions/' } catch { $ns = $null } + foreach ($p in $ns) { if (-not [string]::IsNullOrWhiteSpace($p)) { $newSess += $p.Trim() } } + + $missing = @() + if (-not ($union -contains 'docs/STATUS.md')) { $missing += 'docs/STATUS.md' } + if (-not ($union -contains 'docs/HANDOFF.md')) { $missing += 'docs/HANDOFF.md' } + if ($newSess.Count -eq 0) { $missing += 'docs/changelog/sessions/*(NEW,--diff-filter=A)' } + if (@($union | Where-Object { $_ -match '^\.claude/agent-memory/' }).Count -eq 0) { $missing += '.claude/agent-memory/**' } + + if ($missing.Count -eq 0) { + Write-Host (" [ok] {0} {1}: union ({2}..{3}] has all 4 ritual books" -f $cur.Short, $cur.Label, $prev.Short, $cur.Short) -ForegroundColor DarkGray + } + else { + Write-Flag 'MED' ("git:{0} ({1})" -f $cur.Short, $cur.Label) ` + ("closeout-ritual gap: window ({0}..{1}] missing {2} of 4 books -> {3}" -f $prev.Short, $cur.Short, $missing.Count, ($missing -join ', ')) ` + 'the session-close (or its window since the prior close) must touch STATUS + HANDOFF + a NEW session-log + an agent-memory diary' + } + } +} + +# --------------------------------------------------------------------------- +# H25-role-notebook (GAP-3) : every role that ACTUALLY RAN must leave a diary +# (.claude/agent-memory//MEMORY.md, byte>0). A role defined-but-never-spawned +# is NOT a defect, so the check is gated on evidence-of-run and measures CONTENT not +# NAME (S122): a sub-file whose prefix is not an exact roster role is LISTED as +# UNMAPPED, never flagged (no name-guessing). A zero-byte diary IS flagged +# (anti-Goodhart: an empty notebook is not a notebook; touching a file to green the +# check must still fail). Byte size via (Get-Item).Length -- Get-Content-count is +# FORBIDDEN (a no-BOM file miscounts VN text x2-3, bug E-010/S130). +# --------------------------------------------------------------------------- +Write-Section 'H25-role-notebook (GAP-3)' + +$agentsDir = Join-Path $RepoRoot '.claude\agents' +$roster = @() +if (Test-Path $agentsDir) { + $roster = @(Get-ChildItem -Path $agentsDir -Filter *.md -File -ErrorAction SilentlyContinue | + Where-Object { $_.BaseName -ne 'README' } | ForEach-Object { $_.BaseName }) +} +$rosterSet = @{} +foreach ($r in $roster) { $rosterSet[$r] = $true } + +if ($roster.Count -eq 0) { + Write-Host ' [skip] no .claude/agents/*.md roster - nothing to check' -ForegroundColor DarkGray +} +else { + # Evidence-of-run: scan runs/*/sub-*.md and map each filename prefix to a roster role. + # Two-step greedy parse: 'sub--.md' first, else 'sub-.md'. + $runsDir = Join-Path $RepoRoot '.claude\workflows\runs' + $ran = @{} # role -> ran (exact roster match) + $unmapped = @{} # prefix -> seen (not an exact roster role) + if (Test-Path $runsDir) { + $subs = Get-ChildItem -Path $runsDir -Recurse -Filter 'sub-*.md' -File -ErrorAction SilentlyContinue + foreach ($s in $subs) { + $cand = $null + if ($s.Name -match '^sub-(.+)-\d+\.md$') { $cand = $Matches[1] } + elseif ($s.Name -match '^sub-(.+)\.md$') { $cand = $Matches[1] } + if ($null -eq $cand) { continue } + if ($rosterSet.ContainsKey($cand)) { $ran[$cand] = $true } else { $unmapped[$cand] = $true } + } + } + + # has-run(role) = a mapped sub-file OR a diary directory already on disk (any size). + $memRoot = Join-Path $RepoRoot '.claude\agent-memory' + $okCount = 0; $flagged = 0; $inert = 0 + foreach ($role in ($roster | Sort-Object)) { + $diary = Join-Path (Join-Path $memRoot $role) 'MEMORY.md' + $diaryExists = Test-Path -LiteralPath $diary + $hasRun = ($ran.ContainsKey($role)) -or $diaryExists + if (-not $hasRun) { + $inert++ + continue + } + $bytes = if ($diaryExists) { (Get-Item -LiteralPath $diary).Length } else { -1 } + if ($diaryExists -and $bytes -gt 0) { + $okCount++ + } + else { + $why = if (-not $diaryExists) { 'MISSING' } else { '0-byte (empty notebook)' } + Write-Flag 'MED' ("agent-memory/{0}/MEMORY.md" -f $role) ` + ("role-notebook gap: role '{0}' has run-evidence but its diary is {1}" -f $role, $why) ` + ("write a non-empty agent-memory/{0}/MEMORY.md (harvest the role slice at session-end)" -f $role) + $flagged++ + } + } + Write-Host (" roster={0} ; diaries-ok={1} ; flagged={2} ; inert(defined-not-run)={3}" -f $roster.Count, $okCount, $flagged, $inert) + if ($unmapped.Count -gt 0) { + $ulist = ($unmapped.Keys | Sort-Object) -join ', ' + Write-Host (" UNMAPPED sub-file prefixes ({0}) [INFORM, not flagged - measure content not name, S122]:" -f $unmapped.Count) -ForegroundColor DarkGray + Write-Host (" {0}" -f $ulist) -ForegroundColor DarkGray + } +} + # --------------------------------------------------------------------------- # Summary + C4 self-exclusion audit (RUNTIME proof) # --------------------------------------------------------------------------- diff --git a/scripts/nhip-no-probe.ps1 b/scripts/nhip-no-probe.ps1 new file mode 100644 index 0000000..84fb701 --- /dev/null +++ b/scripts/nhip-no-probe.ps1 @@ -0,0 +1,155 @@ +<# +.SYNOPSIS + nhip-no-probe.ps1 - Harness-25 "no hien thi" 3-part debt line (B1 section 3.3 floor). + +.DESCRIPTION + ONE-LINE, READ-ONLY, ZERO-MUTATION debt probe. Called (never inlined) by the + session command surfaces (/pause, /tiep, session-start, session-end) so a stop + point always PRINTS the standing debt. Contract: + (1) NO-API, NO-MUTATION - only reads JSON + measures file bytes + lists dirs. + (2) exit 0 ALWAYS - a probe MUST NEVER block a stop point. Any exception -> + prints 'probe-loi (khong chan)' and still exit 0. + (3) PowerShell 5.1 compatible, ASCII-only script body (gotcha #30): every + output token is ASCII (NHIP-NO / kiem / light / deep / OVERDUE / over-cap / + run-chua-gom / CONFIG-THIEU / probe-loi) so a BOM-less .ps1 decoded under the + system ANSI codepage never mojibakes. + (4) NO hardcoded cadence/cap - light_every / deep_every / autoinject_cap_bytes are + READ FROM config keys. A MISSING h24_cadence key prints CONFIG-THIEU(h24_cadence) + IN-LINE (no assumed default, not swallowed into the generic probe-loi) - keeps + the no-default signal H24-2 depends on. + + Output contract - EXACTLY one line: + NHIP-NO: kiem light a/e deep b/f | tran-bo-nho x over-cap | run-chua-gom y + * kiem = audit cadence: a = counter - last_audit.light_at_counter (light debt), + e = h24_cadence.light_every ; b/f = deep pair. OVERDUE when a>=e or b>=f. + * tran-bo-nho = x agent-memory/*/MEMORY.md files whose (Get-Item).Length exceeds + archive_gate.autoinject_cap_bytes (byte-measured FRESH, NOT the stale + 'measured' snapshot; Get-Content-count is FORBIDDEN - bay E-010/S130). + * run-chua-gom = y orphan run-folders per the DUAL-ACCEPT rule ported verbatim + from tiep.md:40-53 (flat *-synthesis.md OR C8 harvest/*-synthesis.md, + each non-empty; retired form-3 NOT ported). + +.PARAMETER RepoRoot + Repo root. Default = resolved one level up from this script (scripts/ -> repo root). + A non-existent RepoRoot is a probe error -> 'probe-loi (khong chan)' + exit 0. + +.EXAMPLE + powershell.exe -ExecutionPolicy Bypass -File scripts/nhip-no-probe.ps1 +#> +param( + [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +) + +$ErrorActionPreference = 'Stop' + +# Read a file's byte length via Get-Item (FORBIDDEN: Get-Content line/char count - +# a no-BOM file counted through Get-Content mis-sizes VN text x2-3, bug E-010/S130). +function Get-ByteLen { + param([string]$Path) + return (Get-Item -LiteralPath $Path -ErrorAction Stop).Length +} + +try { + if (-not (Test-Path -LiteralPath $RepoRoot)) { + throw "RepoRoot not found: $RepoRoot" + } + + # ----------------------------------------------------------------------- + # Ve 1 - nhip-kiem (audit cadence debt). Numerators from .session-counter.json, + # denominators from memory-budget.json h24_cadence (READ, never hardcoded). + # ----------------------------------------------------------------------- + $counterPath = Join-Path $RepoRoot '.claude\governance\.session-counter.json' + $budgetPath = Join-Path $RepoRoot '.claude\agent-memory\memory-budget.json' + + $sc = Get-Content -LiteralPath $counterPath -Raw -Encoding UTF8 | ConvertFrom-Json + $budget = Get-Content -LiteralPath $budgetPath -Raw -Encoding UTF8 | ConvertFrom-Json + + $cad = $budget.h24_cadence + if ($null -eq $cad) { + # h24_cadence key ABSENT -> fail-loud IN-LINE (no default, not probe-loi). + $kiemSeg = 'CONFIG-THIEU(h24_cadence)' + } + elseif ($null -eq $cad.light_every) { + $kiemSeg = 'CONFIG-THIEU(h24_cadence.light_every)' + } + elseif ($null -eq $cad.deep_every) { + $kiemSeg = 'CONFIG-THIEU(h24_cadence.deep_every)' + } + else { + $counter = [int]$sc.counter + $lightAt = [int]$sc.last_audit.light_at_counter + $deepAt = [int]$sc.last_audit.deep_at_counter + $lightEvery = [int]$cad.light_every + $deepEvery = [int]$cad.deep_every + + $aLight = $counter - $lightAt + $bDeep = $counter - $deepAt + + $over = @() + if ($aLight -ge $lightEvery) { $over += 'light' } + if ($bDeep -ge $deepEvery) { $over += 'deep' } + $status = if ($over.Count -gt 0) { 'OVERDUE:' + ($over -join ',') } else { 'ok' } + + $kiemSeg = ("light {0}/{1} deep {2}/{3} {4}" -f $aLight, $lightEvery, $bDeep, $deepEvery, $status) + } + + # ----------------------------------------------------------------------- + # Ve 2 - tran-bo-nho (memory ceiling). Byte-measure agent-memory/*/MEMORY.md + # FRESH vs archive_gate.autoinject_cap_bytes (from key, not hardcoded). + # ----------------------------------------------------------------------- + $cap = $budget.archive_gate.autoinject_cap_bytes + if ($null -eq $cap) { + $tranSeg = 'tran-bo-nho CONFIG-THIEU(autoinject_cap_bytes) over-cap' + } + else { + $capN = [int]$cap + $overCap = 0 + $memRoot = Join-Path $RepoRoot '.claude\agent-memory' + if (Test-Path $memRoot) { + foreach ($mem in (Get-ChildItem -Path $memRoot -Directory -ErrorAction SilentlyContinue)) { + $mf = Join-Path $mem.FullName 'MEMORY.md' + if (Test-Path $mf) { + if ((Get-ByteLen $mf) -gt $capN) { $overCap++ } + } + } + } + $tranSeg = ("tran-bo-nho {0} over-cap" -f $overCap) + } + + # ----------------------------------------------------------------------- + # Ve 3 - run-chua-gom (orphan runs). DUAL-ACCEPT ported from tiep.md:40-53: + # form-1 flat /*-synthesis.md (-s = exists AND non-empty) + # form-2 C8 /harvest/*-synthesis.md + # form-3 (run.md '## synthesis' heading) = RETIRED @S123, NOT ported. + # ----------------------------------------------------------------------- + $orphan = 0 + $runsDir = Join-Path $RepoRoot '.claude\workflows\runs' + if (Test-Path $runsDir) { + foreach ($d in (Get-ChildItem -Path $runsDir -Directory -ErrorAction SilentlyContinue)) { + $runMd = Join-Path $d.FullName 'run.md' + if (-not (Test-Path $runMd)) { continue } # no run.md -> not a run -> skip + $found = $false + foreach ($f in (Get-ChildItem -Path $d.FullName -Filter '*-synthesis.md' -File -ErrorAction SilentlyContinue)) { + if ((Get-ByteLen $f.FullName) -gt 0) { $found = $true; break } + } + if (-not $found) { + $harvestDir = Join-Path $d.FullName 'harvest' + if (Test-Path $harvestDir) { + foreach ($f in (Get-ChildItem -Path $harvestDir -Filter '*-synthesis.md' -File -ErrorAction SilentlyContinue)) { + if ((Get-ByteLen $f.FullName) -gt 0) { $found = $true; break } + } + } + } + if (-not $found) { $orphan++ } + } + } + $runSeg = ("run-chua-gom {0}" -f $orphan) + + Write-Host ("NHIP-NO: kiem {0} | {1} | {2}" -f $kiemSeg, $tranSeg, $runSeg) +} +catch { + # A probe NEVER blocks a stop point. Any failure prints one generic line. + Write-Host 'probe-loi (khong chan)' +} + +exit 0