<# .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