Last active
April 30, 2026 08:43
-
-
Save benhook1013/0ddeac5277f5515080ff5756bc9d7849 to your computer and use it in GitHub Desktop.
t3-checkpoints local CLI utility and bash completion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| umask 077 | |
| CONFIG_FILE="${T3_CHECKPOINTS_CONFIG_FILE:-$HOME/.t3-checkpoints/config.env}" | |
| if [[ ! -f "$CONFIG_FILE" ]]; then | |
| mkdir -p "$(dirname "$CONFIG_FILE")" | |
| cat > "$CONFIG_FILE" <<'EOF' | |
| # t3-checkpoints local config | |
| # | |
| # Uncomment and edit any values you want to override for this machine. | |
| # | |
| # T3_CHECKPOINTS_REPO_DIR="$HOME/src/FireMUD" | |
| # T3_CHECKPOINTS_CODEX_ROOT="$HOME/.codex" | |
| # T3_CHECKPOINTS_T3CODE_DB_PATHS="$HOME/.t3/userdata/state.sqlite:$HOME/.t3/dev/state.sqlite" | |
| # T3_CHECKPOINTS_DEFAULT_KEEP="5" | |
| # T3_CHECKPOINTS_DEFAULT_PRUNE_DAYS="5" | |
| EOF | |
| chmod 600 "$CONFIG_FILE" | |
| fi | |
| # shellcheck disable=SC1090 | |
| source "$CONFIG_FILE" | |
| DEFAULT_KEEP="${T3_CHECKPOINTS_DEFAULT_KEEP:-5}" | |
| DEFAULT_PRUNE_DAYS="${T3_CHECKPOINTS_DEFAULT_PRUNE_DAYS:-5}" | |
| MIN_SAFE_KEEP_COMMITS=3 | |
| KEEP="$DEFAULT_KEEP" | |
| PRUNE_DAYS="$DEFAULT_PRUNE_DAYS" | |
| APPLY=0 | |
| RUN_GC=1 | |
| INSPECT_CODEX=1 | |
| INSPECT_T3CODE=1 | |
| CODEX_ROOT="${T3_CHECKPOINTS_CODEX_ROOT:-$HOME/.codex}" | |
| DEFAULT_T3CODE_DB_PATHS="$HOME/.t3/userdata/state.sqlite:$HOME/.t3/dev/state.sqlite" | |
| T3CODE_DB_PATHS_RAW="${T3_CHECKPOINTS_T3CODE_DB_PATHS:-$DEFAULT_T3CODE_DB_PATHS}" | |
| declare -a T3CODE_DB_PATHS=() | |
| REPO_DIR="${T3_CHECKPOINTS_REPO_DIR:-}" | |
| COMMAND="" | |
| MODE="" | |
| usage() { | |
| cat <<'EOF' | |
| Inspect and optionally prune T3 Code checkpoint refs. | |
| Usage: | |
| t3-checkpoints help | |
| t3-checkpoints plan prune-chat-checkpoints [N] [options] | |
| t3-checkpoints apply prune-chat-checkpoints [N] [options] | |
| t3-checkpoints plan clear-chat-checkpoints [N] [options] | |
| t3-checkpoints apply clear-chat-checkpoints [N] [options] | |
| t3cp ... | |
| Commands: | |
| help | |
| Show this help text. | |
| plan | |
| Report what would be deleted. | |
| apply | |
| Delete refs after reporting the plan. | |
| Modes: | |
| prune-chat-checkpoints [N] | |
| Keep turn 0 plus the latest N-1 non-baseline checkpoint refs per thread. | |
| This preserves the canonical baseline ref used by T3 Code diffs. Trim mode | |
| enforces a minimum of 3 total refs so the previous-turn and latest-turn | |
| diff flow keeps working. Default: 5. | |
| clear-chat-checkpoints [N] | |
| Delete every checkpoint ref for threads whose latest checkpoint is older | |
| than N days. This removes the entire checkpoint history for those chats. | |
| After prune, T3/Codex-style checkpoint diff viewing and checkpoint-based | |
| revert for those chats will no longer work. Default: 5. | |
| Options for plan/apply: | |
| --repo PATH | |
| Operate on the Git repo at PATH. | |
| --no-gc | |
| Skip reflog expiry and git gc after deleting refs. Applies to `apply`. | |
| --codex-root PATH | |
| Inspect Codex state under PATH instead of ~/.codex. | |
| --no-codex-inspect | |
| Skip local Codex-state inspection and classification. Advisory only. | |
| --t3-db PATH | |
| Inspect a specific T3 Code SQLite state database. May be passed multiple | |
| times. Defaults to ~/.t3/userdata/state.sqlite and ~/.t3/dev/state.sqlite. | |
| --no-t3code-inspect | |
| Skip T3 Code state inspection and safety guards. Unsafe for apply. | |
| --help | |
| Show command help. | |
| This utility only considers refs matching: | |
| refs/t3/checkpoints/<thread-id>/turn/<integer> | |
| It never touches normal branches, tags, remotes, or other refs. | |
| When enabled, T3 Code state inspection is treated as authoritative for whether | |
| checkpoint refs are still needed for live or archived threads. | |
| EOF | |
| } | |
| die() { | |
| echo "Error: $*" >&2 | |
| exit 1 | |
| } | |
| validate_positive_integer() { | |
| local value="$1" | |
| [[ "$value" =~ ^[1-9][0-9]*$ ]] | |
| } | |
| format_epoch() { | |
| local epoch="$1" | |
| if [[ -z "$epoch" || "$epoch" == "0" ]]; then | |
| printf 'unknown' | |
| return | |
| fi | |
| date -d "@$epoch" '+%Y-%m-%d %H:%M:%S %z' | |
| } | |
| inspect_codex_state() { | |
| local thread_file="$1" | |
| local output_file="$2" | |
| if ! command -v python3 >/dev/null 2>&1; then | |
| echo "Codex-state inspection unavailable: python3 not found." >&2 | |
| return 1 | |
| fi | |
| if ! python3 - "$CODEX_ROOT" "$thread_file" >"$output_file" <<'PY' | |
| import base64 | |
| import json | |
| import sqlite3 | |
| import sys | |
| from pathlib import Path | |
| codex_root = Path(sys.argv[1]).expanduser() | |
| thread_file = Path(sys.argv[2]) | |
| thread_ids = [line.strip() for line in thread_file.read_text().splitlines() if line.strip()] | |
| def decode_thread_id(value: str): | |
| padded = value + "=" * ((4 - len(value) % 4) % 4) | |
| try: | |
| decoded = base64.urlsafe_b64decode(padded.encode("ascii")) | |
| except Exception: | |
| return None | |
| if len(decoded) == 16: | |
| try: | |
| import uuid | |
| return str(uuid.UUID(bytes=decoded)) | |
| except Exception: | |
| return None | |
| try: | |
| text = decoded.decode("utf-8") | |
| except UnicodeDecodeError: | |
| return None | |
| return text if text and text.isprintable() else None | |
| records = [] | |
| db_thread_ids = set() | |
| db_path = codex_root / "state_5.sqlite" | |
| db_status = "missing" | |
| db_error = None | |
| if db_path.exists(): | |
| try: | |
| with sqlite3.connect(db_path) as conn: | |
| tables = {row[0] for row in conn.execute("select name from sqlite_master where type = 'table'")} | |
| if "threads" in tables: | |
| columns = {row[1] for row in conn.execute("pragma table_info(threads)")} | |
| if "id" in columns: | |
| db_thread_ids = { | |
| str(row[0]) for row in conn.execute("select id from threads") if row[0] is not None | |
| } | |
| db_status = "threads.id" | |
| else: | |
| db_status = "threads table without id column" | |
| else: | |
| db_status = "threads table missing" | |
| except Exception as exc: | |
| db_status = "error" | |
| db_error = str(exc) | |
| sessions_root = codex_root / "sessions" | |
| session_hits = {thread_id: [] for thread_id in thread_ids} | |
| session_status = "missing" | |
| if sessions_root.exists(): | |
| session_status = "scanned" | |
| search_tokens = {} | |
| for thread_id in thread_ids: | |
| tokens = {thread_id} | |
| decoded = decode_thread_id(thread_id) | |
| if decoded: | |
| tokens.add(decoded) | |
| search_tokens[thread_id] = tuple(token for token in tokens if token) | |
| for path in sessions_root.rglob("*"): | |
| if not path.is_file() or path.suffix not in {".json", ".jsonl"}: | |
| continue | |
| try: | |
| text = path.read_text(errors="ignore") | |
| except Exception: | |
| continue | |
| rel = str(path.relative_to(codex_root)) | |
| for thread_id, tokens in search_tokens.items(): | |
| if session_hits[thread_id]: | |
| continue | |
| if any(token in text for token in tokens): | |
| session_hits[thread_id].append(rel) | |
| for thread_id in thread_ids: | |
| decoded = decode_thread_id(thread_id) | |
| exact_db = thread_id in db_thread_ids | |
| decoded_db = bool(decoded and decoded in db_thread_ids) | |
| session_paths = session_hits.get(thread_id, []) | |
| if exact_db or decoded_db: | |
| classification = "active-candidate" | |
| elif session_paths: | |
| classification = "archived-candidate" | |
| else: | |
| classification = "orphaned-candidate" | |
| details = [] | |
| if exact_db: | |
| details.append("exact sqlite thread id match") | |
| if decoded_db: | |
| details.append(f"decoded sqlite thread id match ({decoded})") | |
| elif decoded: | |
| details.append(f"decoded id candidate: {decoded}") | |
| if session_paths: | |
| details.append(f"session metadata hit: {session_paths[0]}") | |
| if not details: | |
| details.append("no sqlite or session metadata match found") | |
| records.append( | |
| { | |
| "threadId": thread_id, | |
| "classification": classification, | |
| "details": "; ".join(details), | |
| } | |
| ) | |
| result = { | |
| "codexRoot": str(codex_root), | |
| "dbStatus": db_status, | |
| "dbError": db_error, | |
| "sessionStatus": session_status, | |
| "records": records, | |
| } | |
| print(json.dumps(result)) | |
| PY | |
| then | |
| echo "Codex-state inspection failed." >&2 | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| populate_t3code_db_paths() { | |
| local raw_path path | |
| local old_ifs="$IFS" | |
| IFS=':' | |
| read -r -a raw_paths <<<"$T3CODE_DB_PATHS_RAW" | |
| IFS="$old_ifs" | |
| for raw_path in "${raw_paths[@]}"; do | |
| path="${raw_path/#\~/$HOME}" | |
| [[ -n "$path" ]] || continue | |
| T3CODE_DB_PATHS+=("$path") | |
| done | |
| } | |
| inspect_t3code_state() { | |
| local thread_file="$1" | |
| local output_file="$2" | |
| if ! command -v python3 >/dev/null 2>&1; then | |
| echo "T3 Code state inspection unavailable: python3 not found." >&2 | |
| return 1 | |
| fi | |
| local joined_paths | |
| joined_paths="$(printf '%s\n' "${T3CODE_DB_PATHS[@]}")" | |
| if ! T3CP_T3CODE_DB_PATHS="$joined_paths" python3 - "$thread_file" "$output_file" <<'PY' | |
| import json | |
| import os | |
| import sqlite3 | |
| import sys | |
| from pathlib import Path | |
| thread_file = Path(sys.argv[1]) | |
| output_file = Path(sys.argv[2]) | |
| db_paths = [line.strip() for line in os.environ.get("T3CP_T3CODE_DB_PATHS", "").splitlines() if line.strip()] | |
| thread_ids = [line.strip() for line in thread_file.read_text().splitlines() if line.strip()] | |
| records = {} | |
| db_reports = [] | |
| for thread_id in thread_ids: | |
| records[thread_id] = { | |
| "threadId": thread_id, | |
| "classification": "orphaned-candidate", | |
| "details": [], | |
| "sources": [], | |
| } | |
| def add_detail(thread_id: str, db_path: Path, detail: str): | |
| record = records[thread_id] | |
| record["details"].append(detail) | |
| record["sources"].append(str(db_path)) | |
| def ensure_columns(conn, table): | |
| return {row[1] for row in conn.execute(f"pragma table_info({table})")} | |
| for raw_db_path in db_paths: | |
| db_path = Path(raw_db_path).expanduser() | |
| report = { | |
| "path": str(db_path), | |
| "status": "missing", | |
| "error": None, | |
| } | |
| if not db_path.exists(): | |
| db_reports.append(report) | |
| continue | |
| try: | |
| with sqlite3.connect(db_path) as conn: | |
| tables = {row[0] for row in conn.execute("select name from sqlite_master where type = 'table'")} | |
| report["status"] = "opened" | |
| thread_columns = ensure_columns(conn, "projection_threads") if "projection_threads" in tables else set() | |
| runtime_columns = ensure_columns(conn, "provider_session_runtime") if "provider_session_runtime" in tables else set() | |
| turn_columns = ensure_columns(conn, "projection_turns") if "projection_turns" in tables else set() | |
| thread_rows = {} | |
| if "projection_threads" in tables and "thread_id" in thread_columns: | |
| for row in conn.execute( | |
| """ | |
| select thread_id, archived_at, deleted_at | |
| from projection_threads | |
| where thread_id in (%s) | |
| """ | |
| % ",".join("?" for _ in thread_ids), | |
| thread_ids, | |
| ): | |
| thread_rows[str(row[0])] = { | |
| "archived_at": row[1], | |
| "deleted_at": row[2], | |
| } | |
| runtime_rows = set() | |
| if "provider_session_runtime" in tables and "thread_id" in runtime_columns: | |
| runtime_rows = { | |
| str(row[0]) | |
| for row in conn.execute( | |
| """ | |
| select thread_id | |
| from provider_session_runtime | |
| where thread_id in (%s) | |
| """ | |
| % ",".join("?" for _ in thread_ids), | |
| thread_ids, | |
| ) | |
| } | |
| checkpoint_counts = {} | |
| if ( | |
| "projection_turns" in tables | |
| and "thread_id" in turn_columns | |
| and "checkpoint_turn_count" in turn_columns | |
| ): | |
| for row in conn.execute( | |
| """ | |
| select thread_id, count(*) | |
| from projection_turns | |
| where thread_id in (%s) | |
| and checkpoint_turn_count is not null | |
| group by thread_id | |
| """ | |
| % ",".join("?" for _ in thread_ids), | |
| thread_ids, | |
| ): | |
| checkpoint_counts[str(row[0])] = int(row[1] or 0) | |
| for thread_id in thread_ids: | |
| thread_row = thread_rows.get(thread_id) | |
| runtime_bound = thread_id in runtime_rows | |
| checkpoint_count = checkpoint_counts.get(thread_id, 0) | |
| if thread_row: | |
| deleted_at = thread_row["deleted_at"] | |
| archived_at = thread_row["archived_at"] | |
| if deleted_at: | |
| add_detail(thread_id, db_path, f"thread deleted in T3 Code state ({deleted_at})") | |
| if records[thread_id]["classification"] == "orphaned-candidate": | |
| records[thread_id]["classification"] = "deleted-candidate" | |
| elif archived_at: | |
| add_detail(thread_id, db_path, f"thread archived in T3 Code state ({archived_at})") | |
| records[thread_id]["classification"] = "archived-candidate" | |
| else: | |
| add_detail(thread_id, db_path, "thread exists in live T3 Code state") | |
| records[thread_id]["classification"] = "live-candidate" | |
| elif runtime_bound: | |
| add_detail(thread_id, db_path, "provider runtime binding still exists in T3 Code state") | |
| if records[thread_id]["classification"] not in ("live-candidate", "archived-candidate"): | |
| records[thread_id]["classification"] = "runtime-bound-candidate" | |
| elif checkpoint_count > 0: | |
| add_detail(thread_id, db_path, f"T3 Code still stores {checkpoint_count} projected checkpoint row(s)") | |
| if records[thread_id]["classification"] == "orphaned-candidate": | |
| records[thread_id]["classification"] = "lingering-state-candidate" | |
| except Exception as exc: | |
| report["status"] = "error" | |
| report["error"] = str(exc) | |
| db_reports.append(report) | |
| for record in records.values(): | |
| if not record["details"]: | |
| record["details"].append("no T3 Code state match found") | |
| record["details"] = "; ".join(record["details"]) | |
| record["sources"] = sorted(set(record["sources"])) | |
| output_file.write_text( | |
| json.dumps( | |
| { | |
| "dbReports": db_reports, | |
| "existingDbCount": sum(1 for report in db_reports if report["status"] != "missing"), | |
| "openedDbCount": sum(1 for report in db_reports if report["status"] == "opened"), | |
| "allExistingDbPathsOpened": all( | |
| report["status"] == "opened" | |
| for report in db_reports | |
| if report["status"] != "missing" | |
| ), | |
| "records": [records[thread_id] for thread_id in thread_ids], | |
| } | |
| ) | |
| ) | |
| PY | |
| then | |
| echo "T3 Code state inspection failed." >&2 | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| resolve_repo_dir() { | |
| if [[ -n "$REPO_DIR" ]]; then | |
| [[ -d "$REPO_DIR/.git" || -f "$REPO_DIR/.git" ]] || die "--repo does not point at a Git repository: $REPO_DIR" | |
| printf '%s\n' "$REPO_DIR" | |
| return 0 | |
| fi | |
| if git rev-parse --show-toplevel >/dev/null 2>&1; then | |
| git rev-parse --show-toplevel | |
| return 0 | |
| fi | |
| die "not inside a Git repository; pass --repo PATH or set T3_CHECKPOINTS_REPO_DIR in $CONFIG_FILE" | |
| } | |
| parse_command_and_options() { | |
| if (($# == 0)); then | |
| usage | |
| exit 0 | |
| fi | |
| COMMAND="$1" | |
| shift | |
| case "$COMMAND" in | |
| help|-h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| plan) | |
| APPLY=0 | |
| ;; | |
| apply) | |
| APPLY=1 | |
| ;; | |
| *) | |
| die "unknown command: $COMMAND" | |
| ;; | |
| esac | |
| if (($# > 0)) && [[ "$1" == "help" || "$1" == "--help" || "$1" == "-h" ]]; then | |
| usage | |
| exit 0 | |
| fi | |
| (($# > 0)) || die "missing mode for $COMMAND" | |
| MODE="$1" | |
| shift | |
| case "$MODE" in | |
| prune-chat-checkpoints) | |
| if (($# > 0)) && [[ "$1" =~ ^[0-9]+$ ]]; then | |
| KEEP="$1" | |
| shift | |
| fi | |
| ;; | |
| clear-chat-checkpoints) | |
| if (($# > 0)) && [[ "$1" =~ ^[0-9]+$ ]]; then | |
| PRUNE_DAYS="$1" | |
| shift | |
| fi | |
| ;; | |
| *) | |
| die "unknown mode for $COMMAND: $MODE" | |
| ;; | |
| esac | |
| while (($# > 0)); do | |
| case "$1" in | |
| --repo) | |
| (($# >= 2)) || die "--repo requires a value" | |
| REPO_DIR="$2" | |
| shift 2 | |
| ;; | |
| --no-gc) | |
| RUN_GC=0 | |
| shift | |
| ;; | |
| --codex-root) | |
| (($# >= 2)) || die "--codex-root requires a value" | |
| CODEX_ROOT="$2" | |
| shift 2 | |
| ;; | |
| --no-codex-inspect) | |
| INSPECT_CODEX=0 | |
| shift | |
| ;; | |
| --t3-db) | |
| (($# >= 2)) || die "--t3-db requires a value" | |
| T3CODE_DB_PATHS+=("$2") | |
| shift 2 | |
| ;; | |
| --no-t3code-inspect) | |
| INSPECT_T3CODE=0 | |
| shift | |
| ;; | |
| --help|-h) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| die "unknown option for $COMMAND $MODE: $1" | |
| ;; | |
| esac | |
| done | |
| } | |
| run_main() { | |
| validate_positive_integer "$KEEP" || die "prune-chat-checkpoints must be a positive integer" | |
| validate_positive_integer "$PRUNE_DAYS" || die "clear-chat-checkpoints must be a positive integer" | |
| if [[ "$MODE" == "prune-chat-checkpoints" ]] && ((KEEP < MIN_SAFE_KEEP_COMMITS)); then | |
| echo "Requested prune-chat-checkpoints=$KEEP is below the safe minimum; using $MIN_SAFE_KEEP_COMMITS instead." >&2 | |
| KEEP="$MIN_SAFE_KEEP_COMMITS" | |
| fi | |
| REPO_DIR="$(resolve_repo_dir)" | |
| populate_t3code_db_paths | |
| declare -A THREAD_ENTRIES=() | |
| declare -A THREAD_LATEST_TS=() | |
| declare -A T3CODE_CLASSIFICATION=() | |
| declare -A T3CODE_DETAILS=() | |
| local threads_found=0 | |
| local valid_refs=0 | |
| local kept_refs=0 | |
| local deleted_refs=0 | |
| local protected_refs=0 | |
| local protected_threads=0 | |
| local ignored_refs=0 | |
| local inactive_threads_matched=0 | |
| local inactive_refs_matched=0 | |
| local now_epoch inactive_cutoff_epoch latest_ts count refs_to_delete latest_ts_human | |
| local thread_id turn_number ref ref_ts latest_existing entry rest | |
| local t3code_existing_db_count=0 | |
| local t3code_all_existing_opened=0 | |
| now_epoch="$(date +%s)" | |
| inactive_cutoff_epoch=$((now_epoch - PRUNE_DAYS * 86400)) | |
| mapfile -t ALL_REFS < <(git -C "$REPO_DIR" for-each-ref --format='%(refname)%09%(creatordate:unix)' refs/t3/checkpoints) | |
| for ref_with_ts in "${ALL_REFS[@]}"; do | |
| ref="${ref_with_ts%%$'\t'*}" | |
| ref_ts="${ref_with_ts#*$'\t'}" | |
| [[ "$ref_ts" =~ ^[0-9]+$ ]] || ref_ts=0 | |
| if [[ "$ref" =~ ^refs/t3/checkpoints/([^/]+)/turn/([0-9]+)$ ]]; then | |
| thread_id="${BASH_REMATCH[1]}" | |
| turn_number="${BASH_REMATCH[2]}" | |
| THREAD_ENTRIES["$thread_id"]+="${turn_number}"$'\t'"${ref}"$'\t'"${ref_ts}"$'\n' | |
| latest_existing="${THREAD_LATEST_TS[$thread_id]:-0}" | |
| if ((ref_ts > latest_existing)); then | |
| THREAD_LATEST_TS["$thread_id"]="$ref_ts" | |
| fi | |
| valid_refs=$((valid_refs + 1)) | |
| else | |
| ignored_refs=$((ignored_refs + 1)) | |
| echo "Ignoring non-checkpoint-shaped ref under refs/t3/checkpoints: $ref" >&2 | |
| fi | |
| done | |
| threads_found="${#THREAD_ENTRIES[@]}" | |
| if ((threads_found == 0)); then | |
| echo "No T3 checkpoint refs found matching refs/t3/checkpoints/<thread-id>/turn/<integer>." | |
| echo "Repo: $REPO_DIR" | |
| echo "Threads found: 0" | |
| echo "Refs kept: 0" | |
| echo "Refs would delete: 0" | |
| exit 0 | |
| fi | |
| if ((APPLY)); then | |
| echo "Apply mode: $MODE" | |
| else | |
| echo "Report-only mode: $MODE" | |
| fi | |
| echo "Repo: $REPO_DIR" | |
| case "$MODE" in | |
| prune-chat-checkpoints) | |
| echo "Prune-chat-checkpoints cap: preserve turn 0 and keep up to $KEEP total checkpoint ref(s) per thread" | |
| ;; | |
| clear-chat-checkpoints) | |
| echo "Clear-chat-checkpoints cutoff: latest checkpoint older than $PRUNE_DAYS day(s)" | |
| echo "Inactive cutoff: $(format_epoch "$inactive_cutoff_epoch")" | |
| ;; | |
| esac | |
| THREAD_FILE="$(mktemp)" | |
| INSPECTION_FILE="$(mktemp)" | |
| trap 'rm -f "$THREAD_FILE" "$INSPECTION_FILE"' EXIT | |
| printf '%s\n' "${!THREAD_ENTRIES[@]}" | sort >"$THREAD_FILE" | |
| if ((INSPECT_T3CODE)); then | |
| echo | |
| echo "T3 Code state classification (authoritative safety check):" | |
| if inspect_t3code_state "$THREAD_FILE" "$INSPECTION_FILE"; then | |
| read -r t3code_existing_db_count t3code_all_existing_opened < <( | |
| python3 - "$INSPECTION_FILE" <<'PY' | |
| import json | |
| import sys | |
| payload = json.load(open(sys.argv[1])) | |
| print( | |
| payload.get("existingDbCount", 0), | |
| 1 if payload.get("allExistingDbPathsOpened", False) else 0, | |
| ) | |
| PY | |
| ) | |
| while IFS=$'\t' read -r thread_id classification details; do | |
| [[ -n "$thread_id" ]] || continue | |
| T3CODE_CLASSIFICATION["$thread_id"]="$classification" | |
| T3CODE_DETAILS["$thread_id"]="$details" | |
| done < <( | |
| python3 - "$INSPECTION_FILE" <<'PY' | |
| import json | |
| import sys | |
| from collections import Counter | |
| payload = json.load(open(sys.argv[1])) | |
| counts = Counter(record["classification"] for record in payload["records"]) | |
| for report in payload["dbReports"]: | |
| if report["status"] == "missing": | |
| continue | |
| line = f"DB {report['path']}: {report['status']}" | |
| if report.get("error"): | |
| line += f" ({report['error']})" | |
| print(line, file=sys.stderr) | |
| for key in ( | |
| "live-candidate", | |
| "archived-candidate", | |
| "runtime-bound-candidate", | |
| "lingering-state-candidate", | |
| "deleted-candidate", | |
| "orphaned-candidate", | |
| ): | |
| print(f"{key}: {counts.get(key, 0)}", file=sys.stderr) | |
| for record in payload["records"]: | |
| print(f"{record['threadId']}\t{record['classification']}\t{record['details']}") | |
| PY | |
| ) 2> >(sed 's/^/ /' >&2) | |
| for thread_id in "${!T3CODE_CLASSIFICATION[@]}"; do | |
| echo " - $thread_id: ${T3CODE_CLASSIFICATION[$thread_id]} (${T3CODE_DETAILS[$thread_id]})" | |
| done | |
| if ((APPLY)); then | |
| if ((t3code_existing_db_count == 0)); then | |
| die "No existing T3 Code state database could be inspected; refusing apply." | |
| fi | |
| if ((t3code_all_existing_opened == 0)); then | |
| die "Not every existing configured T3 Code database was inspected successfully; refusing apply." | |
| fi | |
| fi | |
| else | |
| if ((APPLY)); then | |
| die "T3 Code state inspection is unavailable; rerun with 'plan' or pass --no-t3code-inspect only if you intentionally want to bypass T3 Code safety guards." | |
| fi | |
| echo " T3 Code state inspection unavailable; continuing with ref-only report." | |
| fi | |
| else | |
| echo | |
| echo "T3 Code state classification skipped because --no-t3code-inspect was provided." | |
| if ((APPLY)); then | |
| echo "Warning: apply mode is running without T3 Code safety guards." >&2 | |
| fi | |
| fi | |
| if ((INSPECT_CODEX)); then | |
| echo | |
| echo "Codex-state candidate classification (advisory only):" | |
| if inspect_codex_state "$THREAD_FILE" "$INSPECTION_FILE"; then | |
| python3 - "$INSPECTION_FILE" <<'PY' | |
| import json | |
| import sys | |
| from collections import Counter | |
| payload = json.load(open(sys.argv[1])) | |
| print(f" Codex root: {payload['codexRoot']}") | |
| print(f" SQLite status: {payload['dbStatus']}") | |
| if payload.get("dbError"): | |
| print(f" SQLite error: {payload['dbError']}") | |
| print(f" Session metadata status: {payload['sessionStatus']}") | |
| counts = Counter(record["classification"] for record in payload["records"]) | |
| for key in ("active-candidate", "archived-candidate", "orphaned-candidate"): | |
| print(f" {key}: {counts.get(key, 0)}") | |
| for record in payload["records"]: | |
| print(f" - {record['threadId']}: {record['classification']} ({record['details']})") | |
| PY | |
| else | |
| echo " Codex-state inspection unavailable; continuing without advisory Codex metadata." | |
| fi | |
| else | |
| echo | |
| echo "Codex-state candidate classification skipped because --no-codex-inspect was provided." | |
| fi | |
| echo | |
| while IFS= read -r thread_id; do | |
| [[ -n "$thread_id" ]] || continue | |
| mapfile -t sorted_entries < <( | |
| printf '%s' "${THREAD_ENTRIES[$thread_id]}" | awk 'NF' | sort -t $'\t' -k1,1n | |
| ) | |
| count="${#sorted_entries[@]}" | |
| if ((count == 0)); then | |
| continue | |
| fi | |
| latest_ts="${THREAD_LATEST_TS[$thread_id]:-0}" | |
| latest_ts_human="$(format_epoch "$latest_ts")" | |
| echo "Thread $thread_id: $count checkpoint ref(s), latest checkpoint $latest_ts_human" | |
| local t3code_classification t3code_details | |
| t3code_classification="${T3CODE_CLASSIFICATION[$thread_id]:-unknown}" | |
| t3code_details="${T3CODE_DETAILS[$thread_id]:-no T3 Code classification available}" | |
| echo " T3 Code state: $t3code_classification ($t3code_details)" | |
| case "$t3code_classification" in | |
| live-candidate|archived-candidate|runtime-bound-candidate|lingering-state-candidate) | |
| protected_threads=$((protected_threads + 1)) | |
| protected_refs=$((protected_refs + count)) | |
| kept_refs=$((kept_refs + count)) | |
| echo " keeping all $count ref(s); protected by T3 Code state" | |
| continue | |
| ;; | |
| esac | |
| case "$MODE" in | |
| clear-chat-checkpoints) | |
| if ((latest_ts > 0)) && ((latest_ts < inactive_cutoff_epoch)); then | |
| inactive_threads_matched=$((inactive_threads_matched + 1)) | |
| inactive_refs_matched=$((inactive_refs_matched + count)) | |
| if ((APPLY)); then | |
| echo " deleting all $count ref(s); thread is inactive past the cutoff" | |
| else | |
| echo " would delete all $count ref(s); thread is inactive past the cutoff" | |
| fi | |
| for entry in "${sorted_entries[@]}"; do | |
| turn_number="${entry%%$'\t'*}" | |
| rest="${entry#*$'\t'}" | |
| ref="${rest%%$'\t'*}" | |
| if ((APPLY)); then | |
| echo " deleting turn $turn_number: $ref" | |
| git -C "$REPO_DIR" update-ref -d "$ref" | |
| else | |
| echo " would delete turn $turn_number: $ref" | |
| fi | |
| deleted_refs=$((deleted_refs + 1)) | |
| done | |
| continue | |
| fi | |
| kept_refs=$((kept_refs + count)) | |
| echo " keeping all $count ref(s)" | |
| ;; | |
| prune-chat-checkpoints) | |
| mapfile -t deletable_entries < <( | |
| printf '%s\n' "${sorted_entries[@]}" | awk -F $'\t' '$1 != "0"' | |
| ) | |
| local deletable_count refs_to_delete_effective | |
| deletable_count="${#deletable_entries[@]}" | |
| if ((count <= KEEP)) || ((deletable_count == 0)); then | |
| kept_refs=$((kept_refs + count)) | |
| echo " keeping all $count ref(s)" | |
| continue | |
| fi | |
| refs_to_delete=$((count - KEEP)) | |
| refs_to_delete_effective=$refs_to_delete | |
| if ((refs_to_delete_effective > deletable_count)); then | |
| refs_to_delete_effective=$deletable_count | |
| fi | |
| kept_refs=$((count - refs_to_delete_effective)) | |
| if ((APPLY)); then | |
| echo " deleting $refs_to_delete_effective older ref(s); keeping turn 0 and the newest remaining refs" | |
| else | |
| echo " would delete $refs_to_delete_effective older ref(s); keeping turn 0 and the newest remaining refs" | |
| fi | |
| for ((i = 0; i < refs_to_delete_effective; i++)); do | |
| entry="${deletable_entries[$i]}" | |
| turn_number="${entry%%$'\t'*}" | |
| rest="${entry#*$'\t'}" | |
| ref="${rest%%$'\t'*}" | |
| if ((APPLY)); then | |
| echo " deleting turn $turn_number: $ref" | |
| git -C "$REPO_DIR" update-ref -d "$ref" | |
| else | |
| echo " would delete turn $turn_number: $ref" | |
| fi | |
| deleted_refs=$((deleted_refs + 1)) | |
| done | |
| ;; | |
| esac | |
| done <"$THREAD_FILE" | |
| echo | |
| echo "Summary:" | |
| echo " Threads found: $threads_found" | |
| echo " Valid checkpoint refs: $valid_refs" | |
| echo " Refs kept: $kept_refs" | |
| if ((APPLY)); then | |
| echo " Refs deleted: $deleted_refs" | |
| else | |
| echo " Refs would delete: $deleted_refs" | |
| fi | |
| if ((ignored_refs > 0)); then | |
| echo " Ignored non-matching refs: $ignored_refs" | |
| fi | |
| echo " Protected threads: $protected_threads" | |
| echo " Protected refs kept due to T3 Code state: $protected_refs" | |
| if [[ "$MODE" == "clear-chat-checkpoints" ]]; then | |
| echo " Inactive threads matched: $inactive_threads_matched" | |
| echo " Refs deleted via inactive-thread mode: $inactive_refs_matched" | |
| echo " Threads remaining after inactive prune: $((threads_found - inactive_threads_matched))" | |
| fi | |
| if ((APPLY == 0)); then | |
| echo "Report-only mode: skipping ref deletion, reflog expiry, and git gc." | |
| exit 0 | |
| fi | |
| if ((deleted_refs == 0)); then | |
| echo "No refs deleted; skipping reflog expiry and git gc." | |
| exit 0 | |
| fi | |
| if ((RUN_GC == 0)); then | |
| echo "Skipping reflog expiry and git gc because --no-gc was provided." | |
| exit 0 | |
| fi | |
| echo "Expiring reflogs and pruning unreachable objects..." | |
| git -C "$REPO_DIR" reflog expire --expire=now --all | |
| git -C "$REPO_DIR" gc --prune=now | |
| echo "Finished pruning T3 checkpoint refs." | |
| } | |
| parse_command_and_options "$@" | |
| run_main |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| _t3_checkpoints() { | |
| local cur prev | |
| COMPREPLY=() | |
| cur="${COMP_WORDS[COMP_CWORD]}" | |
| prev="${COMP_WORDS[COMP_CWORD-1]}" | |
| if (( COMP_CWORD == 1 )); then | |
| COMPREPLY=($(compgen -W "plan apply help" -- "$cur")) | |
| return 0 | |
| fi | |
| case "$prev" in | |
| plan|apply) | |
| COMPREPLY=($(compgen -W "prune-chat-checkpoints clear-chat-checkpoints" -- "$cur")) | |
| return 0 | |
| ;; | |
| prune-chat-checkpoints) | |
| COMPREPLY=($(compgen -W "1 3 5 10 20 --repo --no-gc --codex-root --no-codex-inspect --help" -- "$cur")) | |
| return 0 | |
| ;; | |
| clear-chat-checkpoints) | |
| COMPREPLY=($(compgen -W "1 3 5 7 14 30 --repo --no-gc --codex-root --no-codex-inspect --help" -- "$cur")) | |
| return 0 | |
| ;; | |
| --repo|--codex-root) | |
| compopt -o filenames 2>/dev/null | |
| COMPREPLY=($(compgen -d -- "$cur")) | |
| return 0 | |
| ;; | |
| esac | |
| COMPREPLY=($(compgen -W "--repo --no-gc --codex-root --no-codex-inspect --help" -- "$cur")) | |
| } | |
| complete -F _t3_checkpoints t3-checkpoints t3cp |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| exec /home/ben/bin/t3-checkpoints "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment