# context_watch.ps1 - cat context ocupa sesiunea curenta Claude Code. # Citeste ultimul "usage" din transcriptul JSONL al sesiunii (input + cache_read + cache_creation). # Utilizare: # - ca hook (PostToolUse): primeste pe stdin JSON-ul hook-ului, cu transcript_path; # - manual: powershell -File context_watch.ps1 [-TranscriptPath ] [-Prag 250000] # Iese cu 2 si scrie pe stderr cand contextul depaseste pragul (asa ajunge mesajul la agent). # -OSinguraData: emite o singura data per prag per sesiune (marcaj in %TEMP%). Necesar pe PostToolUse, # altfel mesajul s-ar repeta la fiecare apel de tool si ar deveni zgomot ignorat. [CmdletBinding()] param( [string]$TranscriptPath, [int]$Prag = 250000, [int]$PragMax = 275000, [switch]$Intotdeauna, [switch]$Stdout, # UserPromptSubmit: mesajul se scrie pe stdout si se iese cu 0 [switch]$OSinguraData # PostToolUse: o singura alerta per prag atins, nu la fiecare tool ) if (-not $TranscriptPath) { $stdin = [Console]::In.ReadToEnd() if ($stdin) { try { $TranscriptPath = (ConvertFrom-Json $stdin).transcript_path } catch { } } } # la primul mesaj al unei sesiuni noi transcriptul inca nu e creat: fara fallback, ca sa nu raporteze alta sesiune if ($TranscriptPath -and -not (Test-Path $TranscriptPath)) { exit 0 } if (-not $TranscriptPath) { $dir = Join-Path $env:USERPROFILE ".claude\projects\$((Get-Location).Path -replace '[:\\/]','-')" if (Test-Path $dir) { $f = Get-ChildItem "$dir\*.jsonl" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if ($f) { $TranscriptPath = $f.FullName } } } if (-not $TranscriptPath -or -not (Test-Path $TranscriptPath)) { exit 0 } # doar coada fisierului - transcriptul poate avea zeci de MB $fs = [IO.File]::Open($TranscriptPath, 'Open', 'Read', 'ReadWrite') try { $len = [Math]::Min(400KB, $fs.Length) $fs.Seek(-$len, 'End') | Out-Null $buf = New-Object byte[] $len $fs.Read($buf, 0, $len) | Out-Null } finally { $fs.Close() } $coada = [Text.Encoding]::UTF8.GetString($buf) $m = [regex]::Matches($coada, '"usage":\{.*?"output_tokens":\d+') if ($m.Count -eq 0) { exit 0 } $ultim = $m[$m.Count - 1].Value $total = 0 foreach ($camp in @('input_tokens', 'cache_creation_input_tokens', 'cache_read_input_tokens')) { if ($ultim -match ('"' + $camp + '":(\d+)')) { $total += [int]$Matches[1] } } $k = [Math]::Round($total / 1000) $mesaj = '' $nivel = '' if ($total -ge $PragMax) { $nivel = 'max' $mesaj = "CONTEXT ${k}k - PESTE LIMITA de $([Math]::Round($PragMax/1000))k. REGULA ZERO, predare OBLIGATORIE ACUM: " + "(1) opreste lucrul, nu incepe nimic nou, adu editarile in curs la o stare consistenta pe disc; " + "(2) scrie handoff-ul PE DISC in docs\handoff_.md (nu doar in mesaj) - inventar livrabile cu fisier:linie, " + "ce e terminat si ce nu, DACA write-back-ul e facut per fisier, comanda de rulare a testelor, ce s-a stabilit deja, " + "ce e interzis, starea datelor de test, capcanele de mediu; " + "(3) confirma in doua randuri ce ramane si ce e intr-o stare periculoasa; (4) preda unei sesiuni noi. " + "'Mai am putin' nu e motiv de amanare." } elseif ($total -ge $Prag) { $nivel = 'avertisment' $mesaj = "CONTEXT ${k}k - peste $([Math]::Round($Prag/1000))k. REGULA ZERO: incheie blocul curent, actualizeaza handoff-ul pe disc " + "(docs\handoff_.md / docs\progres.md) si pregateste predarea. Deleaga la subagenti tot ce mai ai de citit sau de testat - " + "nu citi loguri brute si nu diagnostica prin citire de cod in sesiunea asta." } if ($mesaj) { if ($OSinguraData -and $nivel) { # marcaj per sesiune+prag: transcriptul identifica sesiunea, deci si numele fisierului de marcaj $cheie = [IO.Path]::GetFileNameWithoutExtension($TranscriptPath) + '_' + $nivel $marcaj = Join-Path $env:TEMP "claude_ctxwatch_$cheie.flag" if (Test-Path $marcaj) { exit 0 } New-Item -ItemType File -Path $marcaj -Force | Out-Null } if ($Stdout) { Write-Output $mesaj; exit 0 } [Console]::Error.WriteLine($mesaj) exit 2 } if ($Intotdeauna) { Write-Output "CONTEXT ${k}k" } exit 0