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__":