From 6f0248edbdb36caf217ddf03f8636e154464d748 Mon Sep 17 00:00:00 2001 From: adalseno Date: Sat, 1 Aug 2026 23:32:52 +0200 Subject: [PATCH] i18n: stringhe user-facing in inglese (finding 1.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Il plugin gira su un marketplace pubblico con README inglese, ma tutto l'output visibile all'utente era in italiano — e i test lo pinnavano, quindi era contratto, non svista. Tradotte IN PLACE tutte le stringhe che l'utente (o il modello) vede a runtime; commenti, docstring interne e messaggi di commit restano in italiano: sono la voce del progetto e non costano nulla a nessuno. Superficie toccata: - doctor: VERDETTO -> VERDICT e tutte le righe [ok]/[warn]/[ko] - manifest T2: intestazioni di sezione, ruoli (dipendenza -> dependency, importatore -> importer), [grafo generico] -> [generic graph], [freddo T5] -> [T5 cold], sufficienza/budget/T2b - marker T1: ELISION_MARK "elise" -> "elided", JSON_MARK "elisi" -> "dropped", footer "token, -N%" -> "tokens, -N%", INVARIATO/CAMBIATO/IDENTICO -> UNCHANGED/CHANGED/IDENTICAL, [copia ELISA] -> [ELIDED copy], [parcheggiato:] -> [parked:] - canary/auto-degrade/auto-heal, guardia carta (T3), advisor di compact, brief SessionStart, statusline - CLI: recall, ddmin, smoke, savings, revealed, ab_verify (incluso il PROMPT del giudice: VERDETTO: INVARIANTE -> VERDICT: INVARIANT), slice - descrizioni dei tool MCP (le legge il modello) e messaggi d'errore - bench/ e docs/ck_demo_gif.py, per non lasciare output misto Sincronizzati i punti dove una stringa e' contratto tra due file: i marker che revealed.py cerca nel transcript, la regex del footer in smoke.py, il match di sezione del manifest. Chiavi di stato su disco invariate (charter "vincolo"), cosi' le installazioni esistenti non perdono stato; rinominata solo "invariato" -> "unchanged" nell'output --json di revealed, che e' output, non stato. Suite 469 verde; verificati dal vivo doctor, repo_slice, compress+recall (round trip di page fault), statusline e gli --help. Co-Authored-By: Claude Opus 5 --- .../bench/ephemeral_dividend.py | 24 +- claude-context-kernel/bench/exploration_ab.py | 30 +-- .../bench/sufficiency_bench.py | 6 +- claude-context-kernel/docs/ck_demo_gif.py | 20 +- claude-context-kernel/hooks/ab_verify.py | 98 ++++---- claude-context-kernel/hooks/charter.py | 18 +- claude-context-kernel/hooks/charter_guard.py | 22 +- .../hooks/compact_advisor.py | 9 +- claude-context-kernel/hooks/compress.py | 139 ++++++----- claude-context-kernel/hooks/ddmin.py | 48 ++-- claude-context-kernel/hooks/doctor.py | 48 ++-- claude-context-kernel/hooks/lifetime.py | 22 +- .../hooks/posttool_symptom.py | 11 +- .../hooks/precompact_snapshot.py | 2 +- claude-context-kernel/hooks/recall.py | 95 +++---- claude-context-kernel/hooks/revealed.py | 108 ++++---- claude-context-kernel/hooks/savings.py | 138 +++++----- claude-context-kernel/hooks/session_brief.py | 50 ++-- .../hooks/session_end_snapshot.py | 2 +- claude-context-kernel/hooks/smoke.py | 161 ++++++------ claude-context-kernel/hooks/symptom_slice.py | 27 +- claude-context-kernel/mcp/server.py | 56 ++--- .../kernel-repo-slice/scripts/repo_slice.py | 235 +++++++++--------- .../skills/kernel-slice/scripts/slice.py | 2 +- claude-context-kernel/tests/test_ab_verify.py | 22 +- claude-context-kernel/tests/test_bench.py | 2 +- claude-context-kernel/tests/test_canary.py | 46 ++-- claude-context-kernel/tests/test_charter.py | 24 +- claude-context-kernel/tests/test_compress.py | 26 +- claude-context-kernel/tests/test_ddmin.py | 4 +- claude-context-kernel/tests/test_doctor.py | 6 +- claude-context-kernel/tests/test_faults.py | 12 +- claude-context-kernel/tests/test_json_mcp.py | 14 +- .../tests/test_mcp_server.py | 4 +- claude-context-kernel/tests/test_park.py | 20 +- .../tests/test_posttool_symptom.py | 2 +- .../tests/test_precompact.py | 6 +- claude-context-kernel/tests/test_rates.py | 16 +- .../tests/test_repo_slice.py | 178 ++++++------- claude-context-kernel/tests/test_resume.py | 8 +- claude-context-kernel/tests/test_revealed.py | 48 ++-- claude-context-kernel/tests/test_savings.py | 20 +- .../tests/test_session_brief.py | 10 +- claude-context-kernel/tests/test_slice.py | 2 +- claude-context-kernel/tests/test_smoke.py | 30 +-- claude-context-kernel/tests/test_t1_extras.py | 24 +- .../tests/test_task_switch.py | 10 +- 47 files changed, 957 insertions(+), 948 deletions(-) diff --git a/claude-context-kernel/bench/ephemeral_dividend.py b/claude-context-kernel/bench/ephemeral_dividend.py index 195bc59..fd892ac 100644 --- a/claude-context-kernel/bench/ephemeral_dividend.py +++ b/claude-context-kernel/bench/ephemeral_dividend.py @@ -122,9 +122,9 @@ def main() -> int: ap.add_argument("--transcripts", default=os.path.expanduser("~/.claude/projects")) ap.add_argument("--scale", type=float, default=0.5, - help="CK_EPHEMERAL_SCALE del braccio B") + help="CK_EPHEMERAL_SCALE for arm B") ap.add_argument("--adaptive", type=float, default=0.75, - help="scala adattiva live comune ai due bracci") + help="live adaptive scale shared by both arms") ap.add_argument("--json", action="store_true") args = ap.parse_args() @@ -170,7 +170,7 @@ def main() -> int: tot[arm]["lost_by_tool"].get(key, 0) + lost) if n == 0: - print("corpus vuoto: nessun output effimero trovato", file=sys.stderr) + print("empty corpus: no ephemeral output found", file=sys.stderr) return 1 res = { "outputs": n, "per_tool": per_tool, "token_before": before_tot, @@ -190,15 +190,15 @@ def main() -> int: print(json.dumps(res, indent=2)) return 0 a, b = res["arms"]["A"], res["arms"]["B"] - print(f"corpus: {n} output effimeri reali ({per_tool}), " - f"~{before_tot:,} token grezzi") - print(f"A baseline (scala {a['scale']}): ~{a['token_after']:,} token " - f"(-{a['saving_pct']}%), {a['elisions']} elisioni, " - f"{a['signal_lines_lost']} righe di segnale perse") - print(f"B dividendo (scala {b['scale']}): ~{b['token_after']:,} token " - f"(-{b['saving_pct']}%), {b['elisions']} elisioni, " - f"{b['signal_lines_lost']} righe di segnale perse") - print(f"extra risparmio di B: ~{res['extra_saving_tokens']:,} token") + print(f"corpus: {n} real ephemeral outputs ({per_tool}), " + f"~{before_tot:,} raw tokens") + print(f"A baseline (scale {a['scale']}): ~{a['token_after']:,} tokens " + f"(-{a['saving_pct']}%), {a['elisions']} elisions, " + f"{a['signal_lines_lost']} signal lines lost") + print(f"B dividend (scale {b['scale']}): ~{b['token_after']:,} tokens " + f"(-{b['saving_pct']}%), {b['elisions']} elisions, " + f"{b['signal_lines_lost']} signal lines lost") + print(f"extra savings from B: ~{res['extra_saving_tokens']:,} tokens") return 0 diff --git a/claude-context-kernel/bench/exploration_ab.py b/claude-context-kernel/bench/exploration_ab.py index e9f5eaf..820387e 100644 --- a/claude-context-kernel/bench/exploration_ab.py +++ b/claude-context-kernel/bench/exploration_ab.py @@ -69,21 +69,21 @@ "CK_DELTA": "0", "CK_PRETOOL": "0"} -PROMPT = """Lavori nel repository nella directory corrente. Un utente riporta \ -questo errore, in forma parziale come capita nella realta': +PROMPT = """You are working in the repository in the current directory. A user \ +reports this error, in the partial form you get in the real world: {symptom} -Trova il punto ESATTO del repository che SOLLEVA questo errore. Esplora coi \ -tool a disposizione (read, grep, find, bash in sola lettura). Non modificare \ -nulla. Quando sei sicuro, l'ULTIMA riga della risposta deve essere SOLO: -percorso/relativo/file.py::nome_funzione""" +Find the EXACT point in the repository that RAISES this error. Explore with \ +the tools available (read, grep, find, read-only bash). Do not modify \ +anything. When you are sure, the LAST line of your answer must be ONLY: +relative/path/file.py::function_name""" INJECT = """ -[context-kernel] Un tool ha gia' calcolato il working set rilevante per \ -questo sintomo (slicer deterministico sul grafo degli import). Usalo come \ -prior, non come divieto: +[context-kernel] A tool has already computed the working set relevant to \ +this symptom (deterministic slicer over the import graph). Use it as a \ +prior, not as a prohibition: {manifest}""" @@ -96,13 +96,13 @@ def degraded_symptom(err: str, msg: str, caller: str, l'esplorazione deve navigare il grafo: e' il regime dove l'ipotesi prevede che la correttezza possa divergere tra i bracci.""" if difficulty == "hard": - return (f"{err} da qualche parte durante l'esecuzione.\n" - f"(nessun messaggio disponibile, traceback perso; il modulo " - f"coinvolto lato applicazione dovrebbe essere {caller})") + return (f"{err} somewhere during execution.\n" + f"(no message available, traceback lost; the module involved " + f"on the application side should be {caller})") vague = " ".join(msg.split()[:3]) return (f"{err}: {vague} ...\n" - f"(il traceback e' andato perso; il modulo coinvolto lato " - f"applicazione dovrebbe essere {caller})") + f"(the traceback was lost; the module involved on the application " + f"side should be {caller})") def slice_manifest(root: str, symptom: str) -> str: @@ -188,7 +188,7 @@ def main() -> int: reverse.setdefault(d, set()).add(f) cases = sb.find_cases(rs, root, files, reverse, args.cases) if not cases: - print("nessun caso", file=sys.stderr) + print("no case", file=sys.stderr) return 2 rows = [] diff --git a/claude-context-kernel/bench/sufficiency_bench.py b/claude-context-kernel/bench/sufficiency_bench.py index be23ad0..5077934 100644 --- a/claude-context-kernel/bench/sufficiency_bench.py +++ b/claude-context-kernel/bench/sufficiency_bench.py @@ -74,7 +74,7 @@ def run(root: str, n_cases: int, as_json: bool) -> int: root = os.path.abspath(root) files = [f.replace(os.sep, "/") for f in rs.collect_files(root)] if not files: - print("nessun sorgente", file=sys.stderr) + print("no source", file=sys.stderr) return 2 graph = rs.build_graph(root, files) reverse: dict[str, set[str]] = {} @@ -85,7 +85,7 @@ def run(root: str, n_cases: int, as_json: bool) -> int: cases = find_cases(rs, root, files, reverse, n_cases) if not cases: - print("nessun raise-site candidato (con caller e messaggio letterale)", + print("no candidate raise site (with caller and literal message)", file=sys.stderr) return 2 @@ -123,7 +123,7 @@ def run(root: str, n_cases: int, as_json: bool) -> int: return 0 print(f"# sufficiency bench — {root}") print(f"sorgenti: {len(files)} | casi: {len(cases)} " - f"(raise-site reali, sintomo parziale: frame del caller + messaggio)") + f"(real raise sites, partial symptom: caller frame + message)") print(f"{'deps':>5} {'imp':>4} {'suff.':>7} {'rate':>7} {'slice medio':>12}") for r in results: print(f"{r['deps_depth']:>5} {r['importers_depth']:>4} " diff --git a/claude-context-kernel/docs/ck_demo_gif.py b/claude-context-kernel/docs/ck_demo_gif.py index 8e9fe93..2e1d675 100644 --- a/claude-context-kernel/docs/ck_demo_gif.py +++ b/claude-context-kernel/docs/ck_demo_gif.py @@ -61,7 +61,7 @@ def _seg_color(line: str): ("[--]", DIM)): if stripped.startswith(token): return [(indent + token, col), (stripped[len(token):], FG)] - if stripped.startswith("VERDETTO"): + if stripped.startswith("VERDICT"): return [(line, GREEN)] if stripped.startswith("→"): # freccia routing return [(line, CYAN)] @@ -100,19 +100,19 @@ def _frame(lines, cursor=True): "context-kernel — doctor", "========================================", " [ok] Python 3.10.7", - " [ok] hook registrati (hooks.json)", - " [ok] script core presenti (6)", - " [ok] comandi /ck-* presenti (8)", - " [ok] linguaggio naturale (kernel-ops)", - " [ok] canary verde", - " [ok] A/B: coda vuota", - " VERDETTO: tutto a posto ✓", + " [ok] hooks registered (hooks.json)", + " [ok] core scripts present (6)", + " [ok] /ck-* commands present (8)", + " [ok] natural language (kernel-ops)", + " [ok] canary green", + " [ok] A/B: queue empty", + " VERDICT: all clear ✓", ] DOOR2 = [ " → kernel-ops: savings + canary + A/B", " saved 1,214,963 tokens (−61%)", - " 1358 compressioni · canary verde", - " coda A/B vuota", + " 1358 compressions · canary green", + " A/B queue empty", ] frames: list[Image.Image] = [] diff --git a/claude-context-kernel/hooks/ab_verify.py b/claude-context-kernel/hooks/ab_verify.py index cda552a..8d99918 100644 --- a/claude-context-kernel/hooks/ab_verify.py +++ b/claude-context-kernel/hooks/ab_verify.py @@ -24,9 +24,9 @@ CK_AB_TIMEOUT timeout per giudizio in secondi (default 180) Contratto col giudice: l'ULTIMA riga della sua risposta deve essere - VERDETTO: INVARIANTE + VERDICT: INVARIANT oppure - VERDETTO: DEGRADATO — + VERDICT: DEGRADED — Risposte non parsabili lasciano il campione in attesa (max 3 tentativi). """ from __future__ import annotations @@ -56,30 +56,30 @@ TIMEOUT_S = int(os.environ.get("CK_AB_TIMEOUT", "180")) MAX_ATTEMPTS = 3 -VERDICT_OK = "VERDETTO: INVARIANTE" -VERDICT_BAD = "VERDETTO: DEGRADATO" +VERDICT_OK = "VERDICT: INVARIANT" +VERDICT_BAD = "VERDICT: DEGRADED" PROMPT = """\ -Sei un giudice di answer-invariance (T4 della pipeline context-kernel). -Sotto trovi l'ORIGINALE di un output di tool ({tool}) e la versione COMPRESSA -che e' entrata al suo posto nel contesto di un agente di coding. - -Giudica UNA cosa sola: la compressa conserva tutti i segnali su cui l'agente -potrebbe dover agire? (errori, warning, esiti di test, path, versioni, -conteggi, nomi di simboli). I marcatori [context-kernel: ...] e [x N] sono -attesi e legittimi: non sono perdita di segnale. La perdita di righe di puro -rumore (progress, log ripetitivi, corpo di codice riassunto in firme) e' -il comportamento voluto: e' DEGRADATO solo se manca un segnale AZIONABILE. - -Rispondi in massimo 6 righe. L'ULTIMA riga deve essere esattamente: -VERDETTO: INVARIANTE -oppure: -VERDETTO: DEGRADATO — - -=== ORIGINALE === +You are an answer-invariance judge (T4 of the context-kernel pipeline). +Below is the ORIGINAL of a tool output ({tool}) and the COMPRESSED version +that entered a coding agent's context in its place. + +Judge ONE thing only: does the compressed version preserve every signal the +agent might have to act on? (errors, warnings, test outcomes, paths, versions, +counts, symbol names). The [context-kernel: ...] and [x N] markers are +expected and legitimate: they are not signal loss. Losing lines of pure noise +(progress, repetitive logs, code bodies summarised into signatures) is the +intended behaviour: it is DEGRADED only if an ACTIONABLE signal is missing. + +Answer in at most 6 lines. The LAST line must be exactly: +VERDICT: INVARIANT +or: +VERDICT: DEGRADED — + +=== ORIGINAL === {original} -=== COMPRESSA === +=== COMPRESSED === {compressed} """ @@ -120,7 +120,7 @@ def _sample_label(s: dict) -> str: ts = s.get("ts") when = (datetime.datetime.fromtimestamp(ts).isoformat(timespec="seconds") if ts else "?") - return f"{s.get('tool', '?')} {where} ({when}, sessione {s.get('session', '?')})" + return f"{s.get('tool', '?')} {where} ({when}, session {s.get('session', '?')})" def _judge(prompt: str) -> str | None: @@ -139,15 +139,15 @@ def _judge(prompt: str) -> str | None: encoding="utf-8", errors="replace", env={**os.environ, "PYTHONIOENCODING": "utf-8"}) except FileNotFoundError: - print(f"Giudice non trovato: `{CLAUDE_BIN}`. Imposta CK_AB_CLAUDE " - "o installa Claude Code.", file=sys.stderr) + print(f"Judge not found: `{CLAUDE_BIN}`. Set CK_AB_CLAUDE " + "or install Claude Code.", file=sys.stderr) return None except subprocess.TimeoutExpired: - print(f"Giudizio scaduto dopo {TIMEOUT_S}s.", file=sys.stderr) + print(f"Judging timed out after {TIMEOUT_S}s.", file=sys.stderr) return None if proc.returncode != 0: err = (proc.stderr or "").strip().split("\n")[-1:] - print(f"Giudice uscito con {proc.returncode}: {' '.join(err)}", + print(f"Judge exited with {proc.returncode}: {' '.join(err)}", file=sys.stderr) return None return proc.stdout @@ -176,21 +176,21 @@ def print_cron() -> int: + (f' CK_AB_MODEL="{MODEL}"' if MODEL else "") + f' "{sys.executable}" "{me}" --limit 5' f' >> "$HOME/.context-kernel-ab-cron.log" 2>&1') - print("Riga per `crontab -e` (giudizio A/B quotidiano alle 09:30, " - "max 5 campioni, log in ~/.context-kernel-ab-cron.log):\n") + print("Line for `crontab -e` (daily A/B judging at 09:30, " + "max 5 samples, log in ~/.context-kernel-ab-cron.log):\n") print(line) - print("\nRicorda: ab_verify.py e' l'unico comando del plugin che MANDA " - "contenuti a un modello (README §11). Installa il cron solo se " - "questo ti sta bene; in alternativa il brief di SessionStart " - "ricorda i campioni in attesa a ogni nuova sessione.") + print("\nReminder: ab_verify.py is the only command in the plugin that SENDS " + "content to a model (README §11). Install the cron job only if " + "you are comfortable with that; otherwise the SessionStart brief " + "reminds you of pending samples at the start of every session.") return 0 def status(st: dict) -> str: pend = len(st.get("pending", [])) - return (f"A/B invariance: {st.get('ok', 0)} invarianti, " - f"{st.get('degraded', 0)} degradate, {pend} campioni in attesa " - f"(campionate {st.get('counter', 0)} elisioni)") + return (f"A/B invariance: {st.get('ok', 0)} invariant, " + f"{st.get('degraded', 0)} degraded, {pend} samples pending " + f"({st.get('counter', 0)} elisions sampled)") def main() -> int: @@ -203,7 +203,7 @@ def main() -> int: try: limit = int(argv[argv.index("--limit") + 1]) except (IndexError, ValueError): - print("--limit vuole un intero.", file=sys.stderr) + print("--limit expects an integer.", file=sys.stderr) return 2 st = _load() @@ -211,11 +211,11 @@ def main() -> int: print(status(st)) return 0 if not st["pending"]: - print(f"Nessun campione in attesa. {status(st)}") + print(f"No samples pending. {status(st)}") return 0 todo = st["pending"] if limit is None else st["pending"][:limit] - print(f"{len(todo)} campioni da giudicare (giudice: {CLAUDE_BIN}" + print(f"{len(todo)} samples to judge (judge: {CLAUDE_BIN}" f"{' --model ' + MODEL if MODEL else ''})\n") judged_any = False @@ -227,12 +227,12 @@ def main() -> int: original = _unpack(s.get("orig_z", "")) compressed = _unpack(s.get("comp_z", "")) if not original or not compressed: - print(f" scartato (campione illeggibile): {_sample_label(s)}") + print(f" discarded (unreadable sample): {_sample_label(s)}") continue prompt = PROMPT.format(tool=s.get("tool", "?"), original=original, compressed=compressed) if dry: - print(f"--- prompt per {_sample_label(s)} ---") + print(f"--- prompt for {_sample_label(s)} ---") print(prompt) still.append(s) continue @@ -241,11 +241,11 @@ def main() -> int: if verdict is None: s["attempts"] = s.get("attempts", 0) + 1 if s["attempts"] >= MAX_ATTEMPTS: - print(f" scartato dopo {MAX_ATTEMPTS} tentativi: " + print(f" discarded after {MAX_ATTEMPTS} attempts: " f"{_sample_label(s)}") else: still.append(s) - print(f" rimandato (risposta non parsabile, tentativo " + print(f" deferred (unparsable answer, attempt " f"{s['attempts']}): {_sample_label(s)}") continue judged_any = True @@ -253,16 +253,16 @@ def main() -> int: iso = datetime.datetime.now().isoformat(timespec="seconds") if kind == "ok": st["ok"] = st.get("ok", 0) + 1 - print(f" ✓ INVARIANTE {_sample_label(s)}") + print(f" ✓ INVARIANT {_sample_label(s)}") else: st["degraded"] = st.get("degraded", 0) + 1 st["degradations"] = (st.get("degradations", []) + [{ "ts": iso, "tool": s.get("tool"), "file": s.get("file"), "session": s.get("session"), "missing": detail, }])[-20:] - print(f" ⚠ DEGRADATO {_sample_label(s)}") + print(f" ⚠ DEGRADED {_sample_label(s)}") if detail: - print(f" persi: {detail}") + print(f" lost: {detail}") st["last_run"] = iso st["pending"] = still @@ -270,9 +270,9 @@ def main() -> int: _save(st) print(f"\n{status(st)}") if judged_any and st.get("degraded"): - print("Le degradazioni (ultime 20) sono in " - f"{AB_STATE} -> 'degradations': usale per tarare " - "le euristiche di compress.py.") + print("The degradations (last 20) are in " + f"{AB_STATE} -> 'degradations': use them to tune " + "the heuristics in compress.py.") return 0 diff --git a/claude-context-kernel/hooks/charter.py b/claude-context-kernel/hooks/charter.py index ccd697a..e6d5c0b 100644 --- a/claude-context-kernel/hooks/charter.py +++ b/claude-context-kernel/hooks/charter.py @@ -121,7 +121,7 @@ def refresh_charter(repo: str) -> list[str]: mai indovinata) / senza ancora (carta pre-ancore: rigenerarla).""" rec = get_for_repo(repo) if not rec: - return ["nessuna carta attiva per questo repo"] + return ["no active charter for this repo"] st = load_state() root = rec["repo"] files, text = st[root]["files"], st[root]["text"] @@ -132,13 +132,13 @@ def refresh_charter(repo: str) -> list[str]: encoding="utf-8", errors="replace") as f: lines = [l.strip() for l in f.read().split("\n")] except OSError: - report.append(f"IRRISOLVIBILE {path}: file non leggibile") + report.append(f"UNRESOLVABLE {path}: file not readable") continue for e in entries: cite, anchor = f"{path}:{e['line']}", e.get("anchor") if not anchor: - report.append(f"SENZA ANCORA {cite}: carta salvata prima " - "delle ancore — rigenerare la carta") + report.append(f"NO ANCHOR {cite}: charter saved before " + "anchors existed — regenerate the charter") continue if 1 <= e["line"] <= len(lines) and lines[e["line"] - 1] == anchor: report.append(f"OK {cite}") @@ -156,12 +156,12 @@ def refresh_charter(repo: str) -> list[str]: for e2 in entries2: e2["vincolo"] = e2["vincolo"].replace(old_cite, new_cite) e["line"] = new - report.append(f"RI-ANCORATA {cite} -> :{new}") + report.append(f"RE-ANCHORED {cite} -> :{new}") else: # zero o ambigua: dichiarare, mai indovinare (stessa regola # della risoluzione FQCN: suffisso ambiguo -> None) - report.append(f"IRRISOLVIBILE {cite}: ancora trovata " - f"{len(hits)} volte") + report.append(f"UNRESOLVABLE {cite}: anchor found " + f"{len(hits)} times") st[root]["text"] = text st[root]["ts"] = time.time() save_state(st) @@ -218,8 +218,8 @@ def main() -> int: save_charter(repo, sys.stdin.read()) rec = get_for_repo(repo) n = sum(len(v) for v in (rec or {}).get("files", {}).values()) - print(f"carta salvata per {os.path.abspath(repo)}: " - f"{n} vincoli indicizzati (citazioni file:riga)") + print(f"charter saved for {os.path.abspath(repo)}: " + f"{n} constraints indexed (file:line citations)") elif cmd == "get": rec = get_for_repo(repo) or latest() if rec: diff --git a/claude-context-kernel/hooks/charter_guard.py b/claude-context-kernel/hooks/charter_guard.py index d4fefcc..410f6fe 100644 --- a/claude-context-kernel/hooks/charter_guard.py +++ b/claude-context-kernel/hooks/charter_guard.py @@ -153,9 +153,9 @@ def main() -> int: print("{}") return 0 repo, hits = charter.constraints_for(fpath, payload.get("cwd")) - intro = ("[context-kernel] Il file che stai per modificare e' " - "citato nella CARTA DEL TASK (T3). Vincoli che la " - "modifica deve rispettare:") + intro = ("[context-kernel] The file you are about to edit is " + "cited in the TASK CHARTER (T3). Constraints the edit " + "must respect:") elif tname == "Bash" and BASH_ENABLED: cmd = str(tin.get("command") or "") rec0 = charter.get_for_repo(payload.get("cwd") or os.getcwd()) @@ -164,10 +164,10 @@ def main() -> int: return 0 fpath, hits = bash_hits(cmd, rec0) repo = rec0["repo"] - intro = ("[context-kernel] Il comando Bash che stai per eseguire " - "sembra SCRIVERE su un file citato nella CARTA DEL TASK " - f"(T3): {fpath}. La guardia sugli editor qui non ti " - "coprirebbe. Vincoli da rispettare:") + intro = ("[context-kernel] The Bash command you are about to run " + "appears to WRITE to a file cited in the TASK CHARTER " + f"(T3): {fpath}. The editor guard would not cover you " + "here. Constraints to respect:") else: print("{}") return 0 @@ -184,17 +184,17 @@ def main() -> int: vincoli = "\n".join(f"- {h['vincolo']}" for h in hits[:MAX_VINCOLI]) extra = len(hits) - MAX_VINCOLI if extra > 0: - vincoli += f"\n- … altri {extra} vincoli (charter.py get)" + vincoli += f"\n- … {extra} more constraints (charter.py get)" ctx = (f"{intro}\n{vincoli}\n" - "Dopo il fix ripassa la carta vincolo per vincolo " - "(kernel-verifier), o rileggila con: python3 " + "After the fix, walk the charter constraint by constraint " + "(kernel-verifier), or read it again with: python3 " f"\"{os.path.abspath(charter.__file__)}\" get --repo {repo}") print(json.dumps({"hookSpecificOutput": { "hookEventName": "PreToolUse", "additionalContext": ctx, }})) remember(session, fpath, cts) - print(f"context-kernel[guard]: {len(hits)} vincoli per {fpath}", + print(f"context-kernel[guard]: {len(hits)} constraints for {fpath}", file=sys.stderr) return 0 except Exception: # noqa: BLE001 diff --git a/claude-context-kernel/hooks/compact_advisor.py b/claude-context-kernel/hooks/compact_advisor.py index ffd9c21..3bd4502 100644 --- a/claude-context-kernel/hooks/compact_advisor.py +++ b/claude-context-kernel/hooks/compact_advisor.py @@ -105,10 +105,11 @@ def main() -> int: print(json.dumps({"hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": ( - f"[context-kernel] finestra al {pct}% (~{used // 1000}k su " - f"~{win // 1000}k). Un /compact MANUALE adesso costa meno " - "dell'auto-compact vicino al pieno — e lo snapshot TS(Q) e' " - "gia' difeso dal PreCompact. Avviso una-tantum per sessione."), + f"[context-kernel] window at {pct}% (~{used // 1000}k of " + f"~{win // 1000}k). A MANUAL /compact now costs less than an " + "auto-compact near the limit — and the TS(Q) snapshot is " + "already protected by PreCompact. One-off warning per " + "session."), }})) return 0 except Exception: # noqa: BLE001 diff --git a/claude-context-kernel/hooks/compress.py b/claude-context-kernel/hooks/compress.py index e69312d..77a8da8 100644 --- a/claude-context-kernel/hooks/compress.py +++ b/claude-context-kernel/hooks/compress.py @@ -479,9 +479,9 @@ def dedup(lines: list[str]) -> list[str]: return collapsed -ELISION_MARK = "[context-kernel: elise" +ELISION_MARK = "[context-kernel: elided" # marker delle elisioni STRUTTURALI (oggetti JSON, non righe) -JSON_MARK = "[context-kernel: elisi" +JSON_MARK = "[context-kernel: dropped" def has_elision(text: str) -> bool: @@ -507,14 +507,14 @@ def _numeric_continuity(elided: list[str]) -> str | None: step = nums[1] - nums[0] if step == 0 or any(b - a != step for a, b in zip(nums, nums[1:])): return None - passo = f" a passo {step}" if step not in (1, -1) else "" - return f"numerazione continua {nums[0]}→{nums[-1]}{passo}" + passo = f" with step {step}" if step not in (1, -1) else "" + return f"unbroken numbering {nums[0]}→{nums[-1]}{passo}" def signal_preserving_truncate( lines: list[str], signal: "re.Pattern | Callable[[str], bool]" = SIGNAL, - kind: tuple[str, str] = ("rumore", "con segnale"), + kind: tuple[str, str] = ("noise", "carrying signal"), head_n: int | None = None, tail_n: int | None = None) -> list[str]: head_n = HEAD if head_n is None else head_n @@ -535,10 +535,10 @@ def signal_preserving_truncate( start, end = head_n + 1, len(lines) - tail_n cont = _numeric_continuity(elided_lines) marker = ( - f"{ELISION_MARK} righe {start}-{end}: {elided} righe di {kind[0]} " - f"(~{elided_tokens} token)" + f"{ELISION_MARK} lines {start}-{end}: {elided} lines of {kind[0]} " + f"(~{elided_tokens} tokens)" + (f"; {cont}" if cont else "") - + f"; mantenute {len(kept_signal)} righe {kind[1]}]" + + f"; kept {len(kept_signal)} lines {kind[1]}]" ) out = head + [marker] if kept_signal: @@ -555,12 +555,12 @@ def compress(text: str, fpath: str | None = None, scale: float = 1.0) -> str: if fpath and fpath.lower().endswith(CODE_EXTS): lines = signal_preserving_truncate( lines, signal=CODE_SIGNAL, - kind=("corpo", "di struttura (def/class/import)"), + kind=("body", "of structure (def/class/import)"), head_n=head_n, tail_n=tail_n) elif _diff_aware(lines): lines = signal_preserving_truncate( lines, signal=_diff_signal, - kind=("contesto diff", "cambiate (+/-) e struttura"), + kind=("diff context", "changed (+/-) and structural"), head_n=head_n, tail_n=tail_n) else: lines = signal_preserving_truncate( @@ -675,11 +675,11 @@ def canary_check(payload: dict) -> str | None: pr["ok"] = 0 continue alert = ( - "context-kernel CANARY: la compressione precedente " - f"(tool_use {p.get('id', '?')[:16]}) NON risulta applicata nel " - "transcript: l'harness ha ignorato updatedToolOutput. I risparmi " - "loggati sono solo teorici finche' non si ripristina il contratto " - "(controlla la forma del campo, dict vs stringa). Avvisa l'utente." + "context-kernel CANARY: the previous compression " + f"(tool_use {p.get('id', '?')[:16]}) does not appear applied in " + "the transcript: the harness ignored updatedToolOutput. The " + "logged savings are only theoretical until the contract is " + "restored (check the field shape, dict vs string). Tell the user." ) # auto-degrade: N violazioni nella STESSA sessione -> smetti di # comprimere per il resto della sessione (il messaggio di degrado @@ -691,12 +691,12 @@ def canary_check(payload: dict) -> str | None: st["degraded_sessions"] = (degraded + [sess])[-50:] alert = ( "context-kernel AUTO-DEGRADE: " - f"{_session_failures(st, sess)} compressioni non applicate " - "in questa sessione (soglia " - f"{CANARY_DEGRADE_N}). L'harness sta ignorando " - "updatedToolOutput -> da ora gli output passano INTATTI " - "(raw pass-through), niente piu' compressione ne' marker " - "finche' la sessione non riparte. Avvisa l'utente." + f"{_session_failures(st, sess)} compressions not applied " + "in this session (threshold " + f"{CANARY_DEGRADE_N}). The harness is ignoring " + "updatedToolOutput -> from now on outputs pass through " + "INTACT (raw pass-through), no more compression or markers " + "until the session restarts. Tell the user." ) st["pending"] = still _autoheal_ack(st, iso) @@ -722,11 +722,11 @@ def _probe_verified(st: dict, sess: str) -> str | None: s for s in st["degraded_sessions"] if s != sess] st["probe"].pop(sess, None) return ( - "context-kernel RIPRISTINO: " - f"{CANARY_PROBE_M} sonde canary consecutive risultano applicate " - "nel transcript — l'harness onora di nuovo updatedToolOutput. " - "L'auto-degrade di questa sessione e' rimosso: la compressione " - "riparte." + "context-kernel RECOVERY: " + f"{CANARY_PROBE_M} consecutive canary probes appear applied in " + "the transcript — the harness honours updatedToolOutput again. " + "The auto-degrade for this session is lifted: compression " + "resumes." ) return None @@ -750,9 +750,9 @@ def _autoheal_ack(st: dict, iso: str) -> None: }])[-20:] st["failed"] = 0 st["failures"] = [] - print(f"context-kernel: canary auto-ack — {fl} failure transitori " - f"riconosciuti ({streak} compressioni verificate consecutive " - "dopo l'ultimo)", file=sys.stderr) + print(f"context-kernel: canary auto-ack — {fl} transient failures " + f"acknowledged ({streak} consecutive verified compressions " + "since the last one)", file=sys.stderr) def canary_probe_due(payload: dict) -> bool: @@ -1117,11 +1117,11 @@ def remember(suppressed: bool) -> None: remember(False) return None remember(True) - return (f"[context-kernel: file INVARIATO dall'ultima lettura in " - f"questa sessione (hash {h}) — la copia che hai gia' nel " - f"contesto e' valida. Se ti serve comunque il contenuto, " - f"rileggi di nuovo questo stesso file: la prossima Read " - f"passa integrale]") + return (f"[context-kernel: file UNCHANGED since the last read in " + f"this session (hash {h}) — the copy you already have in " + f"context is valid. If you need the content anyway, read " + f"this same file again: the next Read passes through in " + f"full]") old = _unpack_content(rec) if not old: # file grande: niente diff remember(False) @@ -1129,12 +1129,12 @@ def remember(suppressed: bool) -> None: import difflib diff = "\n".join(difflib.unified_diff( old.split("\n"), text.split("\n"), - fromfile="lettura precedente", tofile="ora", lineterm="", n=2)) + fromfile="previous read", tofile="now", lineterm="", n=2)) remember(False) if not diff or est_tokens(diff) >= est_tokens(text) * 0.6: return None # diff non conviene: integrale - return (f"[context-kernel: file CAMBIATO dall'ultima lettura (questa " - f"sessione). Diff contro la copia che hai gia' nel contesto:]\n" + return (f"[context-kernel: file CHANGED since the last read (this " + f"session). Diff against the copy you already have in context:]\n" f"{diff}") @@ -1245,14 +1245,14 @@ def remember(suppressed: bool, elided: bool = False) -> None: remember(False) return None remember(True) - what = ("di questo stesso comando" + what = ("of this same command" if payload.get("tool_name") == "Bash" - else "di questa stessa chiamata MCP") - return (f"[context-kernel: output IDENTICO all'ultima esecuzione " - f"{what} in questa sessione (hash {h}, " - f"~{est_tokens(text)} token) — la copia che hai gia' nel " - f"contesto e' valida. Se ti serve comunque, riesegui: la " - f"prossima passa integrale]") + else "of this same MCP call") + return (f"[context-kernel: output IDENTICAL to the last run " + f"{what} in this session (hash {h}, " + f"~{est_tokens(text)} tokens) — the copy you already have " + f"in context is valid. If you need it anyway, re-run it: " + f"the next one passes through in full]") remember(False) # output cambiato: registra return None @@ -1316,14 +1316,15 @@ def grep_project(text: str) -> str | None: if extra > 0: hidden += extra hidden_tok += est_tokens("\n".join(ls[GREP_PER_FILE:])) - out.append(f" [+{extra} altri match in {f}]") + out.append(f" [+{extra} more matches in {f}]") if hidden == 0: return None total = sum(len(v) for v in per.values()) - marker = (f"{ELISION_MARK} {hidden} match oltre il {GREP_PER_FILE}o per " - f"file (~{hidden_tok} token): {total} match in {len(order)} " - f"file, tenuti i primi {GREP_PER_FILE} per file — nessun file " - f"e' stato tolto; per il resto ripeti il grep sul singolo file]") + marker = (f"{ELISION_MARK} {hidden} matches beyond the {GREP_PER_FILE}th " + f"per file (~{hidden_tok} tokens): {total} matches in " + f"{len(order)} files, kept the first {GREP_PER_FILE} per file — " + f"no file was dropped; for the rest, repeat the grep on the " + f"single file]") return "\n".join([marker] + out) @@ -1358,10 +1359,10 @@ def walk(node): hidden["n"] += len(dropped) shown = ", ".join(keys[:12]) + (", …" if len(keys) > 12 else "") return items[:JSON_SAMPLE] + [ - f"{JSON_MARK} {len(dropped)} di {len(items)} oggetti " - f"(~{tok} token) con chiavi {{{shown}}}; per " - f"l'integrale ripeti la stessa chiamata: la prossima " - f"passa integrale]" + f"{JSON_MARK} {len(dropped)} of {len(items)} objects " + f"(~{tok} tokens) with keys {{{shown}}}; for the full " + f"payload repeat the same call: the next one passes " + f"through in full]" ] return items if isinstance(node, dict): @@ -1387,7 +1388,7 @@ def py_outline(text: str) -> str | None: def sig(node, indent: str = "") -> str: line = src[node.lineno - 1].strip() if node.lineno - 1 < len(src) else "?" - return f"{indent}{line} # righe {node.lineno}-{node.end_lineno}" + return f"{indent}{line} # lines {node.lineno}-{node.end_lineno}" imports = [src[n.lineno - 1] for n in tree.body if isinstance(n, (ast.Import, ast.ImportFrom)) @@ -1403,10 +1404,10 @@ def sig(node, indent: str = "") -> str: symbols.append(sig(sub, " ")) if not symbols: return None - header = (f"{ELISION_MARK} corpo del file (~{est_tokens(text)} token, " - f"{len(src)} righe): file GRANDE proiettato a OUTLINE — " - f"{len(symbols)} simboli con line-range; leggi il simbolo che " - f"ti serve con Read offset/limit]") + header = (f"{ELISION_MARK} file body (~{est_tokens(text)} tokens, " + f"{len(src)} lines): LARGE file projected to an OUTLINE — " + f"{len(symbols)} symbols with line ranges; read the symbol you " + f"need with Read offset/limit]") return "\n".join([header] + imports[:40] + [""] + symbols) @@ -1433,8 +1434,8 @@ def prose_project(text: str) -> str | None: if len(run) >= 6: tok = est_tokens("\n".join(run[2:])) out.extend(run[:2]) - out.append(f"{ELISION_MARK} {len(run) - 2} righe di " - f"link/navigazione (~{tok} token)]") + out.append(f"{ELISION_MARK} {len(run) - 2} lines of " + f"links/navigation (~{tok} tokens)]") hidden += len(run) - 2 i = j continue @@ -1622,13 +1623,13 @@ def noop() -> int: # se comprime, il pending marcato probe portera' l'evidenza che # decide il ripristino (canary_check al giro dopo) payload["_ck_probe"] = True - print("context-kernel: sonda canary in sessione degradata " - f"(1 ogni {CANARY_PROBE_K} output comprimibili)", + print("context-kernel: canary probe in a degraded session " + f"(1 every {CANARY_PROBE_K} compressible outputs)", file=sys.stderr) else: if not alert: - print("context-kernel: auto-degrade attivo (canary) -> raw " - "pass-through per questa sessione", file=sys.stderr) + print("context-kernel: auto-degrade active (canary) -> raw " + "pass-through for this session", file=sys.stderr) return noop() tool_name = payload.get("tool_name") @@ -1707,7 +1708,7 @@ def noop() -> int: mode, lscale = learned_rate( fpath if payload.get("tool_name") == "Read" else None) if mode == "raw": # fault ricorrenti misurati: - print("context-kernel: tasso appreso (T5) -> raw per " + print("context-kernel: learned rate (T5) -> raw for " f"{os.path.splitext(fpath or '')[1]}", file=sys.stderr) return noop() # l'elisione qui costava piu' scale *= lscale # di quanto risparmiava @@ -1744,8 +1745,8 @@ def noop() -> int: and payload.get("tool_name") == "Read" and ELISION_MARK in compressed and mark_read_elided(payload, text, before - after)): - hint = (" [copia ELISA: per l'integrale rileggi questo stesso file — " - "o solo l'intervallo eliso, con offset/limit dal marker]") + hint = (" [ELIDED copy: for the full text read this same file again — " + "or just the elided range, with offset/limit from the marker]") if (CMD_DELTA_ENABLED and replacement is None and (payload.get("tool_name") == "Bash" or is_mcp) and has_elision(compressed)): @@ -1759,13 +1760,13 @@ def noop() -> int: if pkey: rp = os.path.join(os.path.dirname(os.path.abspath(__file__)), "recall.py") - hint += (f' [parcheggiato: python3 "{rp}" {pkey} ' + hint += (f' [parked: python3 "{rp}" {pkey} ' f"--grep PATTERN | --lines A-B]") # Il marcatore del canary e' il footer NUDO (solo i numeri): l'hint puo' # contenere path tra virgolette (parcheggio) che nel JSONL del transcript # diventano \" — il match esatto sulla riga grezza fallirebbe: falso # allarme "harness ha ignorato updatedToolOutput" su compressioni sane. - footer_core = f"[context-kernel: {before} -> {after} token, -{saved:.0%}]" + footer_core = f"[context-kernel: {before} -> {after} tokens, -{saved:.0%}]" footer = f"{footer_core}{hint}" compressed += f"\n\n{footer}" if replacement is None and has_elision(compressed): diff --git a/claude-context-kernel/hooks/ddmin.py b/claude-context-kernel/hooks/ddmin.py index 2e929cd..6dbb061 100644 --- a/claude-context-kernel/hooks/ddmin.py +++ b/claude-context-kernel/hooks/ddmin.py @@ -1,27 +1,27 @@ #!/usr/bin/env python3 """ -ddmin.py — minimalita' EMPIRICA (delta debugging, Zeller & Hildebrandt 2002). +ddmin.py — EMPIRICAL minimality (delta debugging, Zeller & Hildebrandt 2002). -La sufficiency oracle (T4) PREDICE la distorsione sul grafo statico; ddmin la -PROVA eseguendo: dato un input che riproduce un fallimento e un oracolo -pass/fail, trova il sottoinsieme 1-MINIMALE che ANCORA riproduce — nessun -elemento in piu' puo' essere tolto senza perdere il fallimento. E' il rate -massimo a distorsione zero, misurato invece che stimato, sul lato della QUERY: -un sintomo/ripro minimizzato e' un Q piu' stretto -> una proiezione (T2) piu' -precisa. Deterministico, stdlib, zero API. +The sufficiency oracle (T4) PREDICTS distortion over the static graph; ddmin +PROVES it by running: given an input that reproduces a failure and a pass/fail +oracle, it finds the 1-MINIMAL subset that STILL reproduces — no further +element can be removed without losing the failure. It is the maximum rate at +zero distortion, measured instead of estimated, on the QUERY side: a minimised +symptom/repro is a tighter Q -> a more precise projection (T2). Deterministic, +stdlib, zero API. - # minimizza le righe di un input che ancora fanno fallire un comando + # minimise the lines of an input that still make a command fail python3 ddmin.py --oracle 'python3 buggy.py {} ; test $? -eq 1' \ - --input caso.txt --unit line + --input case.txt --unit line - # minimizza carattere per carattere (es. un path JSON che manda in panic) + # minimise character by character (e.g. a JSON path that triggers a panic) python3 ddmin.py --oracle './repro.sh {}' --input payload.txt --unit char -CONTRATTO DELL'ORACOLO: riceve il candidato (il segnaposto {} sostituito col -path di un file temporaneo; senza {} il candidato arriva su stdin) ed ESCE con ---fail-exit (default 0) quando il fallimento e' ANCORA presente ("riproduce"). -Qualunque altro exit = non riproduce. L'oracolo lo scrivi tu: e' cio' che -definisce "stesso fallimento" (un grep sul panic, un exit code, un diff). +ORACLE CONTRACT: it receives the candidate (the {} placeholder replaced by the +path of a temporary file; without {} the candidate arrives on stdin) and EXITS +with --fail-exit (default 0) when the failure is STILL present ("reproduces"). +Any other exit = does not reproduce. You write the oracle: it is what defines +"the same failure" (a grep on the panic, an exit code, a diff). """ from __future__ import annotations @@ -86,19 +86,19 @@ def main() -> int: ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--oracle", required=True, - help="comando; {} = path del candidato (senza {} -> stdin); " - "exit --fail-exit = riproduce") - ap.add_argument("--input", required=True, help="file con l'input da minimizzare") + help="command; {} = path of the candidate (without {} -> stdin); " + "exit --fail-exit = reproduces") + ap.add_argument("--input", required=True, help="file holding the input to minimise") ap.add_argument("--unit", choices=("line", "char"), default="line") ap.add_argument("--fail-exit", type=int, default=0, - help="exit code dell'oracolo che significa 'riproduce' (def 0)") + help="oracle exit code that means 'reproduces' (default 0)") ap.add_argument("--timeout", type=int, default=60) args = ap.parse_args() try: text = open(args.input, encoding="utf-8", errors="replace").read() except Exception as e: # noqa: BLE001 - print(f"input illeggibile: {e}", file=sys.stderr) + print(f"input not readable: {e}", file=sys.stderr) return 2 calls = {"n": 0} @@ -131,8 +131,8 @@ def reproduces(units: list[str]) -> bool: units = _split(text, args.unit) if not reproduces(units): - print("l'input COMPLETO non riproduce (exit != --fail-exit): " - "controlla l'oracolo o --fail-exit. Niente da minimizzare.", + print("the FULL input does not reproduce (exit != --fail-exit): " + "check the oracle or --fail-exit. Nothing to minimise.", file=sys.stderr) return 2 @@ -142,7 +142,7 @@ def reproduces(units: list[str]) -> bool: print(out) pct = (1 - len(minimal) / before) * 100 if before else 0.0 print(f"[ddmin: {before} -> {len(minimal)} {args.unit} (-{pct:.0f}%), " - f"{calls['n']} chiamate all'oracolo, 1-minimale]", file=sys.stderr) + f"{calls['n']} oracle calls, 1-minimal]", file=sys.stderr) return 0 diff --git a/claude-context-kernel/hooks/doctor.py b/claude-context-kernel/hooks/doctor.py index ba0eb19..f6e69e7 100644 --- a/claude-context-kernel/hooks/doctor.py +++ b/claude-context-kernel/hooks/doctor.py @@ -71,7 +71,7 @@ def ko(msg): if v >= (3, 8): ok(f"Python {v.major}.{v.minor}.{v.micro}") else: - ko(f"Python {v.major}.{v.minor} < 3.8 richiesto") + ko(f"Python {v.major}.{v.minor} < 3.8 required") # 2. Hook registrati (il file vive in hooks/hooks.json; alcune installazioni # lo tengono sotto .claude-plugin/ — accetta entrambe le posizioni). @@ -80,7 +80,7 @@ def ko(msg): os.path.join(PLUGIN_ROOT, ".claude-plugin", "hooks.json")) if os.path.isfile(p)), None) if hooks_json is None: - ko("hooks.json assente — hook non registrati") + ko("hooks.json missing — hooks not registered") else: try: with open(hooks_json, encoding="utf-8") as fh: @@ -88,22 +88,22 @@ def ko(msg): except Exception: # noqa: BLE001 body = "" if "compress.py" in body: - ok("hook registrati (hooks.json cita compress.py)") + ok("hooks registered (hooks.json references compress.py)") else: - ko("hooks.json non cita compress.py — compressione non agganciata") + ko("hooks.json does not reference compress.py — compression not wired up") # 3. Script core missing = [s for s in CORE_SCRIPTS if not os.path.isfile(os.path.join(HOOKS, s))] if missing: - ko(f"script mancanti in hooks/: {', '.join(missing)}") + ko(f"scripts missing from hooks/: {', '.join(missing)}") else: - ok(f"script core presenti ({len(CORE_SCRIPTS)})") + ok(f"core scripts present ({len(CORE_SCRIPTS)})") # 4. MCP server if os.path.isfile(os.path.join(PLUGIN_ROOT, "mcp", "server.py")): - ok("MCP server presente (mcp/server.py)") + ok("MCP server present (mcp/server.py)") else: - warn("mcp/server.py assente — gli slice via MCP non sono disponibili") + warn("mcp/server.py missing — slices over MCP are unavailable") # 5. Superficie comandi cmd_dir = os.path.join(PLUGIN_ROOT, "commands") @@ -112,42 +112,42 @@ def ko(msg): have = {f[:-3] for f in os.listdir(cmd_dir) if f.endswith(".md")} miss_cmd = [c for c in EXPECTED_COMMANDS if c not in have] if not miss_cmd: - ok(f"comandi /ck-* presenti ({len(EXPECTED_COMMANDS)})") + ok(f"/ck-* commands present ({len(EXPECTED_COMMANDS)})") else: - warn(f"comandi assenti: {', '.join('/'+c for c in miss_cmd)}") + warn(f"commands missing: {', '.join('/'+c for c in miss_cmd)}") # 5b. Superficie a linguaggio naturale (skill model-invocable) if os.path.isfile(os.path.join(PLUGIN_ROOT, "skills", "kernel-ops", "SKILL.md")): - ok("superficie a linguaggio naturale presente (skill kernel-ops)") + ok("natural-language surface present (kernel-ops skill)") else: - warn("skill kernel-ops assente — niente accesso a parole, solo /ck-*") + warn("kernel-ops skill missing — no natural-language access, only /ck-*") # 6. Canary st = _load_json(CANARY_STATE) if st is None: - rows.append(("--", "canary: nessuno stato ancora (plugin non ha compresso qui)")) + rows.append(("--", "canary: no state yet (the plugin has not compressed here)")) else: failed = st.get("failed", 0) verified = st.get("verified", 0) ndeg = len(st.get("degraded_sessions", [])) if failed: - ko(f"canary: {failed} compressioni NON applicate dall'harness — " - "risparmi sovrastimati (indaga, poi /ck-savings reset-canary)") + ko(f"canary: {failed} compressions NOT applied by the harness — " + "savings overstated (investigate, then /ck-savings reset-canary)") elif ndeg: - warn(f"canary: {ndeg} sessioni in auto-degrade (compressione sospesa)") + warn(f"canary: {ndeg} sessions auto-degraded (compression suspended)") else: - ok(f"canary verde ({verified} compressioni verificate applicate)") + ok(f"canary green ({verified} compressions verified as applied)") # 7. Coda A/B ab = _load_json(AB_STATE) if ab is None: - rows.append(("--", "A/B: nessuno stato ancora")) + rows.append(("--", "A/B: no state yet")) else: pend = len(ab.get("pending", [])) if pend: - warn(f"A/B: {pend} campioni in coda da giudicare (/ck-verify)") + warn(f"A/B: {pend} samples queued for judging (/ck-verify)") else: - ok(f"A/B: coda vuota ({ab.get('ok', 0)} invarianti confermate)") + ok(f"A/B: queue empty ({ab.get('ok', 0)} invariances confirmed)") n_ko = sum(1 for lvl, _ in rows if lvl == "ko") n_warn = sum(1 for lvl, _ in rows if lvl == "warn") @@ -165,13 +165,13 @@ def main(): print(f" {_GLYPH.get(lvl, '[--] ')} {msg}") print("-" * 40) if n_ko: - print(f" VERDETTO: {n_ko} problemi da risolvere" - + (f", {n_warn} avvisi" if n_warn else "")) + print(f" VERDICT: {n_ko} issue(s) to fix" + + (f", {n_warn} warning(s)" if n_warn else "")) return 1 if n_warn: - print(f" VERDETTO: installazione ok, {n_warn} avvisi (nessun bloccante)") + print(f" VERDICT: installation ok, {n_warn} warning(s) (none blocking)") return 0 - print(" VERDETTO: tutto a posto ✓") + print(" VERDICT: all clear ✓") return 0 diff --git a/claude-context-kernel/hooks/lifetime.py b/claude-context-kernel/hooks/lifetime.py index a9946f9..84d89eb 100644 --- a/claude-context-kernel/hooks/lifetime.py +++ b/claude-context-kernel/hooks/lifetime.py @@ -155,21 +155,21 @@ def main() -> int: live = liveness_by_bucket(evs) if not evs: - print("lifetime: nessun page fault registrato — nessun segnale, " - f"soglia al valore base {_fmt_pct(base if base > 0 else 0.70)} " - "(niente adattamento).") + print("lifetime: no page fault recorded — no signal, " + f"threshold at its base value {_fmt_pct(base if base > 0 else 0.70)} " + "(no adaptation).") return 0 - verdict = ("TIENI (contesto vivo, i drop rientrano)" if p > 0.58 - else "COMPATTA PRIMA (contesto morto, i drop non tornano)" - if p < 0.42 else "neutro (nessuna spinta)") - print(f"lifetime: {len(evs)} page fault, pressione {p:.2f} -> {verdict}") - print(f" soglia avviso: {_fmt_pct(base if base > 0 else 0.70)} base " - f"-> {_fmt_pct(thr)} adattiva") + verdict = ("KEEP (live context, drops come back)" if p > 0.58 + else "COMPACT EARLIER (dead context, drops do not come back)" + if p < 0.42 else "neutral (no push either way)") + print(f"lifetime: {len(evs)} page faults, pressure {p:.2f} -> {verdict}") + print(f" warning threshold: {_fmt_pct(base if base > 0 else 0.70)} base " + f"-> {_fmt_pct(thr)} adaptive") if live: - print(" sopravvivenza per classe (n rientri, token rientrati):") + print(" survival by class (n recoveries, tokens clawed back):") for bucket, (n, t) in sorted(live.items(), key=lambda x: -x[1][1])[:10]: - print(f" {bucket:16s} {n:4d}x ~{t:,} token") + print(f" {bucket:16s} {n:4d}x ~{t:,} tokens") return 0 diff --git a/claude-context-kernel/hooks/posttool_symptom.py b/claude-context-kernel/hooks/posttool_symptom.py index fd46bfe..a9956e6 100644 --- a/claude-context-kernel/hooks/posttool_symptom.py +++ b/claude-context-kernel/hooks/posttool_symptom.py @@ -180,10 +180,11 @@ def main() -> int: print("{}") # niente seed: meglio tacere return 0 head = "\n".join(out.split("\n")[:_sym.MAX_LINES]) - ctx = ("[context-kernel] Fallimento rilevato nell'output del comando: " - "working set calcolato dallo slicer deterministico (T2). " - "Usalo come prior per il debug, non come divieto; per rifarlo " - "con altri parametri c'e' la skill kernel-repo-slice.\n" + head) + ctx = ("[context-kernel] Failure detected in the command output: " + "working set computed by the deterministic slicer (T2). Use it " + "as a prior for debugging, not as a prohibition; to recompute " + "it with different parameters there is the kernel-repo-slice " + "skill.\n" + head) print(json.dumps({"hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": ctx, @@ -193,7 +194,7 @@ def main() -> int: # cambio-task e la compaction devono vedere l'ULTIMO Q, da qualunque # lato del turno sia arrivato il sintomo _sym.task_remember(_sym.hook_session(payload), cwd, out) - print(f"context-kernel[posttool]: slice iniettata da {cwd}", + print(f"context-kernel[posttool]: slice injected from {cwd}", file=sys.stderr) return 0 except Exception: # noqa: BLE001 diff --git a/claude-context-kernel/hooks/precompact_snapshot.py b/claude-context-kernel/hooks/precompact_snapshot.py index 39b69e1..9052736 100644 --- a/claude-context-kernel/hooks/precompact_snapshot.py +++ b/claude-context-kernel/hooks/precompact_snapshot.py @@ -79,7 +79,7 @@ def main() -> int: json.dump(st, f) os.replace(tmp, STATE) print("{}") - print(f"context-kernel[compact]: TS(Q) fotografato per {session}", + print(f"context-kernel[compact]: TS(Q) snapshotted for {session}", file=sys.stderr) return 0 except Exception: # noqa: BLE001 diff --git a/claude-context-kernel/hooks/recall.py b/claude-context-kernel/hooks/recall.py index 3d56f5c..a0dbed5 100644 --- a/claude-context-kernel/hooks/recall.py +++ b/claude-context-kernel/hooks/recall.py @@ -1,27 +1,28 @@ #!/usr/bin/env python3 """ -recall.py — page fault MIRATO sugli output effimeri parcheggiati. - -Quando T1 elide un output Bash/MCP/WebFetch, l'originale integrale viene -parcheggiato (compress.py:park_output) e il footer dichiara la chiave. -Questo CLI recupera SOLO cio' che serve — grep o range di righe — cosi' -il fault costa i token della domanda, non dell'output intero. Nessun -ranking, nessun modello: grep e aritmetica, deterministici. - - python3 recall.py --list # cosa c'e' in parcheggio - python3 recall.py --search 'ERROR|WARN' # cerca in TUTTO il parcheggio - python3 recall.py KEY --grep 'ERROR|WARN' # righe matchanti (+contesto) - python3 recall.py KEY --lines 120-180 # range esatto - python3 recall.py KEY --head 40 # testa - python3 recall.py KEY --all # integrale (paghi tutto) - ---search e' il "recall storage" della sessione: quando non sai in QUALE output -parcheggiato sta cio' che cerchi, lo trova in tutti e ti da' la chiave per il -recall mirato. E' l'inverso della proiezione reso navigabile, non un operatore: -grep su tutti i blob, deterministico, paga solo i match mostrati. - -Suggerimento: aggiungi `# ck:raw` al comando per esentare l'output del -recall dalla compressione T1 (e' gia' una selezione mirata). +recall.py — TARGETED page fault over parked ephemeral outputs. + +When T1 elides a Bash/MCP/WebFetch output, the full original is parked +(compress.py:park_output) and the footer states its key. This CLI recovers +ONLY what is needed — a grep or a line range — so the fault costs the tokens +of the question, not of the whole output. No ranking, no model: grep and +arithmetic, both deterministic. + + python3 recall.py --list # what is parked + python3 recall.py --search 'ERROR|WARN' # search the WHOLE parking lot + python3 recall.py KEY --grep 'ERROR|WARN' # matching lines (+context) + python3 recall.py KEY --lines 120-180 # exact range + python3 recall.py KEY --head 40 # head + python3 recall.py KEY --all # in full (you pay for it all) + +--search is the session's "recall storage": when you do not know WHICH parked +output holds what you are after, it finds it across all of them and gives you +the key for a targeted recall. It is the inverse of the projection made +navigable, not an operator: grep over every blob, deterministic, and you pay +only for the matches shown. + +Hint: add `# ck:raw` to the command to exempt the recall output from T1 +compression (it is already a targeted selection). """ from __future__ import annotations @@ -87,10 +88,10 @@ def _search_all(st: dict, pattern: str, context: int) -> int: try: rx = re.compile(pattern) except re.error as e: - print(f"regex non valida: {e}", file=sys.stderr) + print(f"invalid regex: {e}", file=sys.stderr) return 2 if not st: - print("parcheggio vuoto") + print("parking lot empty") return 0 now = time.time() out: list[str] = [] @@ -103,9 +104,9 @@ def _search_all(st: dict, pattern: str, context: int) -> int: continue matched += 1 age = int((now - e.get("ts", now)) / 60) - trunc = " [TRONCATO]" if e.get("trunc") else "" - out.append(f"== {k} {e.get('tool', '?')} {age}min fa " - f"{e.get('cmd', '')[:60]} ({len(idx)} match){trunc} ==") + trunc = " [TRUNCATED]" if e.get("trunc") else "" + out.append(f"== {k} {e.get('tool', '?')} {age}min ago " + f"{e.get('cmd', '')[:60]} ({len(idx)} matches){trunc} ==") keep: set[int] = set() for i in idx: keep.update(range(max(0, i - context), @@ -114,8 +115,8 @@ def _search_all(st: dict, pattern: str, context: int) -> int: capped = False for i in sorted(keep): if shown >= MAX_GREP_LINES: - out.append(f" … cap {MAX_GREP_LINES} righe: restringi la regex " - "o usa `recall KEY --grep`") + out.append(f" … capped at {MAX_GREP_LINES} lines: narrow the regex " + "or use `recall KEY --grep`") capped = True break if i != last + 1: @@ -127,8 +128,8 @@ def _search_all(st: dict, pattern: str, context: int) -> int: if capped: break if not matched: - print(f"nessun output parcheggiato matcha /{pattern}/ " - f"({len(st)} in parcheggio)") + print(f"no parked output matches /{pattern}/ " + f"({len(st)} parked)") return 0 text = "\n".join(out) print(text) @@ -138,10 +139,10 @@ def _search_all(st: dict, pattern: str, context: int) -> int: def main() -> int: ap = argparse.ArgumentParser(description=__doc__) - ap.add_argument("key", nargs="?", help="chiave dal footer [parcheggiato: ...]") + ap.add_argument("key", nargs="?", help="key from the [parked: ...] footer") ap.add_argument("--grep", metavar="REGEX") ap.add_argument("--search", metavar="REGEX", - help="cerca in TUTTI gli output parcheggiati (recall storage)") + help="search ALL parked outputs (recall storage)") ap.add_argument("-C", "--context", type=int, default=GREP_CONTEXT) ap.add_argument("--lines", metavar="A-B") ap.add_argument("--head", type=int, metavar="N") @@ -154,36 +155,36 @@ def main() -> int: return _search_all(st, args.search, args.context) if args.list or not args.key: if not st: - print("parcheggio vuoto") + print("parking lot empty") return 0 now = time.time() for k, e in sorted(st.items(), key=lambda kv: -kv[1].get("ts", 0)): age = int((now - e.get("ts", now)) / 60) - trunc = " [TRONCATO al parcheggio]" if e.get("trunc") else "" - print(f"{k} {e.get('tool', '?'):<10} {age:>4}min fa " + trunc = " [TRUNCATED at parking time]" if e.get("trunc") else "" + print(f"{k} {e.get('tool', '?'):<10} {age:>4}min ago " f"{e.get('cmd', '')[:80]}{trunc}") return 0 entry = st.get(args.key) if not entry: - print(f"chiave '{args.key}' assente o scaduta (TTL). " - "`--list` per vedere il parcheggio.", file=sys.stderr) + print(f"key '{args.key}' missing or expired (TTL). " + "Use `--list` to see the parking lot.", file=sys.stderr) return 2 lines = _text(entry).split("\n") if entry.get("trunc"): - print("# NOTA: originale TRONCATO al parcheggio (oltre il cap)", + print("# NOTE: original TRUNCATED at parking time (over the cap)", file=sys.stderr) if args.grep: try: rx = re.compile(args.grep) except re.error as e: - print(f"regex non valida: {e}", file=sys.stderr) + print(f"invalid regex: {e}", file=sys.stderr) return 2 hit_idx = [i for i, ln in enumerate(lines) if rx.search(ln)] if not hit_idx: - print(f"nessuna riga matcha /{args.grep}/ " - f"({len(lines)} righe parcheggiate)") + print(f"no line matches /{args.grep}/ " + f"({len(lines)} lines parked)") return 0 keep: set[int] = set() for i in hit_idx: @@ -194,8 +195,8 @@ def main() -> int: last = -2 for i in sorted(keep): if shown >= MAX_GREP_LINES: - out.append(f"… altri match oltre il cap di {MAX_GREP_LINES} " - "righe (restringi la regex o usa --lines)") + out.append(f"… more matches beyond the cap of {MAX_GREP_LINES} " + "lines (narrow the regex or use --lines)") break if i != last + 1: out.append("…") @@ -210,7 +211,7 @@ def main() -> int: if args.lines: m = re.fullmatch(r"(\d+)-(\d+)", args.lines.strip()) if not m: - print("--lines vuole il formato A-B (1-based)", file=sys.stderr) + print("--lines expects the A-B format (1-based)", file=sys.stderr) return 2 a, b = int(m.group(1)), int(m.group(2)) out = [f"{i}\t{lines[i - 1]}" @@ -229,8 +230,8 @@ def main() -> int: n = args.head or 40 out = [f"{i + 1}\t{lines[i]}" for i in range(min(n, len(lines)))] if len(lines) > n: - out.append(f"… {len(lines) - n} righe restanti " - "(--grep, --lines A-B, oppure --all per l'integrale)") + out.append(f"… {len(lines) - n} lines remaining " + "(--grep, --lines A-B, or --all for the whole thing)") text = "\n".join(out) print(text) _log_fault(text) diff --git a/claude-context-kernel/hooks/revealed.py b/claude-context-kernel/hooks/revealed.py index 58451b1..cc5fcab 100644 --- a/claude-context-kernel/hooks/revealed.py +++ b/claude-context-kernel/hooks/revealed.py @@ -50,9 +50,9 @@ os.environ.get("CK_PROJECTS_DIR", "~/.claude/projects")) DEFAULT_LAST = int(os.environ.get("CK_REVEALED_LAST", "5")) -MANIFEST_MARK = "## seed (dal sintomo)" -ELIDED_MARK = "copia ELISA" -INVARIATO_MARK = "file INVARIATO dall'ultima lettura" +MANIFEST_MARK = "## seeds (from the symptom)" +ELIDED_MARK = "ELIDED copy" +INVARIATO_MARK = "file UNCHANGED since the last read" def est_tokens(text: str) -> int: @@ -98,7 +98,7 @@ def manifest_files(text: str) -> tuple[list[str], str | None]: if line.startswith("## "): section = line continue - if section is None or "fuori slice" in section: + if section is None or "outside the slice" in section: continue if line.startswith("- ") and not line.startswith(("- …", "- (")): f = line[2:].split()[0] @@ -182,49 +182,49 @@ def in_slice(fp: str) -> bool: "reads": len(reads), "faults": [{"file": f, "tokens": t} for f, t in faults], "fault_tokens": sum(t for _, t in faults), - "invariato": invariato, + "unchanged": invariato, } def render(r: dict) -> str: - out = [f"# rilevanza rivelata — {os.path.basename(r['transcript'])}"] + out = [f"# revealed relevance — {os.path.basename(r['transcript'])}"] if not r["slice_files"] and not r["reads"]: - out.append("(nessuna slice T2 e nessuna Read nel transcript)") + out.append("(no T2 slice and no Read in the transcript)") return "\n".join(out) if r["slice_files"]: n = len(r["slice_files"]) - out.append(f"manifest T2: {n} file" + out.append(f"T2 manifest: {n} files" + (f" (repo {r['repo']})" if r["repo"] else "")) - out.append(f"- aperti dalla slice: {len(r['opened'])}/{n}") + out.append(f"- opened from the slice: {len(r['opened'])}/{n}") if r["never_opened"]: shown = ", ".join(r["never_opened"][:10]) more = f" (+{len(r['never_opened']) - 10})" \ if len(r["never_opened"]) > 10 else "" - out.append(f"- MAI aperti: {shown}{more}") - out.append(" -> suggerimento: se ricorre su piu' sessioni, il " - "prior e' largo — valuta meno importatori/profondita' " - "(il page fault resta come rete)") + out.append(f"- NEVER opened: {shown}{more}") + out.append(" -> hint: if this recurs across sessions the " + "prior is too wide — consider fewer importers/less depth " + "(the page fault stays as a safety net)") if r["outside_slice"]: shown = ", ".join(r["outside_slice"][:10]) more = f" (+{len(r['outside_slice']) - 10})" \ if len(r["outside_slice"]) > 10 else "" - out.append(f"- aperti FUORI slice: {shown}{more}") - out.append(" -> suggerimento: seed persi — se sono config/DI/" - "import dinamici, candidali ai seed dello slicer") + out.append(f"- opened OUTSIDE the slice: {shown}{more}") + out.append(" -> hint: missed seeds — if they are config/DI/" + "dynamic imports, propose them as slicer seeds") else: - out.append(f"nessun manifest T2 iniettato; Read totali: {r['reads']}") + out.append(f"no T2 manifest injected; total Reads: {r['reads']}") if r["faults"]: files = ", ".join(sorted({f['file'].split('/')[-1] for f in r["faults"]})) - out.append(f"- page fault post-elisione: {len(r['faults'])} " - f"({files}) ~{r['fault_tokens']} token di riletture") - out.append(" -> costo dell'elisione su questi file: se ricorre, " - "alza CK_MIN_TOKENS/CK_OUTLINE_MIN o usa # ck:raw li'") + out.append(f"- page faults after elision: {len(r['faults'])} " + f"({files}) ~{r['fault_tokens']} tokens of re-reads") + out.append(" -> cost of eliding these files: if it recurs, " + "raise CK_MIN_TOKENS/CK_OUTLINE_MIN or use # ck:raw there") else: - out.append("- page fault post-elisione: 0 (nessuna elisione e' " - "costata una rilettura)") - if r["invariato"]: - out.append(f"- riletture INVARIATE evitate: {r['invariato']}") + out.append("- page faults after elision: 0 (no elision cost " + "a re-read)") + if r["unchanged"]: + out.append(f"- UNCHANGED re-reads avoided: {r['unchanged']}") return "\n".join(out) @@ -259,33 +259,33 @@ def aggregate(results: list[dict]) -> dict: outside[fp] = outside.get(fp, 0) + 1 for sf in r["never_opened"]: never[sf] = never.get(sf, 0) + 1 - tot_invariato += r["invariato"] + tot_invariato += r["unchanged"] proposals: list[str] = [] for fp, d in sorted(fault_files.items(), key=lambda kv: (-kv[1]["tokens"], kv[0])): if d["transcripts"] >= 2 or d["faults"] >= 2: proposals.append( - f"`# ck:raw` in {os.path.basename(fp)} (o alza " - f"CK_MIN_TOKENS/CK_OUTLINE_MIN): {d['faults']} fault, " - f"~{d['tokens']} token di riletture in " - f"{d['transcripts']} sessioni — {fp}") + f"`# ck:raw` in {os.path.basename(fp)} (or raise " + f"CK_MIN_TOKENS/CK_OUTLINE_MIN): {d['faults']} faults, " + f"~{d['tokens']} tokens of re-reads across " + f"{d['transcripts']} sessions — {fp}") for fp, n in sorted(outside.items(), key=lambda kv: (-kv[1], kv[0])): if n >= 2: proposals.append( - f"candidalo ai seed dello slicer: {fp} " - f"(aperto FUORI slice in {n} sessioni)") + f"propose as a slicer seed: {fp} " + f"(opened OUTSIDE the slice in {n} sessions)") for sf, n in sorted(never.items(), key=lambda kv: (-kv[1], kv[0])): if n >= 2: proposals.append( - f"prior largo: {sf} in slice ma MAI aperto in {n} manifest " - "— valuta meno importatori/profondita'") + f"wide prior: {sf} in the slice but NEVER opened across {n} " + "manifests — consider fewer importers/less depth") return { "transcripts": len(results), "manifests": manifests, "faults": tot_faults, "fault_tokens": tot_fault_tokens, - "invariato": tot_invariato, + "unchanged": tot_invariato, "fault_files": fault_files, "outside_recurrent": {f: n for f, n in outside.items() if n >= 2}, "never_recurrent": {f: n for f, n in never.items() if n >= 2}, @@ -337,17 +337,17 @@ def write_rates(a: dict) -> list[str]: """Scrive RATES_STATE per compress.py. Ritorna le righe di esito.""" rates = rates_from_aggregate(a) if not rates: - return ["tassi: nessun fault ricorrente — niente da scrivere"] + return ["rates: no recurrent fault — nothing to write"] payload = {"ts": time.time(), "transcripts": a["transcripts"], "categories": rates} tmp = f"{RATES_STATE}.tmp.{os.getpid()}" with open(tmp, "w", encoding="utf-8") as f: json.dump(payload, f) os.replace(tmp, RATES_STATE) - lines = [f"tassi scritti in {RATES_STATE} (compress.py li legge):"] + lines = [f"rates written to {RATES_STATE} (compress.py reads them):"] lines += [f"- {ext} -> {r['mode']}" + (f" x{r['scale']}" if r["mode"] == "relax" else "") - + f" ({r['faults']} fault, ~{r['tokens']} token di riletture)" + + f" ({r['faults']} faults, ~{r['tokens']} tokens of re-reads)" for ext, r in rates.items()] return lines @@ -385,8 +385,8 @@ def write_priors(a: dict, results: list[dict]) -> list[str]: """Scrive PRIORS_STATE per repo_slice.py. Ritorna le righe di esito.""" priors = priors_from_aggregate(a, results) if not priors: - return ["prior: nessun pattern ricorrente con repo noto — " - "niente da scrivere"] + return ["priors: no recurrent pattern with a known repo — " + "nothing to write"] try: with open(PRIORS_STATE, encoding="utf-8") as f: st = json.load(f) @@ -394,12 +394,12 @@ def write_priors(a: dict, results: list[dict]) -> list[str]: st = {} except Exception: # noqa: BLE001 st = {} - lines = [f"prior scritti in {PRIORS_STATE} (repo_slice.py li legge):"] + lines = [f"priors written to {PRIORS_STATE} (repo_slice.py reads them):"] for repo, rec in priors.items(): st[os.path.normpath(repo)] = {"ts": time.time(), "transcripts": a["transcripts"], **rec} - lines.append(f"- {repo}: {len(rec['seeds'])} seed candidati, " - f"{len(rec['cold'])} file freddi") + lines.append(f"- {repo}: {len(rec['seeds'])} candidate seeds, " + f"{len(rec['cold'])} cold files") for k in sorted(st, key=lambda k: st[k].get("ts", 0))[:-8]: st.pop(k, None) # tieni gli ultimi 8 repo tmp = f"{PRIORS_STATE}.tmp.{os.getpid()}" @@ -410,18 +410,18 @@ def write_priors(a: dict, results: list[dict]) -> list[str]: def render_aggregate(a: dict) -> str: - out = [f"# rilevanza rivelata — AGGREGATO su {a['transcripts']} transcript"] - out.append(f"manifest T2 visti: {a['manifests']} | page fault totali: " - f"{a['faults']} (~{a['fault_tokens']} token di riletture) | " - f"riletture INVARIATE evitate: {a['invariato']}") + out = [f"# revealed relevance — AGGREGATE over {a['transcripts']} transcripts"] + out.append(f"T2 manifests seen: {a['manifests']} | total page faults: " + f"{a['faults']} (~{a['fault_tokens']} tokens of re-reads) | " + f"UNCHANGED re-reads avoided: {a['unchanged']}") if a["proposals"]: out.append("") - out.append("## proposta di config (applicala tu: la telemetria " - "suggerisce, mai auto-tuning)") + out.append("## suggested config (you apply it: telemetry " + "suggests, never auto-tunes)") out.extend(f"- {p}" for p in a["proposals"]) else: - out.append("nessun pattern ricorrente: niente da proporre " - "(le soglie scattano su >=2 sessioni/occorrenze)") + out.append("no recurrent pattern: nothing to propose " + "(thresholds fire at >=2 sessions/occurrences)") return "\n".join(out) @@ -455,13 +455,13 @@ def main() -> int: try: last = int(next(it)) except (StopIteration, ValueError): - print("--last vuole un intero", file=sys.stderr) + print("--last expects an integer", file=sys.stderr) return 1 continue args.append(a) paths = pick_transcripts(args, last) if not paths: - print("nessun transcript trovato", file=sys.stderr) + print("no transcript found", file=sys.stderr) return 1 results = [mine_transcript(p) for p in paths] if do_aggregate: @@ -478,7 +478,7 @@ def main() -> int: else: out = render_aggregate(agg) if extra: - out += "\n\n## attuazione esplicita\n" + "\n".join(extra) + out += "\n\n## explicit application\n" + "\n".join(extra) print(out) return 0 if as_json: diff --git a/claude-context-kernel/hooks/savings.py b/claude-context-kernel/hooks/savings.py index 3959614..1933479 100644 --- a/claude-context-kernel/hooks/savings.py +++ b/claude-context-kernel/hooks/savings.py @@ -72,8 +72,8 @@ def read_faults() -> tuple[int, int, dict, dict]: return n, tok, per_kind, per_bucket -_FAULT_LABEL = {"reread": "riletture integrali", "recmd": "riesecuzioni", - "recall": "recall mirati"} +_FAULT_LABEL = {"reread": "full re-reads", "recmd": "re-runs", + "recall": "targeted recalls"} def fault_status(saved_total: int = 0) -> str | None: @@ -85,13 +85,13 @@ def fault_status(saved_total: int = 0) -> str | None: n, tok, per_kind, _ = read_faults() if not n: return None - frac = (f" = {tok / saved_total:.1%} del risparmiato rientrato" + frac = (f" = {tok / saved_total:.1%} of the savings clawed back" if saved_total > 0 else "") - lines = [f" page fault (distorsione): {n} recuperi, " - f"~{tok:,} token rientrati{frac}"] + lines = [f" page faults (distortion): {n} recoveries, " + f"~{tok:,} tokens clawed back{frac}"] for kind, (t, c) in sorted(per_kind.items(), key=lambda x: -x[1][0]): lines.append(f" {_FAULT_LABEL.get(kind, kind):22s} " - f"{c:4d}x ~{t:,} token") + f"{c:4d}x ~{t:,} tokens") return "\n".join(lines) @@ -103,11 +103,11 @@ def reset_canary() -> int: with open(CANARY_STATE, encoding="utf-8") as f: st = json.load(f) except Exception: # noqa: BLE001 - print("Nessuno stato canary da resettare.") + print("No canary state to reset.") return 0 fl = st.get("failed", 0) if not fl: - print("Nessun fallimento attivo: niente da riconoscere.") + print("No active failures: nothing to acknowledge.") return 0 st["failed_acked"] = st.get("failed_acked", 0) + fl st["failed"] = 0 @@ -119,9 +119,9 @@ def reset_canary() -> int: st["probe"] = {} # niente degradate -> niente sonde with open(CANARY_STATE, "w", encoding="utf-8") as f: json.dump(st, f) - deg = f" Sbloccate {ndeg} sessioni in auto-degrade." if ndeg else "" - print(f"Riconosciuti {fl} fallimenti canary (storico: {st['failed_acked']}).{deg} " - "L'allarme si riaccendera' solo su fallimenti NUOVI.") + deg = f" Unblocked {ndeg} auto-degraded sessions." if ndeg else "" + print(f"Acknowledged {fl} canary failures (historical: {st['failed_acked']}).{deg} " + "The alarm will fire again only on NEW failures.") return 0 @@ -140,34 +140,34 @@ def canary_status() -> str | None: pend = len(st.get("pending", [])) hist_parts = [] if acked: - hist_parts.append(f"{acked} storici riconosciuti") + hist_parts.append(f"{acked} historical, acknowledged") if auto: - hist_parts.append(f"{auto} auto-riconosciuti su evidenza") + hist_parts.append(f"{auto} auto-acknowledged on evidence") hist = f" ({', '.join(hist_parts)})" if hist_parts else "" ndeg = len(st.get("degraded_sessions", [])) probe_k = int(os.environ.get("CK_CANARY_PROBE_K", "10")) - sonda = (f"; sonda di ripristino attiva (1 ogni {probe_k} output)" + sonda = (f"; recovery probe active (1 every {probe_k} outputs)" if os.environ.get("CK_CANARY_AUTOHEAL", "1") != "0" and probe_k > 0 else "") - deg = (f"\n AUTO-DEGRADE: {ndeg} sessioni passate a raw pass-through " - f"(compressione sospesa dopo troppe violazioni){sonda}") if ndeg else "" + deg = (f"\n AUTO-DEGRADE: {ndeg} sessions switched to raw pass-through " + f"(compression suspended after too many violations){sonda}") if ndeg else "" if fl: sessions = {f.get("session", "?") for f in st.get("failures", [])} - where = f" [sessioni: {', '.join(sorted(sessions))}]" if sessions else "" + where = f" [sessions: {', '.join(sorted(sessions))}]" if sessions else "" heal_m = int(os.environ.get("CK_CANARY_HEAL_M", "5")) streak = st.get("heal_streak", 0) - heal = (f"\n auto-heal: {streak} verificate consecutive " - f"dall'ultimo failure (auto-ack a {heal_m})" + heal = (f"\n auto-heal: {streak} consecutive verified " + f"since the last failure (auto-ack at {heal_m})" if os.environ.get("CK_CANARY_AUTOHEAL", "1") != "0" else "") - return (f" CANARY: ⚠ {fl} compressioni NON applicate dall'harness " - f"(ultima: {st.get('last_failure')}){where} — risparmi sovrastimati!\n" - f" {v} verificate ok, {pend} in attesa{hist}{deg}{heal}\n" - f" (indaga, poi: python3 savings.py --reset-canary)") + return (f" CANARY: ⚠ {fl} compressions NOT applied by the harness " + f"(last: {st.get('last_failure')}){where} — savings overstated!\n" + f" {v} verified ok, {pend} pending{hist}{deg}{heal}\n" + f" (investigate, then: python3 savings.py --reset-canary)") if v: - return (f" canary: ✓ {v} compressioni verificate applicate nel transcript " - f"(ultima: {st.get('last_ok')}), {pend} in attesa{hist}") + return (f" canary: ✓ {v} compressions verified as applied in the transcript " + f"(last: {st.get('last_ok')}), {pend} pending{hist}") if pend: - return f" canary: {pend} compressioni in attesa di verifica{hist}" + return f" canary: {pend} compressions awaiting verification{hist}" return None @@ -184,12 +184,12 @@ def ab_status() -> str | None: pend = len(st.get("pending", [])) if not (ok or deg or pend): return None - line = f" A/B invariance: {ok} invarianti, {deg} degradate" + line = f" A/B invariance: {ok} invariant, {deg} degraded" if deg: - line = f" A/B invariance: ⚠ {deg} degradate su {ok + deg} giudicate" + line = f" A/B invariance: ⚠ {deg} degraded out of {ok + deg} judged" if pend: - line += (f", {pend} campioni in attesa " - f"(giudica: python3 hooks/ab_verify.py)") + line += (f", {pend} samples pending " + f"(judge them: python3 hooks/ab_verify.py)") return line @@ -260,7 +260,7 @@ def statusline() -> int: # CK_STATUSLINE_VERBOSE=1. L'allarme canary invece resta SEMPRE: e' un # allarme, non un contatore. verbose = os.environ.get("CK_STATUSLINE_VERBOSE", "0") == "1" - core = f"-{_fmt_k(mine)} sessione" if verbose else f"-{_fmt_k(mine)}" + core = f"-{_fmt_k(mine)} session" if verbose else f"-{_fmt_k(mine)}" if verbose: # "-N sessione" da solo non dice quanto pesa: rapportarlo al contesto # che ci SAREBBE stato senza compressione (ctx attuale + risparmiato, @@ -272,10 +272,10 @@ def statusline() -> int: (json.load(f).get(sess) or {}).get("context_tokens") or 0) if mine and ctx: would_be = ctx + mine - core += f" (-{mine / would_be:.0%} su ctx ~{_fmt_k(would_be)})" + core += f" (-{mine / would_be:.0%} of ctx ~{_fmt_k(would_be)})" except Exception: # noqa: BLE001 pass - core += f" · -{_fmt_k(tot)} totale" + core += f" · -{_fmt_k(tot)} total" if tot and tot_before: core += f" (-{tot / tot_before:.0%})" elif tot and tot_before: @@ -294,7 +294,7 @@ def statusline() -> int: with open(AB_STATE, encoding="utf-8") as f: pend = len(json.load(f).get("pending") or []) if pend: - seg += f" · {yellow}A/B: {pend} in attesa{reset}" + seg += f" · {yellow}A/B: {pend} pending{reset}" except Exception: # noqa: BLE001 pass # Lato distorsione, in grigio: i fault non sono allarmi (il recupero @@ -399,7 +399,7 @@ def _svg_cumulative(rows: list[tuple]) -> str: continue pts.append((t, cum)) if len(pts) < 2: - return "

(servono almeno 2 compressioni datate)

" + return "

(at least 2 timestamped compressions needed)

" if len(pts) > 400: # tenere leggero l'HTML step = len(pts) // 400 + 1 pts = pts[::step] + [pts[-1]] @@ -423,7 +423,7 @@ def Y(v): return PT + (1 - v / y1) * (H - PT - PB) f"data-tip='{_dt.fromtimestamp(t).strftime('%d/%m %H:%M')} — " f"-{v:,} token'/>" for t, v in pts) return (f"" + f"aria-label='cumulative curve of tokens saved'>" + "".join(gy) + f"" + f"" @@ -438,12 +438,12 @@ def _svg_hbars(items: list[tuple[str, int]], unit: str = "token") -> str: """Barre orizzontali di una misura (un solo hue, identita' dalle etichette). 4px di arrotondamento solo sul data-end, 2px di gap.""" if not items: - return "

(nessun dato)

" + return "

(no data)

" vmax = max(v for _, v in items) or 1 ROW, BAR, PL, W = 26, 16, 150, 800 H = len(items) * ROW + 6 parts = [f""] + f"aria-label='bars by {unit}'>"] for i, (name, v) in enumerate(items): y = i * ROW + 4 w = max(2, v / vmax * (W - PL - 80)) @@ -504,15 +504,15 @@ def _load(path): canary = _load(CANARY_STATE) ab = _load(AB_STATE) c_failed = canary.get("failed", 0) - c_cls, c_icon, c_txt = ("ok", "✓", f"{canary.get('verified', 0)} verificate") + c_cls, c_icon, c_txt = ("ok", "✓", f"{canary.get('verified', 0)} verified") if c_failed: - c_cls, c_icon, c_txt = ("crit", "✗", f"{c_failed} NON applicate") + c_cls, c_icon, c_txt = ("crit", "✗", f"{c_failed} NOT applied") ab_deg = ab.get("degraded", 0) ab_pend = len(ab.get("pending") or []) ab_cls, ab_icon = ("warn", "⚠") if ab_deg else ("ok", "✓") - ab_txt = f"{ab.get('ok', 0)} invarianti, {ab_deg} degradate" + ab_txt = f"{ab.get('ok', 0)} invariant, {ab_deg} degraded" if ab_pend: - ab_txt += f", {ab_pend} in attesa" + ab_txt += f", {ab_pend} pending" # lato distorsione: token rientrati via page fault + breakdown per tipo f_n, f_tok, f_kind, _f_bucket = read_faults() @@ -524,26 +524,26 @@ def _load(path): f"
{_esc(t)}{n:,}{v:,}
" for t, v, n in ((t, v, sum(1 for r in rows if r[1] == t)) for t, v in tools)) - html = f"""
+ html = f""" -context-kernel — risparmio token +context-kernel — token savings

context-kernel

-

{len(rows):,} compressioni · {rows[0][0][:16] if rows else '—'} → {rows[-1][0][:16] if rows else '—'}{f' · di cui {sub_n:,} in subagent (~{sub_saved:,} token)' if sub_n else ''}

+

{len(rows):,} compressions · {rows[0][0][:16] if rows else '—'} → {rows[-1][0][:16] if rows else '—'}{f' · {sub_n:,} of them in subagents (~{sub_saved:,} tokens)' if sub_n else ''}

-
-{_fmt_k(saved)}
token risparmiati
-
-{pct:.0%}
degli output toccati
-
{len(rows):,}
compressioni
+
-{_fmt_k(saved)}
tokens saved
+
-{pct:.0%}
of the outputs touched
+
{len(rows):,}
compressions
{c_icon}canary
{_esc(c_txt)}
{ab_icon}A/B
{_esc(ab_txt)}
-
{f'-{_fmt_k(f_tok)}' if f_tok else '0'}
rientrati via fault{f' ({f_pct:.0%} del risparmio)' if f_tok else ''}
+
{f'-{_fmt_k(f_tok)}' if f_tok else '0'}
clawed back via faults{f' ({f_pct:.0%} of the savings)' if f_tok else ''}
-

Risparmio cumulativo

{_svg_cumulative(rows)}
-

Per tool

{_svg_hbars(tools)}
-

Per sessione (top 8)

{_svg_hbars(sessions)}
-

Distorsione — token rientrati via page fault ({f_n})

{_svg_hbars(f_bars) if f_bars else "

(nessun page fault registrato — nessuna scommessa persa)

"}
-

Tabella

- +

Cumulative savings

{_svg_cumulative(rows)}
+

By tool

{_svg_hbars(tools)}
+

By session (top 8)

{_svg_hbars(sessions)}
+

Distortion — tokens clawed back via page faults ({f_n})

{_svg_hbars(f_bars) if f_bars else "

(no page faults recorded — no bet lost)

"}
+

Table

+
toolcompressionitoken risparmiati
{table}
toolcompressionstokens saved
@@ -566,7 +566,7 @@ def main() -> int: if "--reset-canary" in sys.argv[1:]: return reset_canary() if not os.path.exists(LOG_PATH): - print(f"Nessun log ancora ({LOG_PATH}). Usa il plugin e ritorna qui.") + print(f"No log yet ({LOG_PATH}). Use the plugin and come back here.") return 0 n = 0 @@ -602,7 +602,7 @@ def main() -> int: last = ts if n == 0: - print("Log presente ma vuoto.") + print("Log present but empty.") return 0 saved = before_tot - after_tot @@ -610,26 +610,26 @@ def main() -> int: # stima costo input Opus 4.8: $5 / 1M token dollars = saved / 1_000_000 * 5.0 - print(f"context-kernel — risparmio token ({first} -> {last})") + print(f"context-kernel — token savings ({first} -> {last})") print("=" * 56) - print(f" compressioni: {n}") - print(f" token in ingresso: {before_tot:,}") - print(f" token dopo: {after_tot:,}") - print(f" RISPARMIATI: {saved:,} (-{pct:.0%})") - print(f" ~costo input evitato (Opus 4.8, prima lettura): ${dollars:.2f}") - print(f" (si somma coi cache-read a ogni turno successivo)") - print("\n per tool:") + print(f" compressions: {n}") + print(f" tokens in: {before_tot:,}") + print(f" tokens after: {after_tot:,}") + print(f" SAVED: {saved:,} (-{pct:.0%})") + print(f" ~input cost avoided (Opus 4.8, first read): ${dollars:.2f}") + print(f" (compounds with cache reads on every subsequent turn)") + print("\n by tool:") for tool, (b, a, c) in sorted(per_tool.items(), key=lambda x: -(x[1][0] - x[1][1])): - print(f" {tool:10s} {c:4d}x -{b - a:,} token") + print(f" {tool:10s} {c:4d}x -{b - a:,} tokens") named = {s: v for s, v in per_session.items() if s != "-"} if named: - print("\n per sessione (anche le headless concorrenti scrivono qui):") + print("\n by session (concurrent headless runs write here too):") for sess, (sv, c) in sorted(named.items(), key=lambda x: -x[1][0])[:8]: - print(f" {sess:10s} {c:4d}x -{sv:,} token") + print(f" {sess:10s} {c:4d}x -{sv:,} tokens") if per_session.get("-"): sv, c = per_session["-"] - print(f" {'(storico)':10s} {c:4d}x -{sv:,} token") + print(f" {'(historical)':10s} {c:4d}x -{sv:,} tokens") status = canary_status() if status: print() diff --git a/claude-context-kernel/hooks/session_brief.py b/claude-context-kernel/hooks/session_brief.py index 9aa01ff..d641000 100644 --- a/claude-context-kernel/hooks/session_brief.py +++ b/claude-context-kernel/hooks/session_brief.py @@ -51,7 +51,7 @@ def savings_line() -> str: n += 1 saved += int(parts[4]) if n: - return f" Finora: {n} compressioni, ~{saved:,} token risparmiati." + return f" So far: {n} compressions, ~{saved:,} tokens saved." except Exception: # noqa: BLE001 pass return "" @@ -66,7 +66,7 @@ def ab_line() -> str: if n: root = (os.environ.get("CLAUDE_PLUGIN_ROOT") or os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - return (f" A/B: {n} campioni in attesa di giudizio — `python3 " + return (f" A/B: {n} samples awaiting judgement — `python3 " f"{os.path.join(root, 'hooks', 'ab_verify.py')}`.") except Exception: # noqa: BLE001 pass @@ -86,11 +86,11 @@ def canary_line() -> str: return "" streak = st.get("heal_streak", 0) ndeg = len(st.get("degraded_sessions", [])) - deg = f", {ndeg} sessioni in auto-degrade" if ndeg else "" - return (f" Canary: {fl} failure aperti " - f"(ultimo: {st.get('last_failure')}), {streak} compressioni " - f"verificate OK dopo{deg} — se l'evidenza continua si " - "auto-riconoscono, se ricompaiono indaga.") + deg = f", {ndeg} sessions auto-degraded" if ndeg else "" + return (f" Canary: {fl} open failures " + f"(last: {st.get('last_failure')}), {streak} compressions " + f"verified OK since{deg} — if the evidence keeps up they " + "auto-acknowledge; if they reappear, investigate.") except Exception: # noqa: BLE001 pass return "" @@ -111,14 +111,14 @@ def compact_restore(payload: dict) -> str: rec = max(st.values(), key=lambda r: r.get("ts", 0), default=None) if not rec or time.time() - rec.get("ts", 0) > COMPACT_MAX_AGE_S: return "" - parts = ["\n[context-kernel] TS(Q) sopravvissuto alla compaction — " - "il riassunto e' una proiezione NON indicizzata dal task; " - "questo e' lo stato del task fotografato prima:"] + parts = ["\n[context-kernel] TS(Q) survived the compaction — " + "the summary is a projection NOT indexed by the task; " + "this is the task state snapshotted beforehand:"] if rec.get("charter_head"): - parts.append("--- carta del task (T3) attiva ---\n" + parts.append("--- active task charter (T3) ---\n" + rec["charter_head"]) if rec.get("slice_head"): - parts.append("--- working set (T2) attivo ---\n" + parts.append("--- active working set (T2) ---\n" + rec["slice_head"]) return "\n".join(parts) except Exception: # noqa: BLE001 @@ -155,15 +155,14 @@ def resume_restore(payload: dict) -> str: slice_head = rec.get("slice_head") or "" if not charter_head and not slice_head: return "" - parts = ["\n[context-kernel] TS(Q) della sessione precedente su " - "questo repo (il riavvio e' una discontinuita' come la " - "compaction — il task state sopravvive a entrambe). Se il " - "task e' cambiato, ignora ed eventualmente pulisci con " - "charter.py clear:"] + parts = ["\n[context-kernel] TS(Q) from the previous session on this " + "repo (a restart is a discontinuity just like a compaction — " + "the task state survives both). If the task has changed, " + "ignore this and optionally clear it with charter.py clear:"] if charter_head: - parts.append("--- carta del task (T3) attiva ---\n" + charter_head) + parts.append("--- active task charter (T3) ---\n" + charter_head) if slice_head: - parts.append("--- working set (T2) dell'ultima sessione ---\n" + parts.append("--- working set (T2) from the last session ---\n" + slice_head) return "\n".join(parts) except Exception: # noqa: BLE001 @@ -182,12 +181,13 @@ def main() -> int: print("{}") return 0 ctx = ( - "[context-kernel] attivo: gli output lunghi dei tool arrivano " - "compressi (footer `[context-kernel: ...]`). Page fault: se una Read " - "arriva ELISA o marcata INVARIATO, rileggere lo stesso file la fa " - "passare integrale. Per bug con sintomo concreto c'e' la skill " - "kernel-repo-slice (T2); con un traceback nel prompt la slice viene " - "iniettata da sola." + savings_line() + ab_line() + canary_line() + "[context-kernel] active: long tool outputs arrive compressed " + "(footer `[context-kernel: ...]`). Page fault: if a Read arrives " + "ELIDED or marked UNCHANGED, reading the same file again lets it " + "through in full. For bugs with a concrete symptom there is the " + "kernel-repo-slice skill (T2); with a traceback in the prompt the " + "slice is injected automatically." + savings_line() + ab_line() + + canary_line() ) if payload.get("source") == "compact": ctx += compact_restore(payload) diff --git a/claude-context-kernel/hooks/session_end_snapshot.py b/claude-context-kernel/hooks/session_end_snapshot.py index 2e9e894..23f6d1b 100644 --- a/claude-context-kernel/hooks/session_end_snapshot.py +++ b/claude-context-kernel/hooks/session_end_snapshot.py @@ -80,7 +80,7 @@ def main() -> int: json.dump(st, f) os.replace(tmp, STATE) print("{}") - print(f"context-kernel[resume]: TS(Q) fotografato per {repo}", + print(f"context-kernel[resume]: TS(Q) snapshotted for {repo}", file=sys.stderr) return 0 except Exception: # noqa: BLE001 diff --git a/claude-context-kernel/hooks/smoke.py b/claude-context-kernel/hooks/smoke.py index af82b0e..6af7783 100644 --- a/claude-context-kernel/hooks/smoke.py +++ b/claude-context-kernel/hooks/smoke.py @@ -1,42 +1,41 @@ #!/usr/bin/env python3 """ -smoke.py — il rito di verifica LIVE, scriptato (1.17.0). - -Il dato empirico che questo file istituzionalizza: OGNI verifica dal vivo -delle release ha trovato bug che 300+ test non vedevano (additionalContext -ignorato, canary contro parcheggio, fixture nello store reale, ...) — -perche' i test esercitano gli operatori, il rito esercita il CONTRATTO con -l'harness reale. Da qui: una release non e' "verde" finche' lo smoke non -passa in una sessione vera. - -Protocollo a DUE comandi, da eseguire in una sessione Claude Code viva -su questo repo (Bash normale, senza `# ck:raw` sul generate): - - python3 hooks/smoke.py generate # 400 righe con AGO CALCOLATO a - # runtime (mai nel comando, mai nel - # contesto) — il hook la comprime - python3 hooks/smoke.py check # verifica sul TRANSCRIPT REALE - # cio' che l'harness ha DAVVERO fatto - -Cosa asserisce `check` (PASS/FAIL per punto, exit != 0 su ogni FAIL): - 1. il tool_result del generate sta nel transcript della sessione; - 2. e' la versione COMPRESSA (footer presente: updatedToolOutput onorato); - 3. l'ago e' stato ELISO dal contesto; - 4. il footer dichiara il parcheggio e la chiave; - 5. la chiave esiste nello store del parcheggio; - 6. recall.py KEY --grep ritrova l'ago, numerato (page fault inverso); - 7. il canary non ha accumulato NUOVI failed dal generate (niente falsi - allarmi sul contratto); - 8. advisor in 4 punti sul context state REALE della sessione (avviso a - soglia bassa; one-shot; subagent muto; soglia alta muta) — SKIP - dichiarato se il tap della sessione non e' ancora stato scritto. - -Copertura DICHIARATA: la gamba effimera e' il Bash (rappresentativo: -stessa via di parcheggio di WebFetch/MCP); compact reale, resume e guardie -restano al rito manuale (richiedono eventi harness non scriptabili da qui). - -Stato tra i due comandi: CK_SMOKE_STATE (default ~/.context-kernel-smoke -.json) — id univoco del lotto, ago, snapshot canary. Zero rete, zero API. +smoke.py — the LIVE verification ritual, scripted (1.17.0). + +The empirical fact this file institutionalises: EVERY live check of a release +has found bugs that 300+ tests did not see (additionalContext ignored, canary +vs parking, fixtures in the real store, ...) — because the tests exercise the +operators, while the ritual exercises the CONTRACT with the real harness. +Hence: a release is not "green" until the smoke passes in a real session. + +A TWO-command protocol, to be run in a live Claude Code session on this repo +(plain Bash, without `# ck:raw` on the generate step): + + python3 hooks/smoke.py generate # 400 lines with a NEEDLE COMPUTED at + # runtime (never in the command, never + # in the context) — the hook compresses it + python3 hooks/smoke.py check # checks against the REAL TRANSCRIPT + # what the harness ACTUALLY did + +What `check` asserts (PASS/FAIL per item, exit != 0 on any FAIL): + 1. the generate tool_result is in the session transcript; + 2. it is the COMPRESSED version (footer present: updatedToolOutput honoured); + 3. the needle was ELIDED from the context; + 4. the footer declares the parking spot and the key; + 5. the key exists in the parking store; + 6. recall.py KEY --grep finds the needle, numbered (inverse page fault); + 7. the canary accumulated no NEW failures since generate (no false alarms + on the contract); + 8. a 4-point advisor check against the session's REAL context state (warning + at a low threshold; one-shot; subagent silent; high threshold silent) — + declared SKIP if the session tap has not been written yet. + +DECLARED coverage: the ephemeral leg is Bash (representative: same parking +path as WebFetch/MCP); a real compact, resume and the guards stay in the +manual ritual (they need harness events that cannot be scripted from here). + +State between the two commands: CK_SMOKE_STATE (default ~/.context-kernel-smoke +.json) — unique batch id, needle, canary snapshot. Zero network, zero API. """ from __future__ import annotations @@ -84,20 +83,20 @@ def generate() -> int: run_id = digest[:8] # ago DECIMALE (niente hex: non deve somigliare a hash/segnale) e # formulazione senza parole di segnale (error/warn/fail/path) - needle = f"sentinella-{int(digest, 16) % 100_000:05d}" + needle = f"sentinel-{int(digest, 16) % 100_000:05d}" state = {"ts": time.time(), "id": run_id, "needle": needle, "canary_failed": _canary_failed()} tmp = f"{SMOKE_STATE}.tmp.{os.getpid()}" with open(tmp, "w", encoding="utf-8") as f: json.dump(state, f) os.replace(tmp, SMOKE_STATE) - print(f"smoke context-kernel — lotto {run_id} — inizio") + print(f"smoke context-kernel — batch {run_id} — start") for i in range(2, N_LINES): if i == NEEDLE_AT: - print(f"riga {i:03d} — {needle} registrata nel lotto notturno") + print(f"line {i:03d} — {needle} recorded in the nightly batch") else: - print(f"riga {i:03d} — elaborazione lotto completata senza variazioni") - print(f"smoke context-kernel — lotto {run_id} — fine") + print(f"line {i:03d} — batch processing completed with no changes") + print(f"smoke context-kernel — batch {run_id} — end") return 0 @@ -118,7 +117,7 @@ def _result_text(obj: dict) -> str | None: def _find_result(run_id: str) -> tuple[str, str] | None: """(transcript_path, testo del tool_result del generate) — cerca il lotto per id nei transcript recenti, dal piu' fresco.""" - marker = f"lotto {run_id}" + marker = f"batch {run_id}" cands = [] for base, _dirs, files in os.walk(TRANSCRIPTS): for fn in files: @@ -153,9 +152,9 @@ def _advisor_checks(transcript: str) -> tuple[str, list[str]]: with open(CONTEXT_STATE, encoding="utf-8") as f: rec = (json.load(f) or {}).get(sid) or {} if int(rec.get("context_tokens") or 0) <= 0: - return "SKIP", ["tap della sessione non ancora scritto"] + return "SKIP", ["session tap not written yet"] except Exception: # noqa: BLE001 - return "SKIP", ["context state assente"] + return "SKIP", ["context state missing"] adv = os.path.join(HOOKS, "compact_advisor.py") iso = f"{SMOKE_STATE}.advise.{os.getpid()}" payload = json.dumps({"tool_name": "Bash", "transcript_path": transcript}) @@ -172,31 +171,31 @@ def run(threshold: str, pl: str = payload, state: str | None = None) -> str: [sys.executable, adv], input=pl, capture_output=True, text=True, env=env, timeout=30).stdout.strip() except Exception: # noqa: BLE001 - return "" + return "" details, ok = [], True first = run("0.05") if "additionalContext" in first and "/compact" in first: - details.append("avviso a soglia bassa: PASS") + details.append("warning at low threshold: PASS") else: - details.append(f"avviso a soglia bassa: FAIL ({first[:80]})") + details.append(f"warning at low threshold: FAIL ({first[:80]})") ok = False if run("0.05") == "{}": - details.append("one-shot per sessione: PASS") + details.append("one-shot per session: PASS") else: - details.append("one-shot per sessione: FAIL") + details.append("one-shot per session: FAIL") ok = False sub = json.dumps({"tool_name": "Bash", "transcript_path": transcript, "agent_id": "smoke-sub"}) if run("0.05", pl=sub, state=iso + ".b") == "{}": - details.append("subagent muto: PASS") + details.append("subagent silent: PASS") else: - details.append("subagent muto: FAIL") + details.append("subagent silent: FAIL") ok = False if run("0.99", state=iso + ".c") == "{}": - details.append("soglia alta muta: PASS") + details.append("high threshold silent: PASS") else: - details.append("soglia alta muta: FAIL") + details.append("high threshold silent: FAIL") ok = False for suffix in ("", ".b", ".c"): try: @@ -211,73 +210,73 @@ def check() -> int: with open(SMOKE_STATE, encoding="utf-8") as f: st = json.load(f) except Exception: # noqa: BLE001 - print("FAIL stato smoke assente: eseguire prima `smoke.py generate`") + print("FAIL smoke state missing: run `smoke.py generate` first") return 1 run_id, needle = st["id"], st["needle"] results: list[tuple[str, str]] = [] found = _find_result(run_id) if not found: - print(f"FAIL lotto {run_id} non trovato in nessun transcript " - f"recente sotto {TRANSCRIPTS} — sessione diversa, o il " - "risultato non e' (ancora) stato scritto") + print(f"FAIL batch {run_id} not found in any recent transcript " + f"under {TRANSCRIPTS} — different session, or the result " + "has not been written (yet)") return 1 transcript, text = found - results.append(("PASS", f"lotto {run_id} trovato nel transcript " + results.append(("PASS", f"batch {run_id} found in transcript " f"{os.path.basename(transcript)}")) - if "[context-kernel:" in text and "token, -" in text: - results.append(("PASS", "tool_result COMPRESSO nel transcript " - "(updatedToolOutput onorato dall'harness)")) + if "[context-kernel:" in text and "tokens, -" in text: + results.append(("PASS", "tool_result COMPRESSED in the transcript " + "(updatedToolOutput honoured by the harness)")) else: - results.append(("FAIL", "tool_result INTEGRALE nel transcript: " - "l'harness ha ignorato updatedToolOutput")) + results.append(("FAIL", "tool_result FULL in the transcript: " + "the harness ignored updatedToolOutput")) if needle not in text: - results.append(("PASS", "ago eliso dal contesto")) + results.append(("PASS", "needle elided from the context")) else: - results.append(("FAIL", "ago ancora presente: nessuna elisione " - "(soglie? plugin disattivo?)")) + results.append(("FAIL", "needle still present: no elision " + "(thresholds? plugin disabled?)")) - m = re.search(r"parcheggiato: python3 .*recall\.py\"? ([0-9a-f]{10})", text) + m = re.search(r"parked: python3 .*recall\.py\"? ([0-9a-f]{10})", text) key = m.group(1) if m else None if key: - results.append(("PASS", f"footer dichiara il parcheggio (chiave {key})")) + results.append(("PASS", f"footer declares the parking spot (key {key})")) try: with open(PARK_STATE, encoding="utf-8") as f: in_store = key in (json.load(f) or {}) except Exception: # noqa: BLE001 in_store = False - results.append(("PASS", "chiave presente nello store") if in_store - else ("FAIL", "chiave assente dallo store del parcheggio")) + results.append(("PASS", "key present in the store") if in_store + else ("FAIL", "key missing from the parking store")) rec = subprocess.run( [sys.executable, os.path.join(HOOKS, "recall.py"), - key, "--grep", "sentinella"], + key, "--grep", "sentinel"], capture_output=True, text=True, timeout=30) if needle in rec.stdout and str(NEEDLE_AT) in rec.stdout: - results.append(("PASS", "recall --grep ritrova l'ago, numerato " - "(page fault inverso funzionante)")) + results.append(("PASS", "recall --grep finds the needle, numbered " + "(inverse page fault working)")) else: - results.append(("FAIL", "recall non ritrova l'ago " + results.append(("FAIL", "recall does not find the needle " f"({rec.stdout[:80]!r})")) else: - results.append(("FAIL", "footer senza hint di parcheggio")) + results.append(("FAIL", "footer without a parking hint")) failed_now = _canary_failed() if failed_now <= st.get("canary_failed", 0): - results.append(("PASS", "canary: nessun nuovo failed dal generate")) + results.append(("PASS", "canary: no new failures since generate")) else: results.append(("FAIL", f"canary: failed {st.get('canary_failed', 0)}" - f" -> {failed_now} — indagare PRIMA di fidarsi del ledger")) + f" -> {failed_now} — investigate BEFORE trusting the ledger")) verdict, details = _advisor_checks(transcript) - results.append((verdict, "advisor (4 punti): " + "; ".join(details))) + results.append((verdict, "advisor (4 points): " + "; ".join(details))) bad = 0 for v, msg in results: print(f"{v:5s} {msg}") bad += int(v == "FAIL") - print(f"\nsmoke: {len(results) - bad}/{len(results)} punti superati" - + ("" if not bad else f", {bad} FALLITI")) + print(f"\nsmoke: {len(results) - bad}/{len(results)} points passed" + + ("" if not bad else f", {bad} FAILED")) return 1 if bad else 0 diff --git a/claude-context-kernel/hooks/symptom_slice.py b/claude-context-kernel/hooks/symptom_slice.py index 8451426..59a638a 100644 --- a/claude-context-kernel/hooks/symptom_slice.py +++ b/claude-context-kernel/hooks/symptom_slice.py @@ -170,15 +170,15 @@ def task_switch_note(session: str, repo: str, out: str) -> str | None: if fresh: shown = ", ".join(fresh[:8]) more = f" (+{len(fresh) - 8})" if len(fresh) > 8 else "" - detail = (f" File richiesti da Q2 assenti dal working set " - f"precedente: {shown}{more}.") + detail = (f" Files required by Q2 that are absent from the " + f"previous working set: {shown}{more}.") if dropped: - detail += (f" {dropped} file del working set precedente non " - f"servono piu' a Q2.") - return ("[context-kernel] CAMBIO TASK rilevato (Q1 -> Q2): il sintomo " - "differisce da quello per cui era stato calcolato il working " - "set precedente. La proiezione era indicizzata su Q1 e non ha " - "garanzie su Q2 — il manifest qui sopra la SOSTITUISCE come " + detail += (f" {dropped} files from the previous working set are " + f"no longer needed for Q2.") + return ("[context-kernel] TASK SWITCH detected (Q1 -> Q2): the symptom " + "differs from the one the previous working set was computed " + "for. That projection was indexed on Q1 and carries no " + "guarantee for Q2 — the manifest above REPLACES it as the " "prior." + detail) except Exception: # noqa: BLE001 return None @@ -218,10 +218,11 @@ def main() -> int: print("{}") # niente seed: meglio tacere return 0 head = "\n".join(out.split("\n")[:MAX_LINES]) - ctx = ("[context-kernel] Sintomo rilevato nel prompt: working set " - "calcolato dallo slicer deterministico (T2). Usalo come prior " - "per l'esplorazione, non come divieto; per rifarlo con altri " - "parametri c'e' la skill kernel-repo-slice.\n" + head) + ctx = ("[context-kernel] Symptom detected in the prompt: working set " + "computed by the deterministic slicer (T2). Use it as a prior " + "for exploration, not as a prohibition; to recompute it with " + "different parameters there is the kernel-repo-slice skill.\n" + + head) session = hook_session(payload) note = task_switch_note(session, cwd, out) if note: @@ -231,7 +232,7 @@ def main() -> int: "additionalContext": ctx, }})) task_remember(session, cwd, out) - print(f"context-kernel[symptom]: slice iniettata da {cwd}", + print(f"context-kernel[symptom]: slice injected from {cwd}", file=sys.stderr) return 0 except Exception: # noqa: BLE001 diff --git a/claude-context-kernel/mcp/server.py b/claude-context-kernel/mcp/server.py index 2b6a986..0b3c462 100644 --- a/claude-context-kernel/mcp/server.py +++ b/claude-context-kernel/mcp/server.py @@ -40,23 +40,23 @@ TOOL = { "name": "kernel_slice", "description": ( - "Estrae la fetta minimale di un file (Python o Go) rilevante per uno o " - "piu' simboli (funzioni/classi/tipi), scartando tutto cio' che non puo' " - "influenzarne il comportamento (backward reachability slice sul grafo " - "def-use). Python: answer-preserving ESATTO (AST). Go: answer-preserving " - "CONSERVATIVO (senza parser, sovra-approssima l'uso -> puo' tenere di " - "piu', mai lasciar fuori una dipendenza). Usalo al posto di leggere un " - "file grande quando ti interessa solo un simbolo specifico." + "Extracts the minimal slice of a file (Python or Go) relevant to one or " + "more symbols (functions/classes/types), discarding everything that " + "cannot affect their behaviour (backward reachability slice over the " + "def-use graph). Python: EXACT answer-preserving (AST). Go: " + "CONSERVATIVE answer-preserving (no parser, over-approximates use -> " + "may keep more, never leaves out a dependency). Use it instead of " + "reading a large file when you only care about one specific symbol." ), "inputSchema": { "type": "object", "properties": { "file": {"type": "string", - "description": "Percorso del file .py o .go"}, + "description": "Path of the .py or .go file"}, "symbols": { "type": "array", "items": {"type": "string"}, - "description": "Nomi dei simboli target (almeno uno)", + "description": "Names of the target symbols (at least one)", }, }, "required": ["file", "symbols"], @@ -67,25 +67,25 @@ TOOL_REPO = { "name": "kernel_repo_slice", "description": ( - "Proietta un intero repository sul working set rilevante per un bug: " - "dato un sintomo (stack trace, messaggio d'errore, path), costruisce " - "il grafo degli import e tiene solo seed + dipendenze transitive + " - "importatori vicini + test correlati. Ritorna un manifest ordinato con " - "le motivazioni; cio' che e' fuori slice resta recuperabile on demand " - "(page fault). Usalo PRIMA di esplorare un repo grande per un bug." + "Projects a whole repository onto the working set relevant to a bug: " + "given a symptom (stack trace, error message, path), it builds the " + "import graph and keeps only seeds + transitive dependencies + nearby " + "importers + related tests. Returns an ordered manifest with the " + "reasons; whatever falls outside the slice stays recoverable on demand " + "(page fault). Use it BEFORE exploring a large repo for a bug." ), "inputSchema": { "type": "object", "properties": { - "repo": {"type": "string", "description": "Root del repository"}, + "repo": {"type": "string", "description": "Root of the repository"}, "symptom": { "type": "string", - "description": "Stack trace / messaggio d'errore / descrizione col path", + "description": "Stack trace / error message / description with the path", }, "seeds": { "type": "array", "items": {"type": "string"}, - "description": "File seed espliciti (opzionale, in aggiunta al sintomo)", + "description": "Explicit seed files (optional, in addition to the symptom)", }, }, "required": ["repo", "symptom"], @@ -98,9 +98,9 @@ def run_slice(args: dict) -> dict: file = args.get("file", "") symbols = args.get("symbols") or [] if not file or not symbols: - return _err_content("Servono 'file' e almeno un elemento in 'symbols'.") + return _err_content("Need 'file' and at least one element in 'symbols'.") if not os.path.exists(file): - return _err_content(f"File non trovato: {file}") + return _err_content(f"File not found: {file}") try: out = subprocess.run( [sys.executable, SLICE, file, *symbols], @@ -109,9 +109,9 @@ def run_slice(args: dict) -> dict: env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) except Exception as e: # noqa: BLE001 - return _err_content(f"Errore nell'esecuzione dello slicer: {e}") + return _err_content(f"Error running the slicer: {e}") if out.returncode != 0: - return _err_content(out.stderr.strip() or "slicer fallito") + return _err_content(out.stderr.strip() or "slicer failed") return {"content": [{"type": "text", "text": out.stdout}], "isError": False} @@ -120,9 +120,9 @@ def run_repo_slice(args: dict) -> dict: symptom = args.get("symptom", "") seeds = args.get("seeds") or [] if not repo or not symptom: - return _err_content("Servono 'repo' e 'symptom'.") + return _err_content("Need 'repo' and 'symptom'.") if not os.path.isdir(repo): - return _err_content(f"Repo non trovato: {repo}") + return _err_content(f"Repo not found: {repo}") cmd = [sys.executable, REPO_SLICE, repo, "--symptom", symptom] for s in seeds: cmd += ["--seed", s] @@ -131,9 +131,9 @@ def run_repo_slice(args: dict) -> dict: encoding="utf-8", errors="replace", env={**os.environ, "PYTHONIOENCODING": "utf-8"}) except Exception as e: # noqa: BLE001 - return _err_content(f"Errore nell'esecuzione del repo slicer: {e}") + return _err_content(f"Error running the repo slicer: {e}") if out.returncode != 0: - return _err_content(out.stderr.strip() or "repo slicer fallito") + return _err_content(out.stderr.strip() or "repo slicer failed") text = out.stdout if out.stderr.strip(): # es. "nessun seed": va mostrato text = out.stderr.strip() + "\n\n" + text @@ -172,11 +172,11 @@ def handle(req: dict) -> dict | None: return _ok(rid, run_slice(params.get("arguments") or {})) if name == TOOL_REPO["name"]: return _ok(rid, run_repo_slice(params.get("arguments") or {})) - return _err(rid, -32602, f"tool sconosciuto: {name}") + return _err(rid, -32602, f"unknown tool: {name}") if rid is None: return None # notifica sconosciuta: ignora - return _err(rid, -32601, f"metodo non gestito: {method}") + return _err(rid, -32601, f"unhandled method: {method}") def _ok(rid, result: dict) -> dict: diff --git a/claude-context-kernel/skills/kernel-repo-slice/scripts/repo_slice.py b/claude-context-kernel/skills/kernel-repo-slice/scripts/repo_slice.py index 1f684b5..5978848 100644 --- a/claude-context-kernel/skills/kernel-repo-slice/scripts/repo_slice.py +++ b/claude-context-kernel/skills/kernel-repo-slice/scripts/repo_slice.py @@ -562,7 +562,7 @@ def _test_ref_edges(root: str, files: list[str]) -> dict[str, set[str]]: def find_seeds(root: str, files: list[str], symptom: str, explicit: list[str], diff: list[str] | None = None, - diff_why: str = "file modificato nel diff", + diff_why: str = "file changed in the diff", ) -> list[tuple[str, str]]: """Ritorna [(file, motivo)]. Path dai frame + espliciti + file del diff + grep dei letterali.""" @@ -595,14 +595,14 @@ def match_path(token: str, why: str) -> None: return for e in explicit: - match_path(e, "seed esplicito") + match_path(e, "explicit seed") for e in diff or []: match_path(e, diff_why) for pat, why in ((PY_FRAME, "frame stack trace"), (JS_FRAME, "frame stack trace"), (PHP_FRAME, "frame stack trace"), (GO_FRAME, "frame stack trace"), - (BARE_PATH, "path nel sintomo")): + (BARE_PATH, "path in the symptom")): for m in pat.finditer(symptom): match_path(m.group(1), why) @@ -617,7 +617,7 @@ def match_path(token: str, why: str) -> None: continue if lit in open(path, encoding="utf-8", errors="replace").read(): seeds.setdefault(rel.replace(os.sep, "/"), - f'contiene il letterale "{lit[:40]}"') + f'contains the literal "{lit[:40]}"') hits += 1 if hits >= GREP_MAX_HITS: break @@ -652,7 +652,7 @@ def slice_repo(graph: dict[str, set[str]], seeds: list[str], for f in frontier: for d in sorted(norm.get(f, ())): if d not in kept: - kept[d] = ("dipendenza", hop, f) + kept[d] = ("dependency", hop, f) nxt.append(d) frontier = nxt @@ -662,7 +662,7 @@ def slice_repo(graph: dict[str, set[str]], seeds: list[str], for f in frontier: for imp in sorted(reverse.get(f, ())): if imp not in kept and not TEST_PAT.search(imp): - kept[imp] = ("importatore", hop, f) # i test li etichetta lo stadio dopo + kept[imp] = ("importer", hop, f) # i test li etichetta lo stadio dopo nxt.append(imp) frontier = nxt @@ -680,13 +680,13 @@ def slice_repo(graph: dict[str, set[str]], seeds: list[str], # --- output ----------------------------------------------------------------- -ORDER = {"seed": 0, "dipendenza": 1, "importatore": 2, "test": 3} +ORDER = {"seed": 0, "dependency": 1, "importer": 2, "test": 3} # Estremi ancorati (lost-in-the-middle): il modello attende testa e coda, non il # mezzo. Ordine SOLO, non selezione: nulla aggiunto/tolto, safe per pi. Il seed # resta in testa; l'importatore (il caller — "la causa puo' stare nel caller") # sale all'estremo basso; i test (repro, non causa) scendono in mezzo, dove # l'attenzione e' minima. -ORDER_ANCHORED = {"seed": 0, "dipendenza": 1, "test": 2, "importatore": 3} +ORDER_ANCHORED = {"seed": 0, "dependency": 1, "test": 2, "importer": 3} def render(root: str, scanned: int, seeds: list[tuple[str, str]], @@ -713,11 +713,11 @@ def render(root: str, scanned: int, seeds: list[tuple[str, str]], "excluded": excluded, "truncated": truncated, "seeds": [{"path": p, "why": w} for p, w in seeds], "files": [{"path": p, "role": r, "hop": h, "via": v, - **({"grafo": "generico"} + **({"graph": "generic"} if os.path.splitext(p)[1] in GENERIC_EXTS else {}), - **({"freddo": cold[p]} if p in cold else {})} + **({"cold": cold[p]} if p in cold else {})} for p, (r, h, v) in rows], - "note": "esclusione = prior, non divieto: page fault on demand", + "note": "exclusion = prior, not prohibition: page fault on demand", **({"symbol_index": {"source": "ctags", "symbols": sym_count}} if sym_count else {}), **({"coverage_note": cov_note} if cov_note else {}), @@ -729,89 +729,92 @@ def render(root: str, scanned: int, seeds: list[tuple[str, str]], **({"budget": budget_note} if budget_note else {}), **({"t2b": {"total_tokens": t2b["total"], "fits": t2b["fits"], "slices": [{"seed": s, "symbols": sy, "tokens": tk, - "esito": e, "estrai": c} + "outcome": e, "extract": c} for s, sy, tk, e, c in t2b["entries"]]}} if t2b else {}), }, ensure_ascii=False, indent=1) pct = (1 - len(kept) / scanned) * 100 if scanned else 0.0 out = ["# kernel repo slice — manifest", - f"operatore: T2@{t2_version()}"] + f"operator: T2@{t2_version()}"] if budget_note: out.append(f"budget: {budget_note}") out += [ f"repo: {root}", - f"sorgenti scansionati: {scanned} | slice: {len(kept)} file (-{pct:.0f}%)"] + f"sources scanned: {scanned} | slice: {len(kept)} files (-{pct:.0f}%)"] gen_exts = sorted({os.path.splitext(p)[1] for p, _ in rows if os.path.splitext(p)[1] in GENERIC_EXTS}) if gen_exts: - promo = (f" — PROMOSSO da indice ctags ({sym_count} simboli univoci): " - "archi simbolo->definitore precisi, non piu' solo euristici" + promo = (f" — PROMOTED by a ctags index ({sym_count} unique symbols): " + "precise symbol->definer edges, no longer merely heuristic" if sym_count else "") - out.append("grafo generico (riferimenti testuali, garanzia dichiarata " - f"piu' debole di un import graph) per: {', '.join(gen_exts)}" + out.append("generic graph (textual references, a declared guarantee " + f"weaker than an import graph) for: {', '.join(gen_exts)}" + promo) - out += ["", "## seed (dal sintomo)"] - out += [f"- {p} <- {w}" for p, w in seeds] or ["- (nessuno)"] - slice_hdr = ("## file della slice (per rilevanza, estremi ancorati: " - "seed in testa, importatori/caller in coda; i test scendono " - "in mezzo — lost-in-the-middle)" - if anchor_ends else "## file della slice (per rilevanza)") + out += ["", "## seeds (from the symptom)"] + out += [f"- {p} <- {w}" for p, w in seeds] or ["- (none)"] + slice_hdr = ("## slice files (by relevance, ends anchored: seeds first, " + "importers/callers last; the tests move to the middle — " + "lost-in-the-middle)" + if anchor_ends else "## slice files (by relevance)") out += ["", slice_hdr] for p, (role, hop, via) in rows: detail = {"seed": "seed", - "dipendenza": f"dipendenza a {hop} hop (via {via})", - "importatore": f"importatore a {hop} hop di {via}", - "test": f"test correlato (usa {via})"}[role] + "dependency": f"dependency {hop} hop(s) away (via {via})", + "importer": f"importer {hop} hop(s) away from {via}", + "test": f"related test (uses {via})"}[role] if role != "seed" and os.path.splitext(p)[1] in GENERIC_EXTS: - detail += " [grafo generico]" + detail += " [generic graph]" if p in cold: - detail += (f" [freddo T5: mai aperto in {cold[p]} sessioni — " - "prior largo, resta in slice]") + detail += (f" [T5 cold: never opened in {cold[p]} sessions — " + "wide prior, kept in the slice]") out.append(f"- {p} — {detail}") if truncated: - out.append(f"- … altri {truncated} file in slice (alza --max-files)") + out.append(f"- … {truncated} more files in the slice (raise --max-files)") if t2b: - out += ["", "## T2b — slice per simbolo (budget file-level insoddisfacibile)"] + out += ["", "## T2b — per-symbol slice (file-level budget unsatisfiable)"] for s, sy, tk, esito, cmds in t2b["entries"]: if sy: - out.append(f"- {s} :: {', '.join(sy)} (~{tk} token)") + out.append(f"- {s} :: {', '.join(sy)} (~{tk} tokens)") else: - out.append(f"- {s} — {esito} (~{tk} token)") + out.append(f"- {s} — {esito} (~{tk} tokens)") for c in cmds: - out.append(f" estrai: {c}") - stato = "RIENTRA nel budget" if t2b["fits"] else "ancora oltre budget" - out.append(f"- totale simboli: ~{t2b['total']} token ({stato}). " - "Leggi le slice coi comandi sopra, NON i file interi; " - "page fault = risali al file solo se la slice non basta.") + out.append(f" extract: {c}") + stato = "FITS the budget" if t2b["fits"] else "still over budget" + out.append(f"- symbol total: ~{t2b['total']} tokens ({stato}). " + "Read the slices with the commands above, NOT the whole " + "files; page fault = fall back to the file only if the " + "slice is not enough.") if cov_note: - out += ["", "## copertura (reachability dinamica, dichiarata)", cov_note] + out += ["", "## coverage (dynamic reachability, declared)", cov_note] if dyn_blind: - out += ["", "## riferimenti dinamici non risolti (punti ciechi dichiarati)", - "import dinamici nei seed con argomento non letterale o fuori " - "repo: NON indovinati (regola FQCN). Il grafo non li segue — se " - "il bug e' dietro uno di questi, leggi il call site:"] + out += ["", "## unresolved dynamic references (declared blind spots)", + "dynamic imports in the seeds with a non-literal argument or " + "outside the repo: NOT guessed (FQCN rule). The graph does not " + "follow them — if the bug is behind one of these, read the " + "call site:"] out += [f"- {b}" for b in dyn_blind] if suf and suf[1]: cov, tot, faults = suf - out += ["", "## sufficienza (T4: distorsione predetta, sul grafo statico)"] + out += ["", "## sufficiency (T4: predicted distortion, over the static graph)"] if not faults: - out.append(f"SUFFICIENTE: la proiezione contiene tutta la chiusura " - f"answer-preserving dei seed ({tot} unita' di dipendenza). " - "Nessun page fault atteso dalla struttura statica.") + out.append(f"SUFFICIENT: the projection contains the whole " + f"answer-preserving closure of the seeds ({tot} " + "dependency units). No page fault expected from the " + "static structure.") else: shown = faults[:10] - out.append(f"INSUFFICIENTE per budget/limite: {cov}/{tot} unita' " - f"della chiusura answer-preserving presenti; {len(faults)} " - "proiettate via = PAGE FAULT ATTESI (rileggile se il " - "ragionamento le tocca, non indovinare):") + out.append(f"INSUFFICIENT for the budget/limit: {cov}/{tot} units " + f"of the answer-preserving closure present; {len(faults)} " + "projected away = EXPECTED PAGE FAULTS (re-read them if " + "the reasoning touches them, do not guess):") out += [f"- {f}" for f in shown] if len(faults) > len(shown): - out.append(f"- … altre {len(faults) - len(shown)} unita'") - out += ["", "## fuori slice (modello page-fault)", - f"{excluded} sorgenti esclusi dal grafo degli import. L'esclusione e' " - "un prior, non un divieto: se un file fuori slice sembra rilevante " - "(config, DI, import dinamici), leggilo comunque."] + out.append(f"- … {len(faults) - len(shown)} more units") + out += ["", "## outside the slice (page-fault model)", + f"{excluded} sources excluded by the import graph. The exclusion is " + "a prior, not a prohibition: if a file outside the slice looks " + "relevant (config, DI, dynamic imports), read it anyway."] return "\n".join(out) @@ -934,16 +937,16 @@ def auto_budget() -> tuple[int, str]: st = json.load(f) sid, rec = max(st.items(), key=lambda kv: kv[1].get("ts", 0)) except Exception: # noqa: BLE001 - return 30_000, "auto: nessuno stato contesto (hook T1 mai girato?) -> fallback 30k" + return 30_000, "auto: no context state (T1 hook never ran?) -> fallback 30k" used = int(rec.get("context_tokens", 0)) model = rec.get("model") or "?" win, _src = _resolve_window(model, used) head = max(0, win - used) budget = max(8_000, min(int(head * 0.4), BUDGET_MAX)) age_m = int((time.time() - rec.get("ts", 0)) / 60) - stale = f" (stima vecchia di {age_m}m)" if age_m > 30 else "" - return budget, (f"auto: sessione {sid}, modello {model}, finestra ~{win // 1000}k, " - f"in uso ~{used // 1000}k, headroom ~{head // 1000}k -> " + stale = f" (estimate {age_m}m old)" if age_m > 30 else "" + return budget, (f"auto: session {sid}, model {model}, window ~{win // 1000}k, " + f"in use ~{used // 1000}k, headroom ~{head // 1000}k -> " f"budget {budget // 1000}k{stale}") @@ -1084,7 +1087,7 @@ def t2b_symbol_slices(root: str, seeds: list[str], symptom: str): gt = _go_symbol_targets(sl, source, frame_lines.get(s, [])) if not gt: entries.append((s, [], whole, - "file intero (nessun simbolo dal sintomo)", [])) + "whole file (no symbol from the symptom)", [])) total += whole continue try: @@ -1093,7 +1096,7 @@ def t2b_symbol_slices(root: str, seeds: list[str], symptom: str): text = None if text is None: # split non fidato / fail-safe entries.append((s, [], whole, - "file intero (slice Go non fidata)", [])) + "whole file (Go slice not trusted)", [])) total += whole continue tok = len(text) // 4 @@ -1103,13 +1106,13 @@ def t2b_symbol_slices(root: str, seeds: list[str], symptom: str): total += tok continue if not s.endswith(".py") or sl is None: - entries.append((s, [], whole, "file intero (non Python/Go)", [])) + entries.append((s, [], whole, "whole file (not Python/Go)", [])) total += whole continue tg = _symbol_targets(source, frame_lines.get(s, [])) if not tg["top"] and not tg["methods"]: entries.append((s, [], whole, - "file intero (nessun simbolo dal sintomo)", [])) + "whole file (no symbol from the symptom)", [])) total += whole continue labels: list[str] = [] @@ -1119,7 +1122,7 @@ def t2b_symbol_slices(root: str, seeds: list[str], symptom: str): for cls, met, a, b in tg["methods"]: seg = "\n".join(src_lines[a - 1:b]) tok += len(seg) // 4 - labels.append(f"{cls}.{met} (righe {a}-{b})") + labels.append(f"{cls}.{met} (lines {a}-{b})") cmds.append(f"sed -n '{a},{b}p' {full_path}") if tg["top"]: try: @@ -1128,14 +1131,14 @@ def t2b_symbol_slices(root: str, seeds: list[str], symptom: str): text = None if text is None: tok += whole - labels.append("(slice fallita: file intero)") + labels.append("(slice failed: whole file)") cmds.append(f"cat {full_path}") else: tok += len(text) // 4 labels += sorted(tg["top"]) cmds.append(f"python3 {SLICE_PY} {full_path} " + " ".join(sorted(tg["top"]))) - esito = "metodi" if tg["methods"] and not tg["top"] else "slice" + esito = "methods" if tg["methods"] and not tg["top"] else "slice" entries.append((s, labels, tok, esito, cmds)) total += tok return entries, total @@ -1164,9 +1167,9 @@ def slice_within_budget(root, graph, seeds, refs, budget: int, symptom: str = "" kept = slice_repo(graph, seeds, imp_d, refs, deps_d) tok = _slice_tokens(root, kept) if tok <= budget: - nota = (f"<= {_k(budget)} token: scelta config deps=" + nota = (f"<= {_k(budget)} tokens: chose config deps=" f"{deps_d or 'full'} imp={imp_d} " - f"({len(kept)} file, {_k(tok)} token)") + f"({len(kept)} files, {_k(tok)} tokens)") # il primo-che-rientra e' un criterio di CENSO, non di merito: # se nella famiglia c'e' una config piu' piccola ANCORA # sufficiente (gap vuoto), e' lei l'argmin — vedi min_sufficient @@ -1181,17 +1184,17 @@ def slice_within_budget(root, graph, seeds, refs, budget: int, symptom: str = "" minimal[f] = (role, hop, via) tok = _slice_tokens(root, minimal) if tok <= budget: - return minimal, (f"<= {_k(budget)} token: fallback minimo seed+test " - f"({len(minimal)} file, {_k(tok)} token)"), None + return minimal, (f"<= {_k(budget)} tokens: minimal seed+test fallback " + f"({len(minimal)} files, {_k(tok)} tokens)"), None entries, t2b_tok = t2b_symbol_slices(root, seeds, symptom) t2b = {"entries": entries, "total": t2b_tok, "fits": t2b_tok <= budget} if t2b["fits"]: - nota = (f"file-level {_k(tok)} token > budget {_k(budget)} -> T2b: " - f"slice per SIMBOLO sui seed = {_k(t2b_tok)} token (rientra)") + nota = (f"file-level {_k(tok)} tokens > budget {_k(budget)} -> T2b: " + f"per-SYMBOL slice of the seeds = {_k(t2b_tok)} tokens (fits)") else: - nota = (f"{_k(budget)} token INSODDISFACIBILE anche per simbolo: " - f"minimo file {_k(tok)}, minimo simbolo {_k(t2b_tok)} — " - f"restituito il minimo file-level") + nota = (f"{_k(budget)} tokens UNSATISFIABLE even per symbol: " + f"file minimum {_k(tok)}, symbol minimum {_k(t2b_tok)} — " + f"returned the file-level minimum") return minimal, nota, t2b @@ -1252,9 +1255,10 @@ def min_sufficient(root, graph, seeds, refs, kept, budget=0): if best is None: return None tok, cand, deps_d, imp_d = best - return cand, (f"argmin sufficiente: deps={deps_d or 'full'} imp={imp_d} " - f"({len(cand)} file, {_k(tok)} token, " - f"-{1 - tok / cur_tok:.0%} vs scelta iniziale, 0 fault attesi)") + return cand, (f"sufficient argmin: deps={deps_d or 'full'} imp={imp_d} " + f"({len(cand)} files, {_k(tok)} tokens, " + f"-{1 - tok / cur_tok:.0%} vs the initial choice, " + f"0 expected faults)") # --- prior appresi (loop T5 -> T2) -------------------------------------------- @@ -1288,8 +1292,8 @@ def prior_seeds(priors: dict | None, fileset: set[str], for s in (priors or {}).get("seeds") or []: p = str(s.get("path") or "").replace(os.sep, "/") if p and p in fileset and p not in have: - out.append((p, "prior appreso (T5: aperto fuori slice in " - f"{s.get('sessions', '?')} sessioni)")) + out.append((p, "learned prior (T5: opened outside the slice in " + f"{s.get('sessions', '?')} sessions)")) return out @@ -1374,11 +1378,11 @@ def dynamic_seeds(root: str, seed_files: list[str], files: list[str], continue ln = getattr(node, "lineno", 0) if mod is None: - blind.append(f"{rel}:{ln} (argomento non letterale)") + blind.append(f"{rel}:{ln} (non-literal argument)") continue hit = _resolve_dyn(mod, mm) if hit and hit not in have and hit not in added: - added[hit] = (f'riferimento dinamico: import "{mod}" ' + added[hit] = (f'dynamic reference: import "{mod}" ' f"({rel}:{ln})") elif not hit: blind.append(f'{rel}:{ln} ("{mod}" fuori repo o ambiguo)') @@ -1399,7 +1403,7 @@ def git_diff_files(root: str, ref: str) -> tuple[list[str], int]: capture_output=True, text=True, timeout=30, encoding="utf-8", errors="replace") if proc.returncode != 0: - raise RuntimeError((proc.stderr or "git diff fallito").strip()[:200]) + raise RuntimeError((proc.stderr or "git diff failed").strip()[:200]) changed = [l.strip() for l in proc.stdout.split("\n") if l.strip()] src = [f for f in changed if os.path.splitext(f)[1].lower() in SRC_EXTS] @@ -1581,28 +1585,28 @@ def main() -> int: ap.add_argument("--seed", action="append", default=[]) ap.add_argument("--from-diff", nargs="?", const="HEAD", default=None, metavar="REF", - help="semina la slice dai file modificati rispetto a REF " - "(git diff --name-only; default HEAD). Per una PR: " + help="seed the slice from the files changed against REF " + "(git diff --name-only; default HEAD). For a PR: " "--from-diff main...") ap.add_argument("--importers-depth", type=int, default=2) ap.add_argument("--deps-depth", type=int, default=0, - help="limita la chiusura delle dipendenze (0 = completa)") + help="limit the dependency closure (0 = complete)") ap.add_argument("--budget", default="0", - help="budget in TOKEN stimati (size/4) per il working " - "set, oppure 'auto' (finestra - occupato, dallo " - "stato del hook T1); 0 = off") + help="budget in estimated TOKENS (size/4) for the working " + "set, or 'auto' (window - used, from the T1 hook " + "state); 0 = off") ap.add_argument("--max-files", type=int, default=60) ap.add_argument("--json", action="store_true") ap.add_argument("--anchor-ends", action="store_true", - help="ancora gli estremi contro il lost-in-the-middle: " - "seed in testa, importatori/caller in coda, i test " - "scesi in mezzo. Solo ORDINE (nessuna selezione), " - "safe per pi. Off di default") + help="anchor the ends against lost-in-the-middle: seeds " + "first, importers/callers last, the tests moved to " + "the middle. ORDER only (no selection), safe for pi. " + "Off by default") args = ap.parse_args() root = os.path.abspath(args.root) if not os.path.isdir(root): - print(f"repo non trovato: {root}", file=sys.stderr) + print(f"repo not found: {root}", file=sys.stderr) return 2 symptom = args.symptom if args.symptom_file: @@ -1611,7 +1615,7 @@ def main() -> int: files = collect_files(root) if not files: - print("nessun sorgente trovato", file=sys.stderr) + print("no source found", file=sys.stderr) return 2 # budget risolto subito: serve alla chiave di cache @@ -1634,11 +1638,11 @@ def main() -> int: print(f"--from-diff {args.from_diff}: {e}", file=sys.stderr) return 2 if skipped: - print(f"--from-diff: {skipped} file modificati non-sorgente " - "(doc/config) esclusi dai seed", file=sys.stderr) + print(f"--from-diff: {skipped} changed non-source files " + "(doc/config) excluded from the seeds", file=sys.stderr) if not diff_files and not symptom and not args.seed: - print(f"--from-diff {args.from_diff}: nessun sorgente modificato " - "— niente da affettare", file=sys.stderr) + print(f"--from-diff {args.from_diff}: no source changed " + "— nothing to slice", file=sys.stderr) # cache PRIMA delle parti costose (grep dei letterali + grafo import). # indice ctags (se il repo ne shippa uno): promuove il grafo generico. @@ -1671,13 +1675,13 @@ def main() -> int: if hit is not None: print(hit) if not args.json: - print(f"[cache T2@{t2_version()}: manifest riusato — repo, " - "sintomo, parametri e operatore invariati]") + print(f"[cache T2@{t2_version()}: manifest reused — repo, " + "symptom, parameters and operator unchanged]") return 0 seeds = find_seeds(root, files, symptom, args.seed, diff_files, - f"file modificato nel diff ({args.from_diff})" - if args.from_diff else "file modificato nel diff") + f"file changed in the diff ({args.from_diff})" + if args.from_diff else "file changed in the diff") dyn_blind: list[str] = [] if seeds: # i prior appresi AGGIUNGONO seed (mai creano una slice da soli: @@ -1694,15 +1698,15 @@ def main() -> int: seeds = sorted(seeds + dyn) # accoppiamento evolutivo (git co-change): prior cold-start additivo. have = {s for s, _ in seeds} - cochange = [(f, f"co-cambiato col seed in {n} commit (git)") + cochange = [(f, f"co-changed with the seed in {n} commits (git)") for f, n in git_cochange(root, have, fileset) if f not in have] if cochange: seeds = sorted(seeds + cochange) if not seeds: - print("ATTENZIONE: nessun seed riconosciuto nel sintomo — slice impossibile.\n" - "Passa --seed oppure includi uno stack trace / messaggio " - "d'errore nel sintomo. Fail-safe: nessuna proiezione applicata.", + print("WARNING: no seed recognised in the symptom — slice impossible.\n" + "Pass --seed , or include a stack trace / error message " + "in the symptom. Fail-safe: no projection applied.", file=sys.stderr) print(render(root, len(files), [], {}, args.max_files, args.json, anchor_ends=args.anchor_ends)) @@ -1722,13 +1726,14 @@ def main() -> int: if f not in static_reach and f not in have) if 0 < len(covdiff) <= COV_MAX: seeds = sorted(seeds + [ - (f, "eseguito a runtime (copertura), invisibile al grafo " - "statico dai seed") for f in covdiff]) + (f, "executed at runtime (coverage), invisible to the " + "static graph from the seeds") for f in covdiff]) elif len(covdiff) > COV_MAX: - cov_note = (f"{len(covdiff)} file eseguiti (copertura) fuori dal " - "grafo statico dai seed — troppi per seminare con " - "precisione (copertura di suite, non del solo " - "scenario); leggili mirati se il ragionamento li tocca") + cov_note = (f"{len(covdiff)} executed files (coverage) outside the " + "static graph from the seeds — too many to seed " + "precisely (whole-suite coverage, not just this " + "scenario); read them selectively if the reasoning " + "touches them") budget_note = None t2b = None if budget: diff --git a/claude-context-kernel/skills/kernel-slice/scripts/slice.py b/claude-context-kernel/skills/kernel-slice/scripts/slice.py index 0ec81de..000b1d9 100644 --- a/claude-context-kernel/skills/kernel-slice/scripts/slice.py +++ b/claude-context-kernel/skills/kernel-slice/scripts/slice.py @@ -228,7 +228,7 @@ def slice_go(src: str, targets: set[str]) -> str | None: def main(argv: list[str]) -> int: if len(argv) < 3: - print("uso: slice.py [...]", file=sys.stderr) + print("usage: slice.py [...]", file=sys.stderr) return 2 path, targets = argv[1], set(argv[2:]) src = open(path, encoding="utf-8").read() diff --git a/claude-context-kernel/tests/test_ab_verify.py b/claude-context-kernel/tests/test_ab_verify.py index 77e5ef9..9a5c713 100644 --- a/claude-context-kernel/tests/test_ab_verify.py +++ b/claude-context-kernel/tests/test_ab_verify.py @@ -19,7 +19,7 @@ def _pack(text: str) -> str: def _sample(orig: str = "riga a\nERRORE: rotto\nriga b", - comp: str = "riga a\n[context-kernel: elise 1 righe]\nERRORE: rotto", + comp: str = "riga a\n[context-kernel: elided 1 righe]\nERRORE: rotto", attempts: int = 0) -> dict: return {"ts": 1752700000.0, "tool": "Bash", "file": None, "session": "abcd1234", "attempts": attempts, @@ -58,20 +58,20 @@ def _run(self, judge: str, args: list[str] | None = None): def test_invariant_verdict_updates_ledger(self): self._write_state([_sample()]) - judge = self._fake_judge("analisi breve\nVERDETTO: INVARIANTE") + judge = self._fake_judge("analisi breve\nVERDICT: INVARIANT") proc = self._run(judge) self.assertEqual(proc.returncode, 0, proc.stderr) st = self._state() self.assertEqual(st["ok"], 1) self.assertEqual(st["degraded"], 0) self.assertEqual(st["pending"], []) - self.assertIn("INVARIANTE", proc.stdout) + self.assertIn("INVARIANT", proc.stdout) def test_degraded_verdict_records_what_was_lost(self): self._write_state([_sample()]) judge = self._fake_judge( "manca l'esito dei test\n" - "VERDETTO: DEGRADATO — conteggio dei test falliti perso") + "VERDICT: DEGRADED — conteggio dei test falliti perso") proc = self._run(judge) self.assertEqual(proc.returncode, 0, proc.stderr) st = self._state() @@ -79,7 +79,7 @@ def test_degraded_verdict_records_what_was_lost(self): self.assertEqual(st["pending"], []) self.assertIn("conteggio dei test falliti perso", st["degradations"][0]["missing"]) - self.assertIn("DEGRADATO", proc.stdout) + self.assertIn("DEGRADED", proc.stdout) def test_unparsable_answer_retries_then_drops(self): self._write_state([_sample()]) @@ -101,7 +101,7 @@ def test_missing_judge_keeps_sample_pending(self): self.assertEqual(proc.returncode, 0) # mai fatale st = self._state() self.assertEqual(len(st["pending"]), 1) - self.assertIn("non trovato", proc.stderr) + self.assertIn("not found", proc.stderr) def test_judge_prompt_contains_both_versions(self): self._write_state([_sample(orig="ORIGINALE-UNICO-XYZ", @@ -116,13 +116,13 @@ def test_status_flag_reports_without_calling(self): self._write_state([_sample()], ok=4, degraded=1) proc = self._run("irrilevante", args=["--status"]) self.assertEqual(proc.returncode, 0) - self.assertIn("4 invarianti", proc.stdout) - self.assertIn("1 degradate", proc.stdout) - self.assertIn("1 campioni in attesa", proc.stdout) + self.assertIn("4 invariant", proc.stdout) + self.assertIn("1 degraded", proc.stdout) + self.assertIn("1 samples pending", proc.stdout) def test_limit_judges_only_first_n(self): self._write_state([_sample(), _sample(orig="secondo campione qui")]) - judge = self._fake_judge("VERDETTO: INVARIANTE") + judge = self._fake_judge("VERDICT: INVARIANT") self._run(judge, args=["--limit", "1"]) st = self._state() self.assertEqual(st["ok"], 1) @@ -151,7 +151,7 @@ def test_savings_report_shows_ab_line(self): "CK_CANARY_STATE": os.path.join(self.dir, "no-canary.json")}) self.assertEqual(proc.returncode, 0, proc.stderr) self.assertIn("A/B invariance", proc.stdout) - self.assertIn("2 degradate", proc.stdout) + self.assertIn("2 degraded", proc.stdout) self.assertIn("ab_verify.py", proc.stdout) diff --git a/claude-context-kernel/tests/test_bench.py b/claude-context-kernel/tests/test_bench.py index 9d95e67..e01ee54 100644 --- a/claude-context-kernel/tests/test_bench.py +++ b/claude-context-kernel/tests/test_bench.py @@ -61,7 +61,7 @@ def test_repo_without_candidates_exits_cleanly(self): capture_output=True, text=True, timeout=60, encoding="utf-8", errors="replace") self.assertEqual(proc.returncode, 2) - self.assertIn("nessun raise-site", proc.stderr) + self.assertIn("no candidate raise site", proc.stderr) finally: shutil.rmtree(empty) diff --git a/claude-context-kernel/tests/test_canary.py b/claude-context-kernel/tests/test_canary.py index 821bcd4..e2884db 100644 --- a/claude-context-kernel/tests/test_canary.py +++ b/claude-context-kernel/tests/test_canary.py @@ -88,7 +88,7 @@ def test_pending_records_exact_footer(self): upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] st = self.state_dict() footer = st["pending"][0]["footer"] - self.assertRegex(footer, r"^\[context-kernel: \d+ -> \d+ token, -\d+%\]$") + self.assertRegex(footer, r"^\[context-kernel: \d+ -> \d+ tokens, -\d+%\]$") self.assertIn(footer, upd["stdout"]) # coerente con l'emesso def test_subagent_compression_not_recorded(self): @@ -116,7 +116,7 @@ class TestCanaryVerify(CanaryCase): def test_footer_in_transcript_means_verified(self): self.seed_pending() with open(self.transcript, "w", encoding="utf-8") as f: - f.write(_transcript_line(TID, "output compresso\n\n[context-kernel: 100 -> 10 token, -90%]")) + f.write(_transcript_line(TID, "output compresso\n\n[context-kernel: 100 -> 10 tokens, -90%]")) proc = _util.run_hook(_util.COMPRESS, self.payload(stdout_text="piccolo"), env=self.env) self.assertEqual(_util.hook_json(proc), {}) # nessun allarme st = self.state_dict() @@ -135,7 +135,7 @@ def test_missing_footer_raises_alert(self): out = _util.hook_json(proc) ctx = out["hookSpecificOutput"]["additionalContext"] self.assertIn("CANARY", ctx) - self.assertIn("NON risulta applicata", ctx) + self.assertIn("does not appear applied", ctx) st = self.state_dict() self.assertEqual(st["failed"], 1) self.assertEqual(st["pending"], []) @@ -156,9 +156,9 @@ def test_content_citing_footer_is_not_verified(self): """Un tool_result INTEGRALE il cui contenuto CITA un footer (doc del progetto, log, transcript riletti) NON deve risultare verificato: il match va fatto sul footer esatto della compressione pending.""" - self.seed_pending(footer="[context-kernel: 1384 -> 855 token, -38%]") + self.seed_pending(footer="[context-kernel: 1384 -> 855 tokens, -38%]") citazione = ("il file spiega il formato del footer, es. " - "[context-kernel: 1847 -> 1014 token, -45%] a fine output") + "[context-kernel: 1847 -> 1014 tokens, -45%] a fine output") with open(self.transcript, "w", encoding="utf-8") as f: f.write(_transcript_line(TID, citazione)) proc = _util.run_hook(_util.COMPRESS, self.payload(stdout_text="piccolo"), env=self.env) @@ -169,7 +169,7 @@ def test_content_citing_footer_is_not_verified(self): self.assertEqual(st["failed"], 1) def test_exact_footer_in_transcript_means_verified(self): - footer = "[context-kernel: 1384 -> 855 token, -38%]" + footer = "[context-kernel: 1384 -> 855 tokens, -38%]" self.seed_pending(footer=footer) with open(self.transcript, "w", encoding="utf-8") as f: f.write(_transcript_line(TID, f"output compresso\n\n{footer}")) @@ -192,12 +192,12 @@ def test_park_hint_quotes_do_not_break_verification(self): proc = _util.run_hook(_util.COMPRESS, self.payload(stdout_text=varied), env=self.env) upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("parcheggiato", upd["stdout"]) # hint presente - self.assertIn('"', upd["stdout"].split("parcheggiato")[1].split("]")[0]) + self.assertIn("parked", upd["stdout"]) # hint presente + self.assertIn('"', upd["stdout"].split("parked")[1].split("]")[0]) st = self.state_dict() footer = st["pending"][0]["footer"] - self.assertRegex(footer, r"^\[context-kernel: \d+ -> \d+ token, -\d+%\]$") - self.assertNotIn("parcheggiato", footer) # footer NUDO + self.assertRegex(footer, r"^\[context-kernel: \d+ -> \d+ tokens, -\d+%\]$") + self.assertNotIn("parked", footer) # footer NUDO # il transcript registra l'output compresso INTEGRALE, JSON-escapato with open(self.transcript, "w", encoding="utf-8") as f: f.write(_transcript_line(TID, upd["stdout"])) @@ -211,10 +211,10 @@ def test_park_hint_quotes_do_not_break_verification(self): def test_elision_marker_alone_is_not_verified(self): """Il marcatore interno di elisione '[context-kernel: elise ...]' non e' il footer: senza footer esatto la compressione non risulta applicata.""" - self.seed_pending(footer="[context-kernel: 1384 -> 855 token, -38%]") + self.seed_pending(footer="[context-kernel: 1384 -> 855 tokens, -38%]") with open(self.transcript, "w", encoding="utf-8") as f: f.write(_transcript_line( - TID, "testa\n[context-kernel: elise 100 righe di rumore " + TID, "testa\n[context-kernel: elided 100 righe di rumore " "(~900 token); mantenute 2 righe con segnale]\ncoda")) _util.run_hook(_util.COMPRESS, self.payload(stdout_text="piccolo"), env=self.env) st = self.state_dict() @@ -226,7 +226,7 @@ def test_legacy_pending_without_footer_falls_back_to_mark(self): prefisso generico, per non condannarli tutti a failed.""" self.seed_pending() # nessun footer with open(self.transcript, "w", encoding="utf-8") as f: - f.write(_transcript_line(TID, "x\n\n[context-kernel: 100 -> 10 token, -90%]")) + f.write(_transcript_line(TID, "x\n\n[context-kernel: 100 -> 10 tokens, -90%]")) _util.run_hook(_util.COMPRESS, self.payload(stdout_text="piccolo"), env=self.env) st = self.state_dict() self.assertEqual(st["verified"], 1) @@ -255,7 +255,7 @@ def test_subagent_pending_dropped_after_short_ttl(self): def test_failure_records_session(self): """Ogni fallimento annota la sessione: distingue questa sessione dalle headless concorrenti (distiller).""" - self.seed_pending(footer="[context-kernel: 999 -> 111 token, -89%]") + self.seed_pending(footer="[context-kernel: 999 -> 111 tokens, -89%]") with open(self.transcript, "w", encoding="utf-8") as f: f.write(_transcript_line(TID, NOISY)) _util.run_hook(_util.COMPRESS, self.payload(stdout_text="piccolo"), env=self.env) @@ -316,7 +316,7 @@ def test_report_shows_verified(self): json.dump({"pending": [], "verified": 3, "failed": 0, "last_ok": "2026-01-01T00:00:00", "last_failure": None}, f) out = _util.run_script(_util.SAVINGS, "", env=self.env).stdout - self.assertIn("canary: ✓ 3 compressioni verificate", out) + self.assertIn("canary: ✓ 3 compressions verified", out) def test_report_warns_on_failures(self): self._seed_log() @@ -324,8 +324,8 @@ def test_report_warns_on_failures(self): json.dump({"pending": [], "verified": 1, "failed": 2, "last_ok": None, "last_failure": "2026-01-01T00:00:00"}, f) out = _util.run_script(_util.SAVINGS, "", env=self.env).stdout - self.assertIn("CANARY: ⚠ 2 compressioni NON applicate", out) - self.assertIn("sovrastimati", out) + self.assertIn("CANARY: ⚠ 2 compressions NOT applied", out) + self.assertIn("overstated", out) def test_report_silent_without_state(self): self._seed_log() @@ -341,7 +341,7 @@ def test_report_shows_auto_acked(self): "failed_auto_acked": 2, "heal_streak": 6, "last_ok": "2026-01-01T00:00:00", "last_failure": None}, f) out = _util.run_script(_util.SAVINGS, "", env=self.env).stdout - self.assertIn("2 auto-riconosciuti", out) + self.assertIn("2 auto-acknowledged", out) self.assertNotIn("⚠", out) def test_report_shows_heal_streak_on_open_failures(self): @@ -353,7 +353,7 @@ def test_report_shows_heal_streak_on_open_failures(self): "heal_streak": 2, "failures": [{"ts": "x", "session": "s1"}], "last_ok": None, "last_failure": "2026-01-01T00:00:00"}, f) out = _util.run_script(_util.SAVINGS, "", env=self.env).stdout - self.assertIn("auto-heal: 2 verificate consecutive", out) + self.assertIn("auto-heal: 2 consecutive verified", out) def test_reset_canary_acks_failures(self): """--reset-canary sposta i fallimenti nello storico riconosciuto: @@ -365,10 +365,10 @@ def test_reset_canary_acks_failures(self): "last_ok": None, "last_failure": "2026-01-01T00:00:00"}, f) out = _util.run_script(_util.SAVINGS, "", env=self.env, args=["--reset-canary"]).stdout - self.assertIn("Riconosciuti 2 fallimenti", out) + self.assertIn("Acknowledged 2 canary failures", out) report = _util.run_script(_util.SAVINGS, "", env=self.env).stdout self.assertNotIn("⚠", report) - self.assertIn("2 storici riconosciuti", report) + self.assertIn("2 historical, acknowledged", report) class TestCanaryAutoDegrade(CanaryCase): @@ -444,7 +444,7 @@ def test_degrade_disabled_via_env(self): self.assertEqual(self.state_dict().get("degraded_sessions", []), []) -FOOTER_OK = "[context-kernel: 100 -> 10 token, -90%]" +FOOTER_OK = "[context-kernel: 100 -> 10 tokens, -90%]" class TestCanaryAutoheal(CanaryCase): @@ -581,7 +581,7 @@ def test_mth_verified_probe_lifts_degrade(self): proc = _util.run_hook(_util.COMPRESS, self.payload(stdout_text="piccolo"), env={**self.env, **self.PROBE_ENV}) ctx = _util.hook_json(proc)["hookSpecificOutput"]["additionalContext"] - self.assertIn("RIPRISTINO", ctx) + self.assertIn("RECOVERY", ctx) st = self.state_dict() self.assertEqual(st["degraded_sessions"], []) # degrado rimosso self.assertNotIn(self._sess(), st.get("probe", {})) diff --git a/claude-context-kernel/tests/test_charter.py b/claude-context-kernel/tests/test_charter.py index 0cc151a..0e0dedb 100644 --- a/claude-context-kernel/tests/test_charter.py +++ b/claude-context-kernel/tests/test_charter.py @@ -86,7 +86,7 @@ def test_vincolo_senza_citazione_non_indicizzato(self): os.makedirs(repo2) proc = _util.run_script(_util.CHARTER, carta, env=self.env, args=["save", "--repo", repo2]) - self.assertIn("0 vincoli indicizzati", proc.stdout) + self.assertIn("0 constraints indexed", proc.stdout) class TestCharterGuard(_Base): @@ -96,7 +96,7 @@ def test_edit_of_cited_file_injects_constraints(self): out = _util.hook_json(_util.run_hook( _util.GUARD, self._edit_payload(fpath), env=self.env)) ctx = out["hookSpecificOutput"]["additionalContext"] - self.assertIn("CARTA DEL TASK", ctx) + self.assertIn("TASK CHARTER", ctx) self.assertIn("TypeError sui misti", ctx) self.assertIn("pool non e' mai None", ctx) # entrambi i vincoli self.assertNotIn("ritorna un int", ctx) # vincolo di ALTRO file @@ -165,7 +165,7 @@ def _run(self, command: str, session: str = "s1", env: dict | None = None): def test_sed_i_on_cited_file_injects(self): out = self._run("sed -i '' 's/a/b/' pkg/calc.py") ctx = out["hookSpecificOutput"]["additionalContext"] - self.assertIn("comando Bash", ctx) + self.assertIn("Bash command", ctx) self.assertIn("TypeError sui misti", ctx) self.assertNotIn("ritorna un int", ctx) # vincolo di ALTRO file @@ -176,7 +176,7 @@ def test_redirect_on_cited_file_injects(self): def test_git_checkout_on_cited_file_injects(self): out = self._run("git checkout -- pkg/calc.py") - self.assertIn("CARTA DEL TASK", + self.assertIn("TASK CHARTER", out["hookSpecificOutput"]["additionalContext"]) def test_readonly_command_is_noop(self): @@ -272,14 +272,14 @@ def test_anchors_captured_at_save(self): def test_unchanged_citations_report_ok(self): out = self._refresh() self.assertEqual(out.count("OK "), 3, out) - self.assertNotIn("RI-ANCORATA", out) + self.assertNotIn("RE-ANCHORED", out) def test_shifted_file_is_reanchored_in_state_and_text(self): with open(self.calc, "w", encoding="utf-8") as f: f.write("# uno\n# due\n# tre\ndef add(a, b):\nPOOL = object()\n") out = self._refresh() - self.assertIn("RI-ANCORATA pkg/calc.py:1 -> :4", out) - self.assertIn("RI-ANCORATA pkg/calc.py:2 -> :5", out) + self.assertIn("RE-ANCHORED pkg/calc.py:1 -> :4", out) + self.assertIn("RE-ANCHORED pkg/calc.py:2 -> :5", out) self.assertEqual({e["line"] for e in self._entries("pkg/calc.py")}, {4, 5}) got = _util.run_script(_util.CHARTER, "", env=self.env, @@ -294,14 +294,14 @@ def test_ambiguous_anchor_is_declared_never_guessed(self): encoding="utf-8") as f: # la riga citata non matcha f.write("# uno\n# due\n# tre\ndef main():\ndef main():\n") out = self._refresh() - self.assertIn("IRRISOLVIBILE app.py:3: ancora trovata 2 volte", out) + self.assertIn("UNRESOLVABLE app.py:3: anchor found 2 times", out) self.assertEqual(self._entries("app.py")[0]["line"], 3) # non tocca def test_vanished_anchor_is_declared(self): with open(self.calc, "w", encoding="utf-8") as f: f.write("def somma(a, b):\nPOOL = object()\n") # riga 1 sparita out = self._refresh() - self.assertIn("IRRISOLVIBILE pkg/calc.py:1: ancora trovata 0 volte", + self.assertIn("UNRESOLVABLE pkg/calc.py:1: anchor found 0 times", out) self.assertEqual({e["line"] for e in self._entries("pkg/calc.py")}, {1, 2}) # linee mai indovinate @@ -318,7 +318,7 @@ def test_twin_citations_on_same_line_stay_coherent(self): with open(self.calc, "w", encoding="utf-8") as f: f.write("# uno\n# due\n# tre\ndef add(a, b):\nPOOL = object()\n") out = self._refresh() - self.assertIn("RI-ANCORATA pkg/calc.py:1 -> :4", out) + self.assertIn("RE-ANCHORED pkg/calc.py:1 -> :4", out) for e in self._entries("app.py"): # la gemella sotto app.py self.assertIn("(pkg/calc.py:4)", e["vincolo"]) self.assertNotIn("(pkg/calc.py:1)", e["vincolo"]) @@ -332,8 +332,8 @@ def test_pre_anchor_charter_is_declared(self): with open(self.state, "w", encoding="utf-8") as f: json.dump(st, f) out = self._refresh() - self.assertEqual(out.count("SENZA ANCORA"), 3, out) - self.assertIn("rigenerare la carta", out) + self.assertEqual(out.count("NO ANCHOR"), 3, out) + self.assertIn("regenerate the charter", out) if __name__ == "__main__": diff --git a/claude-context-kernel/tests/test_compress.py b/claude-context-kernel/tests/test_compress.py index 196ad88..059b998 100644 --- a/claude-context-kernel/tests/test_compress.py +++ b/claude-context-kernel/tests/test_compress.py @@ -10,7 +10,7 @@ import _util -FOOTER = re.compile(r"\[context-kernel: \d+ -> \d+ token, -\d+%\]") +FOOTER = re.compile(r"\[context-kernel: \d+ -> \d+ tokens, -\d+%\]") def _load_module(): @@ -67,7 +67,7 @@ def test_truncate_preserves_signal_lines_in_middle(self): lines[60] = "ERROR: qualcosa di rotto a meta' output" out = self.ck.signal_preserving_truncate(lines) self.assertIn(lines[60], out) - self.assertTrue(any("[context-kernel: elise" in l for l in out)) + self.assertTrue(any("[context-kernel: elided" in l for l in out)) self.assertLess(len(out), len(lines)) def test_truncate_noop_under_budget(self): @@ -319,7 +319,7 @@ def test_already_compressed_output_not_recompressed(self): insieme gli hook si sommano — un output che porta gia' il footer in coda NON va ricompresso.""" already = ("\n".join(["riga di rumore identica"] * 300) - + "\n\n[context-kernel: 2000 -> 500 token, -75%]") + + "\n\n[context-kernel: 2000 -> 500 tokens, -75%]") proc = _util.run_hook(_util.COMPRESS, _util.bash_payload(already), env=self.env) self.assertEqual(_util.hook_json(proc), {}) @@ -522,7 +522,7 @@ def test_source_read_keeps_structure_drops_bodies(self): got = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] got = got["file"]["content"] self.assertIn("def funzione_30(x):", got) # struttura oltre HEAD - self.assertIn("righe di corpo", got) # marker code-aware + self.assertIn("lines of body", got) # marker code-aware self.assertNotIn("except Exception", got) # non piu' "segnale" def test_log_read_still_keeps_error_lines(self): @@ -553,7 +553,7 @@ def test_php_read_keeps_use_and_namespace(self): proc = self._run(content, "/tmp/ArticleModel.php") got = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] got = got["file"]["content"] - self.assertIn("righe di corpo", got) # compressione avvenuta + self.assertIn("lines of body", got) # compressione avvenuta self.assertIn("use Joomla\\CMS\\Factory;", got) self.assertIn("use Joomla\\CMS\\MVC\\Model\\BaseDatabaseModel;", got) self.assertIn("namespace Acme\\Component\\Site\\Model;", got) @@ -565,7 +565,7 @@ def test_elision_marker_declares_explicit_range(self): proc = self._run("\n".join(_util.unique_lines(300)), "/tmp/run.log") got = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] content = got["file"]["content"] - self.assertIn("elise righe 46-280:", content) # HEAD 45, TAIL 20 + self.assertIn("elided lines 46-280:", content) # HEAD 45, TAIL 20 def test_elision_marker_declares_numeric_continuity(self): """Dal primo DEGRADATO A/B: log numerato eliso -> il marker dichiara @@ -575,7 +575,7 @@ def test_elision_marker_declares_numeric_continuity(self): for i in range(300)] proc = self._run("\n".join(lines), "/tmp/migrate.log") got = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("numerazione continua 45→279", + self.assertIn("unbroken numbering 45→279", got["file"]["content"]) def test_no_false_continuity_on_irregular_numbers(self): @@ -628,8 +628,8 @@ def test_elided_read_marks_state_and_hints(self): proc = self._read(self.content) upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] content = upd["file"]["content"] - self.assertIn("elise", content) - self.assertIn("copia ELISA", content) # footer azionabile + self.assertIn("elided", content) + self.assertIn("ELIDED copy", content) # footer azionabile self.assertRegex(content, FOOTER) # canary-compatibile with open(self.reads, encoding="utf-8") as f: st = json.load(f) @@ -662,7 +662,7 @@ def test_third_read_resumes_delta_marker(self): proc = self._read(self.content) # rilettura normale out = _util.hook_json(proc) upd = out["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("INVARIATO", upd["file"]["content"]) + self.assertIn("UNCHANGED", upd["file"]["content"]) class TestDeltaReads(unittest.TestCase): @@ -708,7 +708,7 @@ def test_unchanged_reread_gets_marker(self): self._read(self.content) proc = self._read(self.content) upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("INVARIATO", upd["file"]["content"]) + self.assertIn("UNCHANGED", upd["file"]["content"]) self.assertRegex(upd["file"]["content"], FOOTER) # canary-compatibile def test_third_read_after_marker_passes_full(self): @@ -734,7 +734,7 @@ def test_legacy_raw_content_record_still_diffs(self): "riga MODIFICATA quaranta") proc = self._read(changed) upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("CAMBIATO", upd["file"]["content"]) + self.assertIn("CHANGED", upd["file"]["content"]) def test_parallel_hooks_dont_lose_records(self): """Sessioni/hook concorrenti sullo stesso stato: col lock advisory @@ -768,7 +768,7 @@ def test_changed_file_gets_unified_diff(self): proc = self._read(changed) upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] body = upd["file"]["content"] - self.assertIn("CAMBIATO", body) + self.assertIn("CHANGED", body) self.assertIn("+40\triga MODIFICATA quaranta", body) self.assertIn("@@", body) self.assertLess(len(body), len(changed) // 2) # il diff conviene diff --git a/claude-context-kernel/tests/test_ddmin.py b/claude-context-kernel/tests/test_ddmin.py index dfeb99c..c92f460 100644 --- a/claude-context-kernel/tests/test_ddmin.py +++ b/claude-context-kernel/tests/test_ddmin.py @@ -35,7 +35,7 @@ def test_reduces_lines_to_the_needle(self): r = _run(text, "grep -q AGO {}", "--unit", "line") self.assertEqual(r.returncode, 0, r.stderr) self.assertEqual(r.stdout.strip(), "AGO") - self.assertIn("1-minimale", r.stderr) + self.assertIn("1-minimal", r.stderr) def test_char_mode_reduces_to_substring(self): # riproduce sse il candidato contiene la sottostringa "XZ" @@ -59,7 +59,7 @@ def test_result_is_1_minimal(self): def test_full_input_not_reproducing_errors(self): r = _run("nessun ago qui", "grep -q INTROVABILE {}", "--unit", "line") self.assertEqual(r.returncode, 2) - self.assertIn("non riproduce", r.stderr) + self.assertIn("does not reproduce", r.stderr) def test_custom_fail_exit(self): """Oracolo che segnala 'riproduce' con exit 3.""" diff --git a/claude-context-kernel/tests/test_doctor.py b/claude-context-kernel/tests/test_doctor.py index 64c5078..27f68b7 100644 --- a/claude-context-kernel/tests/test_doctor.py +++ b/claude-context-kernel/tests/test_doctor.py @@ -52,15 +52,15 @@ def test_clean_install_exits_zero(self): # Nessuno stato canary/A-B: struttura ok -> exit 0, nessun [ko]. r = self._run() self.assertEqual(r.returncode, 0, r.stdout + r.stderr) - self.assertIn("VERDETTO", r.stdout) + self.assertIn("VERDICT", r.stdout) self.assertNotIn("[ko]", r.stdout) def test_core_scripts_and_commands_ok(self): mod = _load() rows, n_ko, _ = mod.check() texts = " ".join(m for _, m in rows) - self.assertIn("script core presenti", texts) - self.assertIn("comandi /ck-* presenti", texts) + self.assertIn("core scripts present", texts) + self.assertIn("/ck-* commands present", texts) self.assertEqual(n_ko, 0) def test_canary_failure_is_ko_and_exit_one(self): diff --git a/claude-context-kernel/tests/test_faults.py b/claude-context-kernel/tests/test_faults.py index 163dbb5..7f020dd 100644 --- a/claude-context-kernel/tests/test_faults.py +++ b/claude-context-kernel/tests/test_faults.py @@ -130,7 +130,7 @@ def test_recall_logs_targeted_cost(self): proc = _util.run_hook(_util.COMPRESS, payload, env=self.env) upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] blob = upd["stdout"] - m = re.search(r'parcheggiato: python3 "[^"]+" (\w+) ', blob) + m = re.search(r'parked: python3 "[^"]+" (\w+) ', blob) self.assertIsNotNone(m, blob) key = m.group(1) @@ -154,7 +154,7 @@ def test_recall_respects_log_off(self): payload["transcript_path"] = "/tmp/sess-recall.jsonl" proc = _util.run_hook(_util.COMPRESS, payload, env=self.env) blob = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"]["stdout"] - key = re.search(r'parcheggiato: python3 "[^"]+" (\w+) ', blob).group(1) + key = re.search(r'parked: python3 "[^"]+" (\w+) ', blob).group(1) recall = os.path.join(_util.HOOKS, "recall.py") env = {**os.environ, "CK_PARK_STATE": self.park, "CK_FAULT_LOG": self.faults, "CK_LOG_OFF": "1"} @@ -190,13 +190,13 @@ def test_text_report_shows_distortion(self): capture_output=True, text=True, env=env) self.assertEqual(r.returncode, 0, r.stderr) out = r.stdout - self.assertIn("page fault (distorsione)", out) - self.assertIn("2 recuperi", out) + self.assertIn("page faults (distortion)", out) + self.assertIn("2 recoveries", out) self.assertIn("840", out) # 800 + 40 rientrati # 840 rientrati su 1300 risparmiati = 64.6% self.assertIn("64.6%", out) - self.assertIn("riletture integrali", out) - self.assertIn("recall mirati", out) + self.assertIn("full re-reads", out) + self.assertIn("targeted recalls", out) def test_read_faults_helper(self): sys.path.insert(0, _util.HOOKS) diff --git a/claude-context-kernel/tests/test_json_mcp.py b/claude-context-kernel/tests/test_json_mcp.py index 70e3de8..3801896 100644 --- a/claude-context-kernel/tests/test_json_mcp.py +++ b/claude-context-kernel/tests/test_json_mcp.py @@ -50,7 +50,7 @@ def test_homogeneous_array_projected_to_samples(self): updated = out["hookSpecificOutput"]["updatedToolOutput"] self.assertIsInstance(updated, list) # forma preservata body = updated[0]["text"] - self.assertIn("elisi 37 di 40 oggetti", body) + self.assertIn("dropped 37 of 40 objects", body) self.assertIn("id", body) # schema delle chiavi self.assertIn("utente0", body) # campioni in testa self.assertNotIn("utente39", body) # coda elisa @@ -60,7 +60,7 @@ def test_nested_array_projected(self): text = json.dumps({"meta": {"count": 40}, "items": _items(40)}) out = _util.hook_json(self._run(mcp_payload(text))) body = out["hookSpecificOutput"]["updatedToolOutput"][0]["text"] - self.assertIn("elisi 37 di 40 oggetti", body) + self.assertIn("dropped 37 of 40 objects", body) self.assertIn('"count": 40', body) # il resto resta intatto def test_dict_content_shape_preserved(self): @@ -68,7 +68,7 @@ def test_dict_content_shape_preserved(self): out = _util.hook_json(self._run(mcp_payload(text, shape="dict"))) updated = out["hookSpecificOutput"]["updatedToolOutput"] self.assertIsInstance(updated, dict) - self.assertIn("elisi 37 di 40", updated["content"][0]["text"]) + self.assertIn("dropped 37 of 40", updated["content"][0]["text"]) def test_replay_after_elision_passes_integral(self): cmds = os.path.join(tempfile.gettempdir(), @@ -77,7 +77,7 @@ def test_replay_after_elision_passes_integral(self): env = {"CK_CMDS_STATE": cmds} text = json.dumps(_items(40)) out1 = _util.hook_json(self._run(mcp_payload(text), env=env)) - self.assertIn("elisi", str(out1)) + self.assertIn("dropped", str(out1)) # stessa chiamata, stesso output: la copia in contesto era ELISA # -> page fault, passa integrale (no-op) out2 = _util.hook_json(self._run(mcp_payload(text), env=env)) @@ -98,8 +98,8 @@ def test_identical_call_delta_marker(self): self.assertEqual(out1, {}) # prima volta: registra out2 = _util.hook_json(self._run(mcp_payload(text), env=env)) body = out2["hookSpecificOutput"]["updatedToolOutput"][0]["text"] - self.assertIn("IDENTICO", body) - self.assertIn("chiamata MCP", body) + self.assertIn("IDENTICAL", body) + self.assertIn("MCP call", body) finally: if os.path.exists(cmds): os.remove(cmds) @@ -125,7 +125,7 @@ def test_scalar_array_not_projected(self): # array di scalari: json_project non scatta (solo array di OGGETTI) text = json.dumps({"nums": list(range(2000))}) parsed = _util.hook_json(self._run(mcp_payload(text))) - self.assertNotIn("elisi", str(parsed)) + self.assertNotIn("dropped", str(parsed)) if __name__ == "__main__": diff --git a/claude-context-kernel/tests/test_mcp_server.py b/claude-context-kernel/tests/test_mcp_server.py index 37412c5..f5c3a2e 100644 --- a/claude-context-kernel/tests/test_mcp_server.py +++ b/claude-context-kernel/tests/test_mcp_server.py @@ -97,7 +97,7 @@ def test_repo_slice_call_returns_manifest(self): self.assertIs(res["isError"], False) text = res["content"][0]["text"] self.assertIn("main.py — seed", text) - self.assertIn("util.py — dipendenza", text) + self.assertIn("util.py — dependency", text) self.assertNotIn("loner.py", text) def test_tools_call_returns_slice_only(self): @@ -117,7 +117,7 @@ def test_unknown_tool_is_invalid_params(self): def test_missing_file_is_tool_error_not_crash(self): res = self.by_id[6]["result"] self.assertIs(res["isError"], True) - self.assertIn("non trovato", res["content"][0]["text"]) + self.assertIn("not found", res["content"][0]["text"]) def test_ping(self): self.assertEqual(self.by_id[7]["result"], {}) diff --git a/claude-context-kernel/tests/test_park.py b/claude-context-kernel/tests/test_park.py index 141ae0d..3176eeb 100644 --- a/claude-context-kernel/tests/test_park.py +++ b/claude-context-kernel/tests/test_park.py @@ -55,7 +55,7 @@ def _park_key(self, stdout: str) -> str: def test_elided_bash_output_is_parked_with_declared_recall(self): proc = self._compress(_noisy_with_needle()) key = self._park_key(proc.stdout) - self.assertIn("parcheggiato", proc.stdout) + self.assertIn("parked", proc.stdout) with open(self.park, encoding="utf-8") as f: st = json.load(f) self.assertIn(key, st) @@ -89,13 +89,13 @@ def test_small_output_not_parked(self): def test_kill_switch(self): proc = self._compress(_noisy_with_needle(), env={"CK_PARK": "0"}) - self.assertNotIn("parcheggiato", proc.stdout) + self.assertNotIn("parked", proc.stdout) self.assertFalse(os.path.exists(self.park)) def test_recall_unknown_key_exits_2(self): rec = _util.run_script(RECALL, "", args=["deadbeef00"], env=self.env) self.assertEqual(rec.returncode, 2) - self.assertIn("assente o scaduta", rec.stderr) + self.assertIn("missing or expired", rec.stderr) # --- recall storage di sessione: --search cross-parcheggio (1.24.0) ------- @@ -131,17 +131,17 @@ def test_search_no_match_declares_it(self): rec = _util.run_script(RECALL, "", args=["--search", "NIENTE_QUI"], env=self.env) self.assertEqual(rec.returncode, 0) - self.assertIn("nessun output parcheggiato matcha", rec.stdout) + self.assertIn("no parked output matches", rec.stdout) def test_search_empty_park(self): rec = _util.run_script(RECALL, "", args=["--search", "x"], env=self.env) - self.assertIn("parcheggio vuoto", rec.stdout) + self.assertIn("parking lot empty", rec.stdout) def test_search_bad_regex_exits_2(self): self._seed_park({"aaa1110000": ("x", "Bash", "ls")}) rec = _util.run_script(RECALL, "", args=["--search", "("], env=self.env) self.assertEqual(rec.returncode, 2) - self.assertIn("regex non valida", rec.stderr) + self.assertIn("invalid regex", rec.stderr) def test_search_logs_fault_when_enabled(self): """--search e' un page fault sul parcheggio: col ledger attivo registra @@ -186,8 +186,8 @@ def test_advises_once_above_threshold(self): self._tap(190_000) # finestra stimata ~268k -> ~71% out = self._run() ctx = out["hookSpecificOutput"]["additionalContext"] - self.assertIn("/compact MANUALE", ctx) - self.assertIn("una-tantum", ctx) + self.assertIn("MANUAL /compact", ctx) + self.assertIn("One-off", ctx) self.assertEqual(self._run(), {}) # seconda volta: silenzio def test_silent_below_threshold(self): @@ -234,7 +234,7 @@ def test_medium_bash_elided_only_with_park(self): env=self.env) out = _util.hook_json(proc) self.assertIn("updatedToolOutput", out.get("hookSpecificOutput", {})) - self.assertIn("parcheggiato", proc.stdout) # inversa dichiarata + self.assertIn("parked", proc.stdout) # inversa dichiarata proc_off = _util.run_hook(_util.COMPRESS, _util.bash_payload(self._medium()), env={**self.env, "CK_PARK": "0"}) self.assertEqual(_util.hook_json(proc_off), {}) # niente inversa -> raw @@ -253,7 +253,7 @@ def test_aggressive_arm_elides_from_earlier_line(self): big = "\n".join(_util.unique_lines(300)) def start_of_elision(env): proc = _util.run_hook(_util.COMPRESS, _util.bash_payload(big), env=env) - m = re.search(r"elise righe (\d+)-", proc.stdout) + m = re.search(r"elided lines (\d+)-", proc.stdout) self.assertIsNotNone(m, proc.stdout[-300:]) return int(m.group(1)) base = start_of_elision({**self.env, "CK_EPHEMERAL_SCALE": "1.0"}) diff --git a/claude-context-kernel/tests/test_posttool_symptom.py b/claude-context-kernel/tests/test_posttool_symptom.py index 2d20053..2273368 100644 --- a/claude-context-kernel/tests/test_posttool_symptom.py +++ b/claude-context-kernel/tests/test_posttool_symptom.py @@ -82,7 +82,7 @@ def test_pytest_failure_injects_slice_manifest(self): hso = out["hookSpecificOutput"] self.assertEqual(hso["hookEventName"], "PostToolUse") ctx = hso["additionalContext"] - self.assertIn("Fallimento rilevato", ctx) + self.assertIn("Failure detected", ctx) self.assertIn("## seed", ctx) self.assertIn("app.py", ctx) diff --git a/claude-context-kernel/tests/test_precompact.py b/claude-context-kernel/tests/test_precompact.py index 6a87b0f..a552439 100644 --- a/claude-context-kernel/tests/test_precompact.py +++ b/claude-context-kernel/tests/test_precompact.py @@ -20,9 +20,9 @@ """ MANIFEST_HEAD = """# kernel repo slice — manifest -operatore: T2@test +operator: T2@test repo: /repo -## seed (dal sintomo) +## seeds (from the symptom) - db.py <- citato nel sintomo""" @@ -69,7 +69,7 @@ def test_brief_restores_ts_q_after_compact(self): out = _util.hook_json(_util.run_hook(_util.BRIEF, payload, env=self.env)) ctx = out["hookSpecificOutput"]["additionalContext"] - self.assertIn("TS(Q) sopravvissuto alla compaction", ctx) + self.assertIn("TS(Q) survived the compaction", ctx) self.assertIn("retry esattamente 3 volte", ctx) self.assertIn("## seed", ctx) diff --git a/claude-context-kernel/tests/test_rates.py b/claude-context-kernel/tests/test_rates.py index afdc2cd..d49dc59 100644 --- a/claude-context-kernel/tests/test_rates.py +++ b/claude-context-kernel/tests/test_rates.py @@ -13,13 +13,13 @@ MANIFEST = "\n".join([ "# kernel repo slice — manifest", - "operatore: T2@test", + "operator: T2@test", "repo: /repo", "", - "## seed (dal sintomo)", + "## seeds (from the symptom)", "- app.md <- citato nel sintomo", "", - "## file della slice (per rilevanza)", + "## slice files (per rilevanza)", "- app.md — seed", "", "## fuori slice (modello page-fault)", @@ -46,8 +46,8 @@ def _fault_transcript(path: str) -> None: "text": "[context-kernel] Sintomo rilevato\n" + MANIFEST}]}}, _tool_use("t1", {"file_path": "/repo/app.md"}), _tool_result("t1", "x" * 100 + - "\n[context-kernel: elise righe 10-90: 80 righe]" - "\n[copia ELISA: per l'integrale rileggi questo file]"), + "\n[context-kernel: elided righe 10-90: 80 righe]" + "\n[ELIDED copy: for the full text read this file again]"), _tool_use("t2", {"file_path": "/repo/app.md"}), _tool_result("t2", "y" * 400), ] @@ -74,7 +74,7 @@ def test_recurrence_writes_relax_rate(self): for name in ("s1.jsonl", "s2.jsonl"): _fault_transcript(os.path.join(self.tmp, name)) out = self._run(self.tmp, "--aggregate", "--apply-rates").stdout - self.assertIn("attuazione esplicita", out) + self.assertIn("explicit application", out) self.assertIn(".md -> relax", out) with open(self.rates) as f: st = json.load(f) @@ -94,7 +94,7 @@ def test_heavy_faults_write_raw(self): def test_single_occurrence_writes_nothing(self): _fault_transcript(os.path.join(self.tmp, "s1.jsonl")) out = self._run(self.tmp, "--aggregate", "--apply-rates").stdout - self.assertIn("nessun fault ricorrente", out) + self.assertIn("no recurrent fault", out) self.assertFalse(os.path.exists(self.rates)) @@ -124,7 +124,7 @@ def test_raw_category_skips_compression(self): self._write_rates({".log": {"mode": "raw"}}) proc = self._compress("/tmp/grande.log") self.assertEqual(json.loads(proc.stdout.strip()), {}) - self.assertIn("tasso appreso", proc.stderr) + self.assertIn("learned rate", proc.stderr) def test_relax_scale_raises_threshold(self): # scala enorme: la soglia MIN_TOKENS*scala supera l'output -> no-op. diff --git a/claude-context-kernel/tests/test_repo_slice.py b/claude-context-kernel/tests/test_repo_slice.py index 00a70ae..ebf3936 100644 --- a/claude-context-kernel/tests/test_repo_slice.py +++ b/claude-context-kernel/tests/test_repo_slice.py @@ -79,8 +79,8 @@ def test_python_traceback_slice(self): out = _run(self.root, "--symptom", PY_SYMPTOM).stdout self.assertIn("app/db.py — seed", out) # dal frame self.assertIn("app/api.py — seed", out) # anche lui nei frame - self.assertIn("app/util.py — dipendenza", out) # db importa util - self.assertIn("tests/test_db.py — test correlato", out) + self.assertIn("app/util.py — dependency", out) # db importa util + self.assertIn("tests/test_db.py — related test", out) self.assertNotIn("app/unrelated.py", out) self.assertNotIn("tests/test_unrelated.py", out) self.assertNotIn("web/", out) # altro linguaggio, non toccato @@ -91,34 +91,34 @@ def test_importer_found_without_its_frame(self): symptom = 'File "app/db.py", line 4, in connect' out = _run(self.root, "--symptom", symptom).stdout self.assertIn("app/db.py — seed", out) - self.assertIn("app/api.py — importatore", out) + self.assertIn("app/api.py — importer", out) def test_error_literal_greps_raise_site(self): """Niente path nel sintomo: il letterale quotato trova il raise site.""" out = _run(self.root, "--symptom", "il servizio muore con 'connection refused by upstream'").stdout - self.assertIn('app/db.py <- contiene il letterale', out) - self.assertIn("app/util.py — dipendenza", out) + self.assertIn('app/db.py <- contains the literal', out) + self.assertIn("app/util.py — dependency", out) def test_js_stack_frame_slice(self): out = _run(self.root, "--symptom", "TypeError: fmt is not a function\n at main (web/index.js:2:13)").stdout self.assertIn("web/index.js — seed", out) - self.assertIn("web/helper.js — dipendenza", out) + self.assertIn("web/helper.js — dependency", out) self.assertNotIn("web/orphan.js", out) self.assertNotIn("app/", out) def test_explicit_seed(self): out = _run(self.root, "--seed", "app/db.py").stdout - self.assertIn("app/db.py <- seed esplicito", out) + self.assertIn("app/db.py <- explicit seed", out) def test_dynamic_test_reference_found(self): """Test che caricano il sorgente SENZA import statico (importlib da path, subprocess): trovati per convenzione di nome o per citazione del basename tra virgolette.""" out = _run(self.root, "--seed", "app/loader.py").stdout - self.assertIn("tests/test_loader.py — test correlato (usa app/loader.py)", out) - self.assertIn("tests/test_dyn.py — test correlato (usa app/loader.py)", out) + self.assertIn("tests/test_loader.py — related test (uses app/loader.py)", out) + self.assertIn("tests/test_dyn.py — related test (uses app/loader.py)", out) def test_ambiguous_ref_basename_not_guessed(self): """Basename citato ma ambiguo (piu' sorgenti omonimi): nessun arco.""" @@ -139,14 +139,14 @@ def test_json_structure(self): self.assertEqual(data["kept"], len(data["files"])) self.assertGreater(data["excluded"], 0) roles = {f["role"] for f in data["files"]} - self.assertLessEqual(roles, {"seed", "dipendenza", "importatore", "test"}) + self.assertLessEqual(roles, {"seed", "dependency", "importer", "test"}) seed_paths = {s["path"] for s in data["seeds"]} self.assertIn("app/db.py", seed_paths) def test_manifest_declares_page_fault(self): out = _run(self.root, "--symptom", PY_SYMPTOM).stdout self.assertIn("page-fault", out) - self.assertIn("prior, non un divieto", out) + self.assertIn("a prior, not a prohibition", out) class TestFailSafe(RepoSliceCase): @@ -154,8 +154,8 @@ class TestFailSafe(RepoSliceCase): def test_no_seed_is_explicit_not_silent(self): proc = _run(self.root, "--symptom", "boh, qualcosa non va") self.assertEqual(proc.returncode, 0) - self.assertIn("nessun seed riconosciuto", proc.stderr) - self.assertIn("nessuna proiezione applicata", proc.stderr) + self.assertIn("no seed recognised", proc.stderr) + self.assertIn("no projection applied", proc.stderr) def test_missing_repo_exits_nonzero(self): proc = _run("/percorso/inesistente", "--symptom", "x") @@ -180,10 +180,10 @@ def test_deps_depth_caps_closure(self): """--deps-depth limita la chiusura delle dipendenze: con 1 hop util.py (2 hop da api.py) resta fuori; senza limite entra.""" full = _run(self.root, "--seed", "app/api.py").stdout - self.assertIn("app/util.py — dipendenza", full) + self.assertIn("app/util.py — dependency", full) capped = _run(self.root, "--seed", "app/api.py", "--deps-depth", "1").stdout - self.assertIn("app/db.py — dipendenza", capped) - self.assertNotIn("app/util.py — dipendenza", capped) + self.assertIn("app/db.py — dependency", capped) + self.assertNotIn("app/util.py — dependency", capped) def test_package_root_prefixed_imports_resolved(self): """Root = directory del package stesso (root=pandas/): gli import col @@ -203,7 +203,7 @@ def test_package_root_prefixed_imports_resolved(self): with open(p, "w", encoding="utf-8") as f: f.write(content) out = _run(root, "--seed", "core/frame.py").stdout - self.assertIn("core/generic.py — dipendenza", out) + self.assertIn("core/generic.py — dependency", out) finally: shutil.rmtree(base) @@ -227,17 +227,17 @@ def test_budget_picks_richest_fitting_config(self): rientra; sotto, scala; se nemmeno il minimo (seed+test) rientra, INSODDISFACIBILE — ma i seed non si tagliano mai.""" out = _run(self.root, "--seed", "app/api.py", "--budget", "100000").stdout - self.assertIn("scelta config deps=full", out) + self.assertIn("chose config deps=full", out) self.assertIn("token", out) # slice piena ~49 token: budget 30 forza la discesa, util (2 hop) esce out = _run(self.root, "--seed", "app/api.py", "--budget", "30").stdout self.assertIn("app/api.py — seed", out) # util esce dalla SLICE, ma l'oracle di sufficienza lo dichiara page # fault atteso (non sparisce in silenzio: e' l'inverso, non l'oblio) - self.assertNotIn("app/util.py", out.split("## sufficienza")[0]) + self.assertNotIn("app/util.py", out.split("## sufficiency")[0]) self.assertIn("app/util.py", out) out = _run(self.root, "--symptom", PY_SYMPTOM, "--budget", "3").stdout - self.assertIn("INSODDISFACIBILE", out) + self.assertIn("UNSATISFIABLE", out) self.assertIn("app/db.py — seed", out) def test_budget_note_in_json(self): @@ -264,15 +264,15 @@ def test_argmin_prefers_smallest_sufficient_config(self): with open(os.path.join(base, rel), "w", encoding="utf-8") as f: f.write(content) out = _run(base, "--seed", "core.py").stdout - self.assertIn("argmin sufficiente", out) - self.assertIn("0 fault attesi", out) - self.assertIn("mid.py — importatore", out) # 1 hop: tenuto - self.assertNotIn("top.py — importatore", out) # 2 hop: fuori + self.assertIn("sufficient argmin", out) + self.assertIn("0 expected faults", out) + self.assertIn("mid.py — importer", out) # 1 hop: tenuto + self.assertNotIn("top.py — importer", out) # 2 hop: fuori # kill switch: comportamento storico intatto out = _run(base, "--seed", "core.py", env={"CK_ARGMIN": "0"}).stdout - self.assertNotIn("argmin sufficiente", out) - self.assertIn("top.py — importatore", out) + self.assertNotIn("sufficient argmin", out) + self.assertIn("top.py — importer", out) finally: shutil.rmtree(base) @@ -283,8 +283,8 @@ def test_argmin_never_accepts_expected_faults(self): out = _run(self.root, "--symptom", PY_SYMPTOM).stdout # util.py e' nella chiusura piena dei seed: qualunque scelta della # discesa deve tenerlo dentro la slice - self.assertIn("app/util.py — dipendenza", out) - self.assertIn("tests/test_db.py — test correlato", out) + self.assertIn("app/util.py — dependency", out) + self.assertIn("tests/test_db.py — related test", out) def test_t2b_symbol_slice_on_unsatisfiable_budget(self): """Budget insoddisfacibile a livello di file -> T2b: metodo di classe @@ -313,14 +313,14 @@ def test_t2b_symbol_slice_on_unsatisfiable_budget(self): f"RuntimeError: motore ingolfato irreparabilmente") out = _run(base, "--symptom", symptom, "--budget", "60").stdout self.assertIn("## T2b", out) - self.assertIn("Motore.avvia (righe 202-203)", out) + self.assertIn("Motore.avvia (lines 202-203)", out) self.assertIn("sed -n '202,203p'", out) - self.assertIn("RIENTRA", out) + self.assertIn("FITS", out) # JSON: struttura t2b presente out = _run(base, "--symptom", symptom, "--budget", "60", "--json").stdout data = json.loads(out) self.assertTrue(data["t2b"]["fits"]) - self.assertEqual(data["t2b"]["slices"][0]["esito"] in ("metodi", "slice", + self.assertEqual(data["t2b"]["slices"][0]["outcome"] in ("methods", "slice", "file intero (nessun simbolo dal sintomo)"), True) finally: shutil.rmtree(base) @@ -354,9 +354,9 @@ def test_t2b_go_symbol_slice(self): "--json").stdout data = json.loads(out) sl = data["t2b"]["slices"][0] - self.assertEqual(sl["esito"], "slice") + self.assertEqual(sl["outcome"], "slice") self.assertIn("Handle", sl["symbols"]) - self.assertIn("slice.py", sl["estrai"][0]) + self.assertIn("slice.py", sl["extract"][0]) # la slice a simbolo costa meno del file intero self.assertLess(sl["tokens"], len(big) // 4) finally: @@ -380,7 +380,7 @@ def test_budget_auto_from_context_state(self): capture_output=True, text=True, timeout=60, env=env) out = proc.stdout # finestra stimata 200k, in uso 100k, headroom 100k -> 40% - self.assertIn("auto: sessione abc12345", out) + self.assertIn("auto: session abc12345", out) self.assertIn("headroom ~100k -> budget 40k", out) finally: os.unlink(state) @@ -400,14 +400,14 @@ def test_manifest_cache_hit_and_invalidation(self): env = {"CK_SLICE_CACHE": "1", "CK_SLICE_CACHE_PATH": cpath} try: out1 = _run(self.root, "--symptom", PY_SYMPTOM, env=env).stdout - self.assertNotIn("manifest riusato", out1) - self.assertIn("operatore: T2@", out1) + self.assertNotIn("manifest reused", out1) + self.assertIn("operator: T2@", out1) out2 = _run(self.root, "--symptom", PY_SYMPTOM, env=env).stdout - self.assertIn("manifest riusato", out2) + self.assertIn("manifest reused", out2) self.assertIn(out1.strip().split("\n")[2], out2) # stesso manifest os.utime(os.path.join(self.root, "app", "db.py")) # tocca un file out3 = _run(self.root, "--symptom", PY_SYMPTOM, env=env).stdout - self.assertNotIn("manifest riusato", out3) + self.assertNotIn("manifest reused", out3) finally: if os.path.exists(cpath): os.unlink(cpath) @@ -433,7 +433,7 @@ def test_ambiguous_basename_not_guessed(self): f.write("pass\n") try: out = _run(self.root, "--symptom", 'File "db.py", line 1').stdout - self.assertIn("(nessuno)", out) + self.assertIn("(none)", out) finally: os.unlink(extra) @@ -498,17 +498,17 @@ def test_php_fatal_error_slice(self): out = _run(self.root, "--symptom", PHP_SYMPTOM).stdout self.assertIn("src/Service/Mailer.php — seed", out) self.assertIn("src/Controller/MailController.php — seed", out) - self.assertIn("src/Transport/Smtp.php — dipendenza", out) # use singolo - self.assertIn("src/Util/Logger.php — dipendenza", out) # use di gruppo - self.assertIn("src/Util/Clock.php — dipendenza", out) # use di gruppo - self.assertIn("tests/MailerTest.php — test correlato", out) + self.assertIn("src/Transport/Smtp.php — dependency", out) # use singolo + self.assertIn("src/Util/Logger.php — dependency", out) # use di gruppo + self.assertIn("src/Util/Clock.php — dependency", out) # use di gruppo + self.assertIn("tests/MailerTest.php — related test", out) self.assertNotIn("src/Orphan.php", out.split("## fuori slice")[0]) def test_php_require_edge(self): """require_once __DIR__.'/...' -> arco verso il file incluso.""" out = _run(self.root, "--seed", "src/legacy.php").stdout - self.assertIn("src/legacy.php <- seed esplicito", out) - self.assertIn("src/Service/Mailer.php — dipendenza", out) + self.assertIn("src/legacy.php <- explicit seed", out) + self.assertIn("src/Service/Mailer.php — dependency", out) def test_php_on_line_frame_seeds(self): """La sola forma 'in FILE.php on line N' basta come seed.""" @@ -584,17 +584,17 @@ def test_go_panic_slice(self): out = _run(self.root, "--symptom", GO_SYMPTOM).stdout self.assertIn("db/db.go — seed", out) self.assertIn("main.go — seed", out) - self.assertIn("api/api.go — dipendenza", out) # import a blocco - self.assertIn("db/util.go — dipendenza", out) # package = dir - self.assertIn("db/db_test.go — test correlato", out) + self.assertIn("api/api.go — dependency", out) # import a blocco + self.assertIn("db/util.go — dependency", out) # package = dir + self.assertIn("db/db_test.go — related test", out) self.assertNotIn("extra/orphan.go", out.split("## fuori slice")[0]) def test_go_aliased_single_import_edge(self): """`import alias "example.com/app/db"` (forma singola, con alias) -> arco verso il package db.""" out = _run(self.root, "--seed", "api/api.go").stdout - self.assertIn("api/api.go <- seed esplicito", out) - self.assertIn("db/db.go — dipendenza", out) + self.assertIn("api/api.go <- explicit seed", out) + self.assertIn("db/db.go — dependency", out) def test_go_without_gomod_declares_no_edges(self): """Senza go.mod gli import interni non si risolvono senza indovinare: @@ -608,8 +608,8 @@ def test_go_without_gomod_declares_no_edges(self): with open(path, "w", encoding="utf-8") as f: f.write(GO_FIXTURE[rel]) out = _run(bare, "--seed", "main.go").stdout - self.assertIn("main.go <- seed esplicito", out) - self.assertNotIn("db/db.go — dipendenza", out) + self.assertIn("main.go <- explicit seed", out) + self.assertNotIn("db/db.go — dependency", out) finally: _sh.rmtree(bare) @@ -661,10 +661,10 @@ def test_rust_panic_slice_with_declared_class(self): "store offline\n at src/main.rs:5").stdout self.assertIn("src/store.rs — seed", out) self.assertIn("src/main.rs — seed", out) - self.assertIn("src/util.rs — dipendenza", out) # stem univoco - self.assertIn("[grafo generico]", out) # classe dichiarata - self.assertIn("grafo generico (riferimenti testuali", out) # header - self.assertIn("tests/store_test.rs — test correlato", out) + self.assertIn("src/util.rs — dependency", out) # stem univoco + self.assertIn("[generic graph]", out) # classe dichiarata + self.assertIn("generic graph (textual references", out) # header + self.assertIn("tests/store_test.rs — related test", out) head = out.split("## fuori slice")[0] self.assertNotIn("src/orphan.rs", head) @@ -685,8 +685,8 @@ def test_c_filename_literal_edge(self): with open(os.path.join(base, rel), "w", encoding="utf-8") as f: f.write(content) out = _run(base, "--seed", "app.c").stdout - self.assertIn("app.c <- seed esplicito", out) - self.assertIn("render.h — dipendenza", out) + self.assertIn("app.c <- explicit seed", out) + self.assertIn("render.h — dependency", out) head = out.split("## fuori slice")[0] self.assertNotIn("render.c —", head) finally: @@ -730,11 +730,11 @@ def test_diff_files_become_seeds(self): proc = _run(self.root, "--from-diff", "HEAD") out = proc.stdout self.assertIn("app/db.py", out) - self.assertIn("file modificato nel diff (HEAD)", out) - self.assertIn("app/util.py — dipendenza", out) # blast radius - self.assertIn("app/api.py — importatore", out) - self.assertIn("tests/test_db.py — test correlato", out) - self.assertIn("non-sorgente", proc.stderr) # note.md scartato + self.assertIn("file changed in the diff (HEAD)", out) + self.assertIn("app/util.py — dependency", out) # blast radius + self.assertIn("app/api.py — importer", out) + self.assertIn("tests/test_db.py — related test", out) + self.assertIn("non-source", proc.stderr) # note.md scartato def test_diff_composes_with_symptom(self): out = _run(self.root, "--from-diff", "HEAD", @@ -770,7 +770,7 @@ def test_prior_seed_added_with_declared_why(self): "cold": []}) out = _run(self.root, "--symptom", PY_SYMPTOM, env=env).stdout self.assertIn("app/unrelated.py", out) - self.assertIn("prior appreso (T5: aperto fuori slice in 3 sessioni)", + self.assertIn("learned prior (T5: opened outside the slice in 3 sessions)", out) self.assertIn("app/db.py — seed", out) # i seed dal sintomo restano @@ -779,7 +779,7 @@ def test_cold_file_flagged_not_excluded(self): {"seeds": [], "cold": [{"path": "app/util.py", "sessions": 2}]}) out = _run(self.root, "--symptom", PY_SYMPTOM, env=env).stdout self.assertIn("app/util.py", out) # resta in slice - self.assertIn("freddo T5: mai aperto in 2 sessioni", out) + self.assertIn("T5 cold: never opened in 2 sessions", out) def test_priors_alone_do_not_create_slice(self): """Senza seed dal sintomo il fail-safe resta: nessuna proiezione.""" @@ -788,7 +788,7 @@ def test_priors_alone_do_not_create_slice(self): "cold": []}) proc = _run(self.root, "--symptom", "frase senza alcun sintomo", env=env) - self.assertIn("nessun seed riconosciuto", proc.stderr) + self.assertIn("no seed recognised", proc.stderr) def test_missing_prior_file_ignored(self): env = self._priors_env( @@ -808,7 +808,7 @@ def test_priors_change_cache_key(self): "cold": []}), **base} second = _run(self.root, "--symptom", PY_SYMPTOM, env=env).stdout self.assertNotIn("[cache T2@", second) # chiave diversa: no riuso - self.assertIn("prior appreso", second) + self.assertIn("learned prior", second) self.assertNotEqual(first, second) @@ -858,7 +858,7 @@ def test_lcov_adds_dynamic_seed(self): "SF:unrelated.py\nDA:1,0\nend_of_record\n") out = _run(self.root, "--seed", "app.py").stdout self.assertIn("plugin.py", out) - self.assertIn("eseguito a runtime (copertura)", out) + self.assertIn("executed at runtime (coverage)", out) self.assertNotIn("unrelated.py", out.split("## fuori slice")[0]) def test_cobertura_adds_dynamic_seed(self): @@ -869,7 +869,7 @@ def test_cobertura_adds_dynamic_seed(self): '') out = _run(self.root, "--seed", "app.py").stdout self.assertIn("plugin.py", out) - self.assertIn("eseguito a runtime (copertura)", out) + self.assertIn("executed at runtime (coverage)", out) def test_sqlite_coverage_adds_dynamic_seed(self): import sqlite3 @@ -884,7 +884,7 @@ def test_sqlite_coverage_adds_dynamic_seed(self): con.close() out = _run(self.root, "--seed", "app.py").stdout self.assertIn("plugin.py", out) - self.assertIn("eseguito a runtime (copertura)", out) + self.assertIn("executed at runtime (coverage)", out) def test_disabled_via_env(self): self._w("lcov.info", "SF:plugin.py\nDA:1,5\nend_of_record\n") @@ -898,8 +898,8 @@ def test_broad_coverage_declared_not_seeded(self): lines += [f"SF:mod{i}.py", "DA:1,1", "end_of_record"] self._w("lcov.info", "\n".join(lines) + "\n") out = _run(self.root, "--seed", "app.py").stdout - self.assertIn("## copertura", out) - self.assertIn("troppi per seminare", out) + self.assertIn("## coverage", out) + self.assertIn("too many to seed", out) self.assertNotIn("mod0.py — seed", out) # non seminato alla cieca def test_no_slice_from_coverage_alone(self): @@ -907,7 +907,7 @@ def test_no_slice_from_coverage_alone(self): (charter #5): la copertura non semina da sola.""" self._w("lcov.info", "SF:plugin.py\nDA:1,5\nend_of_record\n") proc = _run(self.root, "--symptom", "frase senza alcun sintomo") - self.assertIn("nessun seed riconosciuto", proc.stderr) + self.assertIn("no seed recognised", proc.stderr) class TestGitCochange(unittest.TestCase): @@ -948,7 +948,7 @@ def tearDownClass(cls): def test_cochange_added_as_prior(self): out = _run(self.root, "--seed", "app.py").stdout - self.assertIn("co-cambiato col seed", out) + self.assertIn("co-changed with the seed", out) self.assertIn("helper.py", out) def test_low_cochange_excluded(self): @@ -963,7 +963,7 @@ def test_disabled_via_env(self): def test_no_slice_from_cochange_alone(self): """Senza seed dal sintomo, nessuna proiezione (charter #5).""" proc = _run(self.root, "--symptom", "frase senza alcun sintomo") - self.assertIn("nessun seed riconosciuto", proc.stderr) + self.assertIn("no seed recognised", proc.stderr) class TestCtagsIndex(unittest.TestCase): @@ -1002,7 +1002,7 @@ def test_ctags_promotes_symbol_edge(self): self._tags('compute_checksum\tsrc/core.rs\t/^pub fn/;"\tf\n') out = _run(self.root, "--seed", "src/handler.rs").stdout self.assertIn("src/core.rs", out) # recuperato via simbolo - self.assertIn("PROMOSSO da indice ctags", out) + self.assertIn("PROMOTED by a ctags index", out) def test_without_tags_symbol_edge_missing(self): # nessun file tags: l'euristica non collega core.rs (solo simbolo citato) @@ -1047,8 +1047,8 @@ def test_sufficient_when_full_closure_present(self): def test_sufficient_text_manifest(self): out = _run(self.root, "--symptom", PY_SYMPTOM).stdout - self.assertIn("## sufficienza", out) - self.assertIn("SUFFICIENTE", out) + self.assertIn("## sufficiency", out) + self.assertIn("SUFFICIENT", out) def test_insufficient_when_budget_drops_dependencies(self): """Budget minuscolo -> fallback che droppa le dipendenze -> la @@ -1062,8 +1062,8 @@ def test_insufficient_when_budget_drops_dependencies(self): def test_insufficient_text_lists_expected_faults(self): out = _run(self.root, "--symptom", PY_SYMPTOM, "--budget", "40").stdout - self.assertIn("INSUFFICIENTE", out) - self.assertIn("PAGE FAULT ATTESI", out) + self.assertIn("INSUFFICIENT", out) + self.assertIn("EXPECTED PAGE FAULTS", out) self.assertIn("app/util.py", out) @@ -1075,7 +1075,7 @@ class TestAnchorEnds(RepoSliceCase): def _slice_paths(self, out: str) -> list[str]: """Path della sezione 'file della slice', nell'ordine di stampa.""" - body = out.split("## file della slice", 1)[1] + body = out.split("## slice files", 1)[1] body = body.split("\n## ", 1)[0] return [ln[2:].split(" — ", 1)[0].strip() for ln in body.splitlines() if ln.startswith("- ")] @@ -1105,7 +1105,7 @@ def test_anchor_is_reorder_not_selection(self): def test_anchored_header_declares_the_reorder(self): out = _run(self.root, "--seed", "app/db.py", "--anchor-ends").stdout - self.assertIn("estremi ancorati", out) + self.assertIn("ends anchored", out) self.assertIn("lost-in-the-middle", out) @@ -1131,7 +1131,7 @@ def test_literal_dynamic_import_added_as_seed(self): """import_module('pk.backend') letterale -> backend.py entra come seed col call site, benche' il grafo statico non lo raggiunga.""" out = _run(self.root, "--seed", "pk/registry.py").stdout - self.assertIn('pk/backend.py <- riferimento dinamico: ' + self.assertIn('pk/backend.py <- dynamic reference: ' 'import "pk.backend"', out) self.assertIn("pk/registry.py:7", out) # call site visibile self.assertIn("pk/backend.py — seed", out) @@ -1143,14 +1143,14 @@ def test_dynref_off_excludes_dynamic_target(self): env={"CK_DYNREF": "0"}).stdout self.assertNotIn("pk/backend.py — seed", out.split("## fuori slice")[0]) - self.assertNotIn("riferimento dinamico", out) + self.assertNotIn("dynamic reference", out) def test_non_literal_arg_is_declared_blind_never_guessed(self): """import_module(name): argomento non letterale -> punto cieco dichiarato, MAI indovinato (regola FQCN, charter #3).""" out = _run(self.root, "--seed", "pk/registry.py").stdout - self.assertIn("riferimenti dinamici non risolti", out) - self.assertIn("pk/registry.py:4 (argomento non letterale)", out) + self.assertIn("unresolved dynamic references", out) + self.assertIn("pk/registry.py:4 (non-literal argument)", out) # niente indovinelli: other.py non e' stato tirato dentro a caso self.assertNotIn("pk/other.py — seed", out) @@ -1164,15 +1164,15 @@ def test_dynamic_refs_alone_do_not_create_slice(self): """Come i prior (charter #5): senza seed dal sintomo, nessuna proiezione — i riferimenti dinamici non si autoseminano.""" proc = _run(self.root, "--symptom", "frase senza sintomo alcuno") - self.assertIn("nessun seed riconosciuto", proc.stderr) + self.assertIn("no seed recognised", proc.stderr) def test_blind_spots_in_json(self): out = _run(self.root, "--seed", "pk/registry.py", "--json").stdout data = json.loads(out) - self.assertTrue(any("argomento non letterale" in b + self.assertTrue(any("non-literal argument" in b for b in data.get("dynamic_blind", []))) self.assertTrue(any(s["path"] == "pk/backend.py" - and "riferimento dinamico" in s["why"] + and "dynamic reference" in s["why"] for s in data["seeds"])) def test_dynref_flag_changes_cache_key(self): diff --git a/claude-context-kernel/tests/test_resume.py b/claude-context-kernel/tests/test_resume.py index 592fc3d..82df117 100644 --- a/claude-context-kernel/tests/test_resume.py +++ b/claude-context-kernel/tests/test_resume.py @@ -20,9 +20,9 @@ """ MANIFEST_HEAD = """# kernel repo slice — manifest -operatore: T2@test +operator: T2@test repo: /repo -## seed (dal sintomo) +## seeds (from the symptom) - db.py <- citato nel sintomo""" @@ -71,7 +71,7 @@ def test_end_saves_by_repo(self): def test_new_session_same_repo_restores(self): self._end() ctx = self._start()["hookSpecificOutput"]["additionalContext"] - self.assertIn("TS(Q) della sessione precedente", ctx) + self.assertIn("TS(Q) from the previous session", ctx) self.assertIn("retry esattamente 3 volte", ctx) self.assertIn("## seed", ctx) @@ -96,7 +96,7 @@ def test_cleared_charter_drops_charter_keeps_slice(self): _util.run_script(_util.CHARTER, "", env=self.env, args=["clear", "--repo", self.repo]) ctx = self._start()["hookSpecificOutput"]["additionalContext"] - self.assertIn("TS(Q) della sessione precedente", ctx) + self.assertIn("TS(Q) from the previous session", ctx) self.assertNotIn("retry esattamente 3 volte", ctx) # carta pulita self.assertIn("## seed", ctx) # working set resta diff --git a/claude-context-kernel/tests/test_revealed.py b/claude-context-kernel/tests/test_revealed.py index bbb6d60..54968ad 100644 --- a/claude-context-kernel/tests/test_revealed.py +++ b/claude-context-kernel/tests/test_revealed.py @@ -13,17 +13,17 @@ MANIFEST = "\n".join([ "# kernel repo slice — manifest", - "operatore: T2@test", + "operator: T2@test", "repo: /repo", "sorgenti scansionati: 100 | slice: 3 file (-97%)", "", - "## seed (dal sintomo)", + "## seeds (from the symptom)", "- app.py <- citato nel sintomo", "", - "## file della slice (per rilevanza)", + "## slice files (per rilevanza)", "- app.py — seed", - "- pkg/calc.py — dipendenza a 1 hop (via app.py)", - "- test_app.py — test correlato (usa app.py)", + "- pkg/calc.py — dependency a 1 hop (via app.py)", + "- test_app.py — related test (usa app.py)", "", "## fuori slice (modello page-fault)", "97 sorgenti esclusi dal grafo degli import.", @@ -52,9 +52,9 @@ def setUp(self): "text": "[context-kernel] Sintomo rilevato\n" + MANIFEST}]}}, _tool_use("t1", "Read", {"file_path": "/repo/app.py"}), _tool_result("t1", "x" * 100 + - "\n[context-kernel: elise righe 46-90: 40 righe]" - "\n[context-kernel: 500 -> 100 token, -80%] " - "[copia ELISA: per l'integrale rileggi questo " + "\n[context-kernel: elided righe 46-90: 40 righe]" + "\n[context-kernel: 500 -> 100 tokens, -80%] " + "[ELIDED copy: for the full text read this " "stesso file]"), _tool_use("t2", "Read", {"file_path": "/repo/app.py"}), _tool_result("t2", "y" * 400), @@ -73,14 +73,14 @@ def _run(self, *args): def test_report_covers_all_signals(self): out = self._run(self.transcript).stdout - self.assertIn("manifest T2: 3 file", out) - self.assertIn("aperti dalla slice: 1/3", out) + self.assertIn("T2 manifest: 3 files", out) + self.assertIn("opened from the slice: 1/3", out) self.assertIn("pkg/calc.py", out) # mai aperto self.assertIn("test_app.py", out) - self.assertIn("FUORI slice", out) + self.assertIn("OUTSIDE the slice", out) self.assertIn("config/extra.py", out) # seed perso - self.assertIn("page fault post-elisione: 1", out) - self.assertIn("suggerimento", out) + self.assertIn("page faults after elision: 1", out) + self.assertIn("-> hint:", out) def test_fault_cost_measured_from_reread(self): r = json.loads(self._run(self.transcript, "--json").stdout)[0] @@ -91,7 +91,7 @@ def test_fault_cost_measured_from_reread(self): def test_directory_argument(self): out = self._run(self.tmp).stdout - self.assertIn("rilevanza rivelata", out) + self.assertIn("revealed relevance", out) def test_transcript_without_slice(self): empty = os.path.join(self.tmp, "vuota.jsonl") @@ -99,7 +99,7 @@ def test_transcript_without_slice(self): f.write(json.dumps(_tool_use("a", "Read", {"file_path": "/x.py"})) + "\n") out = self._run(empty).stdout - self.assertIn("nessun manifest T2", out) + self.assertIn("no T2 manifest", out) def test_no_transcript_exits_nonzero(self): proc = self._run(os.path.join(self.tmp, "inesistente.jsonl")) @@ -117,19 +117,19 @@ def _write_second_transcript(self): def test_aggregate_proposals_on_recurrence(self): self._write_second_transcript() out = self._run(self.tmp, "--aggregate").stdout - self.assertIn("AGGREGATO su 2 transcript", out) - self.assertIn("proposta di config", out) + self.assertIn("AGGREGATE over 2 transcript", out) + self.assertIn("suggested config", out) self.assertIn("# ck:raw", out) # fault su app.py x2 self.assertIn("app.py", out) - self.assertIn("candidalo ai seed", out) # extra.py fuori slice x2 + self.assertIn("propose as a slicer seed", out) # extra.py fuori slice x2 self.assertIn("config/extra.py", out) - self.assertIn("prior largo", out) # calc.py mai aperto x2 - self.assertIn("mai auto-tuning", out) # l'umano applica + self.assertIn("wide prior", out) # calc.py mai aperto x2 + self.assertIn("never auto-tunes", out) # l'umano applica def test_aggregate_single_occurrence_no_proposal(self): out = self._run(self.transcript, "--aggregate").stdout - self.assertIn("AGGREGATO su 1 transcript", out) - self.assertIn("nessun pattern ricorrente", out) + self.assertIn("AGGREGATE over 1 transcript", out) + self.assertIn("no recurrent pattern", out) def test_write_priors_on_recurrence(self): self._write_second_transcript() @@ -137,7 +137,7 @@ def test_write_priors_on_recurrence(self): out = _util.run_script( _util.REVEALED, "", args=[self.tmp, "--write-priors"], env={"CK_PRIORS_STATE": priors}).stdout - self.assertIn("prior scritti", out) + self.assertIn("priors written", out) with open(priors) as f: st = json.load(f) rec = st[os.path.normpath("/repo")] @@ -151,7 +151,7 @@ def test_write_priors_single_occurrence_no_write(self): out = _util.run_script( _util.REVEALED, "", args=[self.transcript, "--write-priors"], env={"CK_PRIORS_STATE": priors}).stdout - self.assertIn("nessun pattern ricorrente", out) + self.assertIn("no recurrent pattern", out) self.assertFalse(os.path.exists(priors)) def test_aggregate_json(self): diff --git a/claude-context-kernel/tests/test_savings.py b/claude-context-kernel/tests/test_savings.py index 679705a..e21b1ea 100644 --- a/claude-context-kernel/tests/test_savings.py +++ b/claude-context-kernel/tests/test_savings.py @@ -38,7 +38,7 @@ def test_report_totals_and_per_tool(self): finally: os.unlink(log) - self.assertIn("compressioni: 2", out) # la malformata non conta + self.assertIn("compressions: 2", out) # la malformata non conta self.assertIn("1,500", out) # before totale self.assertIn("1,150", out) # risparmiati self.assertIn("Bash", out) @@ -86,9 +86,9 @@ def test_statusline_session_and_total_verbose(self): out = proc.stdout self.assertIn("Fable 5", out) self.assertIn("mio-progetto", out) - self.assertIn("-12.0k sessione", out) # 9000+3000, solo abcd1234 - self.assertIn("-20.0k totale", out) # tutte le righe - self.assertIn("totale (-83%)", out) # 20000 elisi su 24000 before + self.assertIn("-12.0k session", out) # 9000+3000, solo abcd1234 + self.assertIn("-20.0k total", out) # tutte le righe + self.assertIn("total (-83%)", out) # 20000 elisi su 24000 before self.assertNotIn("su ctx", out) # senza tracker niente % sessione self.assertEqual(len(proc.stdout.strip().split("\n")), 1) @@ -107,7 +107,7 @@ def test_statusline_session_pct_from_context_tracker(self): finally: for p in (log, ctx): os.unlink(p) - self.assertIn("-12.0k sessione (-30% su ctx ~40.0k)", proc.stdout) + self.assertIn("-12.0k session (-30% of ctx ~40.0k)", proc.stdout) self.assertEqual(len(proc.stdout.strip().split("\n")), 1) def test_statusline_pct_omitted_when_session_empty(self): @@ -124,7 +124,7 @@ def test_statusline_pct_omitted_when_session_empty(self): finally: for p in (log, ctx): os.unlink(p) - self.assertIn("-0 sessione · -20.0k totale (-83%)", proc.stdout) + self.assertIn("-0 session · -20.0k total (-83%)", proc.stdout) def test_statusline_shows_pending_ab_and_canary_alarm(self): log = self._statusline_log() @@ -145,7 +145,7 @@ def test_statusline_shows_pending_ab_and_canary_alarm(self): # slim: il canary (ALLARME) resta, il contatore A/B (diagnostica) no self.assertIn("⚠ canary", slim.stdout) self.assertNotIn("A/B", slim.stdout) - self.assertIn("A/B: 2 in attesa", proc.stdout) + self.assertIn("A/B: 2 pending", proc.stdout) self.assertIn("⚠ canary", proc.stdout) finally: for p in (log, ab, canary): @@ -213,7 +213,7 @@ def test_html_report_contains_charts_and_totals(self): self.assertIn("-20.0k", html) # totale risparmiato self.assertIn("Bash", html) self.assertIn("abcd1234", html) # per-sessione - self.assertIn("Tabella", html) # vista accessibile + self.assertIn("Table", html) # vista accessibile self.assertNotIn("NaN", html) def test_html_report_empty_log_never_fatal(self): @@ -230,7 +230,7 @@ def test_html_report_empty_log_never_fatal(self): def test_missing_log_is_friendly(self): out = _run("/percorso/che/non/esiste.csv").stdout - self.assertIn("Nessun log ancora", out) + self.assertIn("No log yet", out) def test_empty_log_is_friendly(self): fd, log = tempfile.mkstemp(suffix=".csv") @@ -239,7 +239,7 @@ def test_empty_log_is_friendly(self): out = _run(log).stdout finally: os.unlink(log) - self.assertIn("Log presente ma vuoto", out) + self.assertIn("Log present but empty", out) if __name__ == "__main__": diff --git a/claude-context-kernel/tests/test_session_brief.py b/claude-context-kernel/tests/test_session_brief.py index 6c25494..c00a5c1 100644 --- a/claude-context-kernel/tests/test_session_brief.py +++ b/claude-context-kernel/tests/test_session_brief.py @@ -32,7 +32,7 @@ def test_savings_totals_from_ledger(self): "2026-07-17T07:01:00,Bash,2000,500,1500,abc\n") proc = _util.run_hook(BRIEF, PAYLOAD, env={"CK_LOG": log}) ctx = _util.hook_json(proc)["hookSpecificOutput"]["additionalContext"] - self.assertIn("2 compressioni", ctx) + self.assertIn("2 compressions", ctx) self.assertIn("2,100", ctx) finally: os.unlink(log) @@ -48,7 +48,7 @@ def test_ab_pending_reminder_in_brief(self): env={"CK_LOG": "/inesistente", "CK_AB_STATE": ab}) ctx = _util.hook_json(proc)["hookSpecificOutput"]["additionalContext"] - self.assertIn("2 campioni in attesa", ctx) + self.assertIn("2 samples awaiting judgement", ctx) self.assertIn("ab_verify.py", ctx) finally: os.unlink(ab) @@ -75,9 +75,9 @@ def test_canary_open_failures_contextualized_in_brief(self): env={"CK_LOG": "/inesistente", "CK_CANARY_STATE": canary}) ctx = _util.hook_json(proc)["hookSpecificOutput"]["additionalContext"] - self.assertIn("2 failure aperti", ctx) - self.assertIn("3 compressioni verificate OK dopo", ctx) - self.assertIn("1 sessioni in auto-degrade", ctx) + self.assertIn("2 open failures", ctx) + self.assertIn("3 compressions verified OK since", ctx) + self.assertIn("1 sessions auto-degraded", ctx) finally: os.unlink(canary) diff --git a/claude-context-kernel/tests/test_slice.py b/claude-context-kernel/tests/test_slice.py index 636ee2a..dfdf4e2 100644 --- a/claude-context-kernel/tests/test_slice.py +++ b/claude-context-kernel/tests/test_slice.py @@ -97,7 +97,7 @@ def test_missing_args_exits_nonzero_with_usage(self): proc = subprocess.run([sys.executable, _util.SLICE], capture_output=True, text=True) self.assertEqual(proc.returncode, 2) - self.assertIn("uso:", proc.stderr) + self.assertIn("usage:", proc.stderr) GO_FIXTURE = '''\ diff --git a/claude-context-kernel/tests/test_smoke.py b/claude-context-kernel/tests/test_smoke.py index fb67f03..a48ce09 100644 --- a/claude-context-kernel/tests/test_smoke.py +++ b/claude-context-kernel/tests/test_smoke.py @@ -69,9 +69,9 @@ def compressed_of(self, run_id: str, original: str) -> str: """Versione 'compressa' plausibile: testa, marker, coda, footer con hint di parcheggio — e l'ago NON c'e' (eliso).""" lines = original.strip().split("\n") - body = lines[:20] + ["[context-kernel: elise righe 21-390: ...]"] + lines[-8:] - footer = ('[context-kernel: 5799 -> 728 token, -87%] ' - f'[parcheggiato: python3 "{os.path.join(_util.HOOKS, "recall.py")}" ' + body = lines[:20] + ["[context-kernel: elided lines 21-390: ...]"] + lines[-8:] + footer = ('[context-kernel: 5799 -> 728 tokens, -87%] ' + f'[parked: python3 "{os.path.join(_util.HOOKS, "recall.py")}" ' f"{KEY} --grep PATTERN | --lines A-B]") return "\n".join(body) + "\n\n" + footer @@ -89,11 +89,11 @@ def test_needle_is_computed_and_positioned(self): run_id, needle, out = self.gen() lines = out.strip().split("\n") self.assertEqual(len(lines), 400) - self.assertIn(f"lotto {run_id} — inizio", lines[0]) - self.assertIn(f"lotto {run_id} — fine", lines[-1]) + self.assertIn(f"batch {run_id} — start", lines[0]) + self.assertIn(f"batch {run_id} — end", lines[-1]) self.assertIn(needle, lines[236]) # riga 237, 1-based self.assertEqual(sum(needle in l for l in lines), 1) - self.assertRegex(needle, r"^sentinella-\d{5}$") # niente hex/segnale + self.assertRegex(needle, r"^sentinel-\d{5}$") # niente hex/segnale class TestCheck(SmokeCase): @@ -105,9 +105,9 @@ def test_full_pass_end_to_end(self): proc = run_smoke("check", self.env) self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) self.assertNotIn("FAIL", proc.stdout) - self.assertIn("updatedToolOutput onorato", proc.stdout) - self.assertIn("ago eliso", proc.stdout) - self.assertIn("page fault inverso funzionante", proc.stdout) + self.assertIn("updatedToolOutput honoured", proc.stdout) + self.assertIn("needle elided", proc.stdout) + self.assertIn("inverse page fault working", proc.stdout) self.assertIn("SKIP", proc.stdout) # advisor: tap assente def test_raw_transcript_is_the_alarm_case(self): @@ -117,8 +117,8 @@ def test_raw_transcript_is_the_alarm_case(self): self.write_transcript(out) # integrale, con ago proc = run_smoke("check", self.env) self.assertEqual(proc.returncode, 1) - self.assertIn("ha ignorato updatedToolOutput", proc.stdout) - self.assertIn("ago ancora presente", proc.stdout) + self.assertIn("ignored updatedToolOutput", proc.stdout) + self.assertIn("needle still present", proc.stdout) def test_missing_generate_state(self): proc = run_smoke("check", self.env) @@ -143,10 +143,10 @@ def test_advisor_leg_runs_on_real_context_state(self): json.dump({"sessione": {"model": "m", "context_tokens": 50_000}}, f) proc = run_smoke("check", self.env) self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) - self.assertIn("avviso a soglia bassa: PASS", proc.stdout) - self.assertIn("one-shot per sessione: PASS", proc.stdout) - self.assertIn("subagent muto: PASS", proc.stdout) - self.assertIn("soglia alta muta: PASS", proc.stdout) + self.assertIn("warning at low threshold: PASS", proc.stdout) + self.assertIn("one-shot per session: PASS", proc.stdout) + self.assertIn("subagent silent: PASS", proc.stdout) + self.assertIn("high threshold silent: PASS", proc.stdout) if __name__ == "__main__": diff --git a/claude-context-kernel/tests/test_t1_extras.py b/claude-context-kernel/tests/test_t1_extras.py index b33a895..ae2772f 100644 --- a/claude-context-kernel/tests/test_t1_extras.py +++ b/claude-context-kernel/tests/test_t1_extras.py @@ -50,7 +50,7 @@ def test_second_identical_run_gets_marker(self): self.assertEqual(_util.hook_json(first), {}) # sotto MIN: intatto second = self._bash(self.OUT, "git status") upd = _util.hook_json(second)["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("output IDENTICO", upd["stdout"]) + self.assertIn("output IDENTICAL", upd["stdout"]) self.assertIn("[context-kernel:", upd["stdout"]) # footer def test_rerun_after_marker_passes_integral(self): @@ -75,7 +75,7 @@ def test_rerun_after_elision_is_integral(self): for i in range(150)) first = self._bash(noisy, "make build") upd = _util.hook_json(first)["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("elise", upd["stdout"]) # eliso davvero + self.assertIn("elided", upd["stdout"]) # eliso davvero second = self._bash(noisy, "make build") self.assertEqual(_util.hook_json(second), {}) # integrale @@ -100,8 +100,8 @@ def test_groups_by_file_and_caps_matches(self): f"{f}-{i} {'y' * 20}") proc = self._grep("\n".join(lines)) upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("match oltre il 5o per file", upd) - self.assertIn("[+15 altri match in src/modulo_0.py]", upd) + self.assertIn("matches beyond the 5th per file", upd) + self.assertIn("[+15 more matches in src/modulo_0.py]", upd) self.assertIn("src/modulo_9.py:1:", upd) # nessun file perso self.assertNotIn("occorrenza unica 0-7", upd) # oltre il cap self.assertIn("[context-kernel:", upd) # footer @@ -132,11 +132,11 @@ def test_giant_python_read_becomes_outline(self): proc = _util.run_hook(_util.COMPRESS, payload, env=self.env) got = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] content = got["file"]["content"] - self.assertIn("proiettato a OUTLINE", content) - self.assertIn("def funzione_7(argomento_7): # righe ", content) + self.assertIn("projected to an OUTLINE", content) + self.assertIn("def funzione_7(argomento_7): # lines ", content) self.assertIn("import os", content) self.assertNotIn("valore_3", content) # corpi elisi - self.assertIn("copia ELISA", proc.stdout) # page fault attivo + self.assertIn("ELIDED copy", proc.stdout) # page fault attivo def test_syntax_error_falls_back_to_code_aware(self): payload = _util.read_payload(self._giant_py(broken=True), @@ -145,8 +145,8 @@ def test_syntax_error_falls_back_to_code_aware(self): proc = _util.run_hook(_util.COMPRESS, payload, env=self.env) got = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] content = got["file"]["content"] - self.assertNotIn("proiettato a OUTLINE", content) - self.assertIn("righe di corpo", content) # fallback classico + self.assertNotIn("projected to an OUTLINE", content) + self.assertIn("lines of body", content) # fallback classico class TestAdaptiveRate(_Base): @@ -163,7 +163,7 @@ def test_full_headroom_keeps_default_window(self): proc = self._read_log("sess-adp1") content = _util.hook_json(proc)["hookSpecificOutput"][ "updatedToolOutput"]["file"]["content"] - self.assertIn("righe 46-280:", content) # HEAD 45 / TAIL 20 + self.assertIn("lines 46-280:", content) # HEAD 45 / TAIL 20 def test_high_usage_shrinks_head_tail(self): # finestra DICHIARATA via env (fonte 1 di window.py): 190k/200k=95%. @@ -175,7 +175,7 @@ def test_high_usage_shrinks_head_tail(self): proc = self._read_log("sess-adp2") content = _util.hook_json(proc)["hookSpecificOutput"][ "updatedToolOutput"]["file"]["content"] - self.assertIn("righe 23-290:", content) # scala 0.5 + self.assertIn("lines 23-290:", content) # scala 0.5 class TestProseProjection(_Base): @@ -195,7 +195,7 @@ def test_webfetch_link_runs_collapse(self): } proc = _util.run_hook(_util.COMPRESS, payload, env=self.env) upd = _util.hook_json(proc)["hookSpecificOutput"]["updatedToolOutput"] - self.assertIn("righe di link/navigazione", upd["result"]) + self.assertIn("lines of links/navigation", upd["result"]) self.assertIn("voce di menu 0", upd["result"]) # le prime 2 restano self.assertNotIn("voce di menu 25", upd["result"]) self.assertIn("Paragrafo di testo vero numero 29", upd["result"]) diff --git a/claude-context-kernel/tests/test_task_switch.py b/claude-context-kernel/tests/test_task_switch.py index f5b7f7c..9390ead 100644 --- a/claude-context-kernel/tests/test_task_switch.py +++ b/claude-context-kernel/tests/test_task_switch.py @@ -65,14 +65,14 @@ def test_first_symptom_has_no_switch_note(self): ctx = self._ctx(_util.run_hook( _util.SYMPTOM, _payload(TRACEBACK_A, self.repo), env=self.env)) self.assertIn("working set", ctx) - self.assertNotIn("CAMBIO TASK", ctx) + self.assertNotIn("TASK SWITCH", ctx) def test_second_different_symptom_declares_switch_with_diff(self): _util.run_hook(_util.SYMPTOM, _payload(TRACEBACK_A, self.repo), env=self.env) ctx = self._ctx(_util.run_hook( _util.SYMPTOM, _payload(TRACEBACK_B, self.repo), env=self.env)) - self.assertIn("CAMBIO TASK", ctx) + self.assertIn("TASK SWITCH", ctx) self.assertIn("web.py", ctx) # diff: file nuovi di Q2 def test_same_symptom_twice_is_not_a_switch(self): @@ -80,7 +80,7 @@ def test_same_symptom_twice_is_not_a_switch(self): env=self.env) ctx = self._ctx(_util.run_hook( _util.SYMPTOM, _payload(TRACEBACK_A, self.repo), env=self.env)) - self.assertNotIn("CAMBIO TASK", ctx) + self.assertNotIn("TASK SWITCH", ctx) def test_other_session_is_not_a_switch(self): _util.run_hook(_util.SYMPTOM, @@ -89,7 +89,7 @@ def test_other_session_is_not_a_switch(self): ctx = self._ctx(_util.run_hook( _util.SYMPTOM, _payload(TRACEBACK_B, self.repo, session="s-due"), env=self.env)) - self.assertNotIn("CAMBIO TASK", ctx) + self.assertNotIn("TASK SWITCH", ctx) def test_switch_detector_disabled_via_env(self): _util.run_hook(_util.SYMPTOM, _payload(TRACEBACK_A, self.repo), @@ -98,7 +98,7 @@ def test_switch_detector_disabled_via_env(self): _util.SYMPTOM, _payload(TRACEBACK_B, self.repo), env={**self.env, "CK_TASK_SWITCH": "0"})) self.assertIn("working set", ctx) # la slice arriva comunque - self.assertNotIn("CAMBIO TASK", ctx) + self.assertNotIn("TASK SWITCH", ctx) if __name__ == "__main__":