
    i?{                     
   U d Z ddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZmZmZmZ  ej        e          Z e
            dz  Zg dZ ed ed	 e ej        d
d                                        Zeed<   dZ ej        d          Zdedee         fdZdededee         fdZ dedefdZ!dedefdZ"dedede#fdZ$edfdee         dedededeee                  de%fdZ&dededee         fdZ'dedefd Z( G d! d"          Z)dee         d#edefd$Z*d%Z+dedee         fd&Z,dede-fd'Z.	 	 	 d1d*ed+e/d,ee         deeef         fd-Z0	 	 	 	 d2d*ed/ed+e/d,ee         deee1f         f
d0Z2dS )3uL  
Checkpoint Manager — Transparent filesystem snapshots via shadow git repos.

Creates automatic snapshots of working directories before file-mutating
operations (write_file, patch), triggered once per conversation turn.
Provides rollback to any previous checkpoint.

This is NOT a tool — the LLM never sees it.  It's transparent infrastructure
controlled by the ``checkpoints`` config flag or ``--checkpoints`` CLI flag.

Architecture:
    ~/.hermes/checkpoints/{sha256(abs_dir)[:16]}/   — shadow git repo
        HEAD, refs/, objects/                        — standard git internals
        HERMES_WORKDIR                               — original dir path
        info/exclude                                 — default excludes

The shadow repo uses GIT_DIR + GIT_WORK_TREE so no git state leaks
into the user's project directory.
    N)Path)get_hermes_home)DictListOptionalSetcheckpoints)znode_modules/zdist/zbuild/z.envz.env.*z
.env.localz.env.*.localz__pycache__/z*.pycz*.pyoz	.DS_Storez*.logz.cache/z.next/z.nuxt/z	coverage/z.pytest_cache/z.venv/zvenv/z.git/
   <   HERMES_CHECKPOINT_TIMEOUT30_GIT_TIMEOUTiP  z^[0-9a-fA-F]{4,64}$commit_hashreturnc                     | r|                                  sdS |                     d          rd| S t                              |           sd| S dS )zValidate a commit hash to prevent git argument injection.

    Returns an error string if invalid, None if valid.
    Values starting with '-' would be interpreted as git flags
    (e.g., '--patch', '-p') instead of revision specifiers.
    zEmpty commit hash-z/Invalid commit hash (must not start with '-'): z4Invalid commit hash (expected 4-64 hex characters): N)strip
startswith_COMMIT_HASH_REmatch)r   s    =/home/ubuntu/.hermes/hermes-agent/tools/checkpoint_manager.py_validate_commit_hashr   L   st      #k//11 #""c"" QPPPP  -- VUkUUU4    	file_pathworking_dirc                 "   | r|                                  sdS t          j                            |           rd| S t	          |          }|| z                                  }	 |                    |           n# t          $ r d| cY S w xY wdS )zValidate a file path to prevent path traversal outside the working directory.

    Returns an error string if invalid, None if valid.
    zEmpty file pathz/File path must be relative, got absolute path: z7File path escapes the working directory via traversal: N)r   ospathisabs_normalize_pathresolverelative_to
ValueError)r   r   abs_workdirresolveds       r   _validate_file_pathr&   \   s    
  !IOO-- !  	w}}Y ONNNN!+..Ki'0022HW[)))) W W WVVVVVVW4s   $A: :BB
path_valuec                 h    t          |                                                                           S )z;Return a canonical absolute path for checkpoint operations.)r   
expanduserr!   )r'   s    r   r    r    t   s(    
&&((00222r   c                     t          t          |                     }t          j        |                                                                          dd         }t          |z  S )z6Deterministic shadow repo path: sha256(abs_path)[:16].N   )strr    hashlibsha256encode	hexdigestCHECKPOINT_BASE)r   abs_pathdir_hashs      r   _shadow_repo_pathr4   y   sP    ?;//00H~hoo//00::<<SbSAHX%%r   shadow_repoc                 r   t          |          }t          j                                        }t	          |           |d<   t	          |          |d<   |                    dd           |                    dd           |                    dd           t          j        |d<   t          j        |d<   d	|d
<   |S )u=  Build env dict that redirects git to the shadow repo.

    The shadow repo is internal Hermes infrastructure — it must NOT inherit
    the user's global or system git config.  User-level settings like
    ``commit.gpgsign = true``, signing hooks, or credential helpers would
    either break background snapshots or, worse, spawn interactive prompts
    (pinentry GUI windows) mid-session every time a file is written.

    Isolation strategy:
    * ``GIT_CONFIG_GLOBAL=<os.devnull>`` — ignore ``~/.gitconfig`` (git 2.32+).
    * ``GIT_CONFIG_SYSTEM=<os.devnull>`` — ignore ``/etc/gitconfig`` (git 2.32+).
    * ``GIT_CONFIG_NOSYSTEM=1`` — legacy belt-and-suspenders for older git.

    The shadow repo still has its own per-repo config (user.email, user.name,
    commit.gpgsign=false) set in ``_init_shadow_repo``.
    GIT_DIRGIT_WORK_TREEGIT_INDEX_FILENGIT_NAMESPACE GIT_ALTERNATE_OBJECT_DIRECTORIESGIT_CONFIG_GLOBALGIT_CONFIG_SYSTEM1GIT_CONFIG_NOSYSTEM)r    r   environcopyr,   popdevnull)r5   r   normalized_working_direnvs       r   _git_envrF      s    " -[99
*//

C%%C	N566CGGd###GGOT"""GG.555
  "zC!zC!$CJr   argstimeoutallowed_returncodesc           
         t          |          }|                                sJd| }t                              dd                    dgt          |           z             |           dd|fS |                                sJd| }t                              dd                    dgt          |           z             |           dd|fS t          |t          |                    }dgt          |           z   }|pt                      }	 t          j        |dd||t          |          	          }	|	j        d
k    }
|	j                                        }|	j                                        }|
s>|	j        |vr5t                              dd                    |          |	j        |           |
||fS # t          j        $ r? d| dd                    |           }t                              |d           dd|fcY S t"          $ r}t%          |dd          }|dk    r6t                              dd                    |          d           Y d}~dS d| }t                              dd                    |          |d           dd|fcY d}~S d}~wt&          $ rM}t                              dd                    |          |d           ddt          |          fcY d}~S d}~ww xY w)a4  Run a git command against the shadow repo.  Returns (ok, stdout, stderr).

    ``allowed_returncodes`` suppresses error logging for known/expected non-zero
    exits while preserving the normal ``ok = (returncode == 0)`` contract.
    Example: ``git diff --cached --quiet`` returns 1 when changes exist.
    zworking directory not found: zGit command skipped: %s (%s) gitF z&working directory is not a directory: T)capture_outputtextrH   rE   cwdr   z(Git command failed: %s (rc=%d) stderr=%szgit timed out after zs: )exc_infofilenameNzGit executable not found: %s)FrM   zgit not foundz,Git command failed before execution: %s (%s)z#Unexpected git error running %s: %s)r    existsloggererrorjoinlistis_dirrF   r,   set
subprocessrun
returncodestdoutr   stderrTimeoutExpiredFileNotFoundErrorgetattr	Exception)rG   r5   r   rH   rI   rD   msgrE   cmdresultokr]   r^   excmissing_targets                  r   _run_gitri      sA    -[99!((** F.DFF3SXXugT

>R5S5SUXYYYb#~!((** O7MOO3SXXugT

>R5S5SUXYYYb#~
;$: ; ;
<
<C'DJJ
C-6 #*++
 
 
 !#$$&&$$&& 	f'/BBBLL:v0&   66!!$   @W@@#@@S4(((b#~    j$77U""LL7#QULVVV------F.DFFCSXXc]]TWbfgggb#~ # # #:CHHSMM3Y]^^^b#c(("""""""#s@   B(F6 6AK3	K3AJ:JK3K3&AK.(K3.K3c                 ~   | dz                                   rdS |                     dd           t          dg| |          \  }}}|sd| S t          g d| |           t          g d| |           t          g d	| |           t          g d
| |           | dz  }|                    d           |dz                      d                    t
                    dz   d           | dz                      t          t          |                    dz   d           t          	                    d| |           dS )z@Initialise shadow repo if needed.  Returns error string or None.HEADNT)parentsexist_okinitzShadow repo init failed: )configz
user.emailzhermes@local)ro   z	user.namezHermes Checkpoint)ro   zcommit.gpgsignfalse)ro   ztag.gpgSignrp   info)rm   exclude
utf-8encodingHERMES_WORKDIRz(Initialised checkpoint repo at %s for %s)
rS   mkdirri   
write_textrV   DEFAULT_EXCLUDESr,   r    rT   debug)r5   r   rf   _errinfo_dirs         r   _init_shadow_repor      s   f$$&& tdT2226(K==JB3 103000555{KPPP999;TTT 222KMMM///kJJJV#HNNDN!!!	%%		"##d*W &    ##//OK(())D07 0    LL;[+VVV4r   r   c                     d}	 t          |                               d          D ]}|dz  }|t          k    r|c S n# t          t          f$ r Y nw xY w|S )z;Quick file count estimate (stops early if over _MAX_FILES).r   *   )r   rglob
_MAX_FILESPermissionErrorOSError)r   countr|   s      r   _dir_file_countr     s    Ed!!#&& 	 	AQJEz!! "	 W%   Ls   7> > AAc            	           e Zd ZdZddedefdZdd	ZddededefdZ	dede
e         fdZedededdfd            ZdededefdZddedededefdZdedefdZdededefdZdededdfdZdS )CheckpointManagera  Manages automatic filesystem checkpoints.

    Designed to be owned by AIAgent.  Call ``new_turn()`` at the start of
    each conversation turn and ``ensure_checkpoint(dir, reason)`` before
    any file-mutating tool call.  The manager deduplicates so at most one
    snapshot is taken per directory per turn.

    Parameters
    ----------
    enabled : bool
        Master switch (from config / CLI flag).
    max_snapshots : int
        Keep at most this many checkpoints per directory.
    F2   enabledmax_snapshotsc                 V    || _         || _        t                      | _        d | _        d S N)r   r   rY   _checkpointed_dirs_git_available)selfr   r   s      r   __init__zCheckpointManager.__init__#  s+    *,/EE.2r   r   Nc                 8    | j                                          dS )zAReset per-turn dedup.  Call at the start of each agent iteration.N)r   clear)r   s    r   new_turnzCheckpointManager.new_turn-  s    %%'''''r   autor   reasonc                 D   | j         sdS | j        <t          j        d          du| _        | j        st                              d           | j        sdS t          t          |                    }|dt          t          j	                              fv rt                              d|           dS || j
        v rdS | j
                            |           	 |                     ||          S # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)u   Take a checkpoint if enabled and not already done this turn.

        Returns True if a checkpoint was taken, False otherwise.
        Never raises — all errors are silently logged.
        FNrL   z#Checkpoints disabled: git not found/z,Checkpoint skipped: directory too broad (%s)z!Checkpoint failed (non-fatal): %s)r   r   shutilwhichrT   r{   r,   r    r   homer   add_takerb   )r   r   r   abs_dires        r   ensure_checkpointz#CheckpointManager.ensure_checkpoint5  s9    | 	5 &"(,u"5"5T"AD& DBCCC" 	5ok2233 sC	,,---LLGQQQ5 d---5##G,,,	::gv... 	 	 	LL<a@@@55555	s   C/ /
D9DDc           	      n   t          t          |                    }t          |          }|dz                                  sg S t	          dddt          | j                  g||          \  }}}|r|sg S g }|                                D ]}|                    dd          }	t          |	          dk    r}|	d         |	d	         |	d
         |	d         dddd}
t	          dd|	d          d|	d         g||ddh          \  }}}|r|r| 	                    ||
           |
                    |
           |S )zList available checkpoints for a directory.

        Returns a list of dicts with keys: hash, short_hash, timestamp, reason,
        files_changed, insertions, deletions.  Most recent first.
        rk   logz--format=%H|%h|%aI|%sz-n|      r   r      )hash
short_hash	timestampr   files_changed
insertions	deletionsdiffz--shortstatz~1      rI   )r,   r    r4   rS   ri   r   
splitlinessplitlen_parse_shortstatappend)r   r   r   shadowrf   r]   r|   resultslinepartsentrystat_okstat_outs                r   list_checkpointsz"CheckpointManager.list_checkpointsY  s    ok2233"7++'')) 	I +T3t7I3J3JKG
 
FA
  	 	I%%'' 	& 	&DJJsA&&E5zzQ!!H"'(!&q#Ah%&"#!"  (0]uQxOOOU1XFG),c
( ( ($1
  ;x ;))(E:::u%%%r   	stat_liner   c                 r   t          j        d|           }|r%t          |                    d                    |d<   t          j        d|           }|r%t          |                    d                    |d<   t          j        d|           }|r't          |                    d                    |d<   dS dS )	z-Parse git --shortstat output into entry dict.z
(\d+) filer   r   z(\d+) insertionr   z(\d+) deletionr   N)researchintgroup)r   r   ms      r   r   z"CheckpointManager._parse_shortstat  s     ImY// 	5%(__E/"I()44 	2"%aggajj//E,I'33 	1!$QWWQZZE+	1 	1r   r   c                    t          |          }|rd|dS t          t          |                    }t          |          }|dz                                  sdddS t          dd|g||          \  }}}|s	dd| ddS t          d	d
g||t          dz             t          dd|dg||          \  }	}
}t          d|ddg||          \  }}}t          g d||           |	s|sdddS d|	r|
nd|r|nddS )zShow diff between a checkpoint and the current working tree.

        Returns dict with success, diff text, and stat summary.
        FsuccessrU   rk   'No checkpoints exist for this directorycat-file-tCheckpoint '' not foundr   -Ar   rH   r   z--stat--cachedz
--no-color)resetrk   --quietzCould not generate diffTrM   )r   statr   )r   r,   r    r4   rS   ri   r   )r   r   r   hash_errr   r   rf   r|   r}   ok_statr   ok_diffdiff_outs                r   r   zCheckpointManager.diff  s    )55 	9$x888ok2233"7++'')) 	Z$/XYYY {+VW
 

As  	X$/Vk/V/V/VWWW 	%9IJJJJ  (X{J7G 
  
1  ([*l;G 
  
1 	---vw??? 	Jw 	J$/HIII  '/HHR '/HHR
 
 	
r   r   c                 X   t          |          }|rd|dS t          t          |                    }|rt          ||          }|rd|dS t	          |          }|dz                                  sdddS t          dd|g||          \  }}	}
|sdd| d|
pd	d
S |                     |d|d	d          d           |r|nd}t          d|d|g||t          dz            \  }}}
|sdd|
 |
pd	d
S t          ddd|g||          \  }}}	|r|nd}d|d	d         ||d}|r||d<   |S )u  Restore files to a checkpoint state.

        Uses ``git checkout <hash> -- .`` (or a specific file) which restores
        tracked files without moving HEAD — safe and reversible.

        Parameters
        ----------
        file_path : str, optional
            If provided, restore only this file instead of the entire directory.

        Returns dict with success/error info.
        Fr   rk   r   r   r   r   r   N)r   rU   r{   z$pre-rollback snapshot (restoring to    ).checkoutz--r   r   zRestore failed: r   z--format=%sz-1unknownT)r   restored_tor   	directoryfile)	r   r,   r    r&   r4   rS   ri   r   r   )r   r   r   r   r   r   path_errr   rf   r|   r}   restore_targetr]   ok2
reason_outr   re   s                    r   restorezCheckpointManager.restore  s    )55 	9$x888ok2233  	=*9g>>H =#(8<<<"7++'')) 	Z$/XYYY {+VW
 

As  	n$/Vk/V/V/Vadalhlmmm 	

7U;rPQr?UUUVVV '08S"dN;G\A%5
 
 
FC
  	_$/G#/G/GRUR]Y]^^^ &M45vw
 
Z  #1	 &rr? 	
 
  	'&F6Nr   c                    t          |          }|                                r|}n|j        }h d}|j        k    r<t          fd|D                       rt	                    S j        j        k    <t	          |          S )a  Resolve a file path to its working directory for checkpointing.

        Walks up from the file's parent to find a reasonable project root
        (directory containing .git, pyproject.toml, package.json, etc.).
        Falls back to the file's parent directory.
        >	   .hg.gitgo.modpom.xml
Cargo.tomlpackage.jsonpyproject.tomlGemfileMakefilec              3   F   K   | ]}|z                                   V  d S r   )rS   ).0r   checks     r   	<genexpr>z=CheckpointManager.get_working_dir_for_path.<locals>.<genexpr>  s3      99AEAI%%''999999r   )r    rX   parentanyr,   )r   r   r   	candidatemarkersr   s        @r   get_working_dir_for_pathz*CheckpointManager.get_working_dir_for_path  s     y));;== 	$IIIG G Gu|##999999999 "5zz!LE u|## 9~~r   c                    t          |          }t          ||          }|rt                              d|           dS t	          |          t
          k    r#t                              dt
          |           dS t          ddg||t          dz            \  }}}|st                              d|           dS t          g d	||d
h          \  }}}|rt                              d|           dS t          dd|ddg||t          dz            \  }}}|st                              d|           dS t                              d||           |                     ||           dS )z*Take a snapshot.  Returns True on success.zCheckpoint init failed: %sFz#Checkpoint skipped: >%d files in %sr   r   r   r   zCheckpoint git-add failed: %s)r   r   r   r   r   z$Checkpoint skipped: no changes in %scommitz-mz--allow-empty-messagez--no-gpg-signzCheckpoint commit failed: %szCheckpoint taken in %s: %sT)	r4   r   rT   r{   r   r   ri   r   _prune)	r   r   r   r   r}   rf   r|   r   r   s	            r   r   zCheckpointManager._take$  s   ";//  44 	LL5s;;;5 ;''*44LL>
KXXX5 DM6;q8H
 
 

As  	LL8#>>>5  (+++!"	 
  
  
1  	LL?MMM5
 tV%<oNK)9
 
 

As  	LL7===51;GGG 	FK(((tr   r5   c                     t          g d||          \  }}}|sdS 	 t          |          }n# t          $ r Y dS w xY w|| j        k    rdS t                              d|| j                   dS )z:Keep only the last max_snapshots commits via orphan reset.)zrev-listz--countrk   Nz)Checkpoint repo has %d commits (limit %d))ri   r   r#   r   rT   r{   )r   r5   r   rf   r]   r|   r   s          r   r   zCheckpointManager._pruneY  s     +++[+
 
FA  	F	KKEE 	 	 	FF	 D&&&F 	@%I[\\\\\s   - 
;;)Fr   )r   N)r   r   )__name__
__module____qualname____doc__boolr   r   r   r,   r   r   r   r   staticmethodr   r   r   r   r   r   r    r   r   r   r     s        3 3 3S 3 3 3 3( ( ( (" "S "# "4 " " " "H*C *DJ * * * *X 
1C 
1 
1 
1 
1 
1 \
10
 0
# 0
$ 0
 0
 0
 0
dA A3 AS AS ATX A A A AF# #    :3 3c 3d 3 3 3 3j]$ ]S ]T ] ] ] ] ] ]r   r   r   c                 "   | sd| S d| dg}t          | d          D ]\  }}|d         }d|v r}|                    d          d                             d          d                             d	          d         d
d         }|d                             d          d         }| d| }|                    dd          }|                    dd          }|                    dd          }	|rd| d|dk    rdnd d| d|	 d	}
nd}
|                    d| d|d          d| d|d          |
 	           |                    d           |                    d           |                    d           d                    |          S )z+Format checkpoint list for display to user.zNo checkpoints found for u   📸 Checkpoints for z:
r   r   T+r   r   N   rK   r   r   r   z  (z filesrM   z, +z/-r   z  z. r   r   z4
  /rollback <N>             restore to checkpoint Nz>  /rollback diff <N>        preview changes since checkpoint NzC  /rollback <N> <file>      restore a single file from checkpoint Nrs   )	enumerater   getr   rV   )r	   r   linesicptsdatefilesinsdeler   s              r   format_checkpoint_listr  q  s    7696663Y3334E;** O O2_"99#q!'',,Q/55c::1=bqbABk?((--a0D2B **ff\1%%vvk1%% 	QQQEQJJSSBQQ3QQ$QQQDDDM!MMr,/MM2MMHMtMMNNNN	LLHIII	LLQRRR	LLVWWW99Ur   z.last_prunec                     	 | dz                       d                                          S # t          t          f$ r Y dS w xY w)zJRead ``HERMES_WORKDIR`` from a shadow repo, or None if missing/unreadable.rw   rt   ru   N)	read_textr   r   UnicodeDecodeError)r5   s    r   _read_workdir_markerr    sX    ..9979KKQQSSS'(   tts   *- AAc                     d}	 |                      d          D ]4}	 |                                j        }||k    r|}%# t          $ r Y 1w xY wn# t          $ r Y nw xY w|S )u  Return newest mtime across the shadow repo (walks objects/refs/HEAD).

    We walk instead of trusting the directory mtime because git's pack
    operations can leave the top-level dir untouched while refs/objects
    inside get updated.  Best-effort — returns 0.0 on any error.
            r   )r   r   st_mtimer   )r5   newestpr   s       r   _shadow_repo_newest_mtimer    s     F	""3'' 	 	AFFHH%v::F   	    Ms1   A !>A 
AA 
AA 
AA   Tretention_daysdelete_orphanscheckpoint_basec                    |pt           }dddddd}|                                s|S d}| dk    rddl}|                                | dz  z
  }|                                D ]}|                                s|dz                                  s0|dxx         dz  cc<   d}|r4t          |          }	|	!t          |	                                          sd	}|#| dk    rt          |          }
|
dk    r|
|k     rd
}|	 t          d |	                    d          D                       }n# t          $ r d}Y nw xY w	 t          j        |           |dxx         |z  cc<   |d	k    r|dxx         dz  cc<   n|dxx         dz  cc<   t                              d||j        |           P# t          $ r<}|dxx         dz  cc<   t                              d|j        |           Y d}~d}~ww xY w|S )u  Delete stale/orphan shadow repos under ``checkpoint_base``.

    A shadow repo is deleted when either:

    * ``delete_orphans=True`` and its ``HERMES_WORKDIR`` path no longer
      exists on disk (the original project was deleted / moved); OR
    * its newest in-repo mtime is older than ``retention_days`` days.

    Returns a dict with counts ``{"scanned", "deleted_orphan",
    "deleted_stale", "errors", "bytes_freed"}``.

    Never raises — maintenance must never block interactive startup.
    r   scanneddeleted_orphandeleted_staleerrorsbytes_freedr  NiQ rk   r%  r   orphanstalec              3   p   K   | ]1}|                                 |                                j        V  2d S r   )is_filer   st_size)r   r  s     r   r   z$prune_checkpoints.<locals>.<genexpr>  s:      QQAQYY[[Qqvvxx'QQQQQQr   r   r)  r&  r'  z(Pruned %s checkpoint repo: %s (%d bytes)r(  z&Failed to prune checkpoint repo %s: %s)r1   rS   timeiterdirrX   r  r   r  sumr   r   r   rmtreerT   r{   namewarning)r   r!  r"  basere   cutoff_timechildr   workdirr  sizerg   s                r   prune_checkpointsr;    s   $ -oD F ;;== F 66 &V &V||~~ 	 &&(( 	yQ $ 	"*511Gd7mm&:&:&<&<!>nq00.u55Fzzfvoo >	QQS1A1AQQQQQDD 	 	 	DDD	
	VM%   =!!!T)!!!!!'(((A-(((('''1,'''LLCVUZY]^^^^ 	V 	V 	V8!NNCUZQTUUUUUUUU	V Ms+   ,D00D?>D?A-F22
G8<1G33G8   min_interval_hoursc                    ddl }|pt          }ddi}	 |                                sdddddd|d<   |S |t          z  }|                                 }|                                ra	 t	          |                    d                                                    }	||	z
  |d	z  k     rd
|d<   |S n# t          t          f$ r Y nw xY wt          | ||          }
|
|d<   	 |
                    t          |          d           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY w|
d         |
d         z   }|dk    r3t                              d||
d         |
d         |
d         dz             nD# t          $ r7}t                              d|           t          |          |d<   Y d}~nd}~ww xY w|S )a  Idempotent wrapper around ``prune_checkpoints`` for startup hooks.

    Writes ``CHECKPOINT_BASE/.last_prune`` on completion so subsequent
    calls within ``min_interval_hours`` short-circuit.  Designed to be
    called once per CLI/gateway process startup; the marker keeps costs
    bounded regardless of how many times hermes is invoked per day.

    Returns ``{"skipped": bool, "result": prune_checkpoints-dict,
    "error": optional str}``.
    r   NskippedFr$  re   rt   ru   i  T)r   r!  r"  z+Could not write checkpoint prune marker: %sr&  r'  zWcheckpoint auto-maintenance: pruned %d repo(s) (%d orphan, %d stale), reclaimed %.1f MBr)  i   z&checkpoint auto-maintenance failed: %srU   )r/  r1   rS   _PRUNE_MARKER_NAMEfloatr  r   r   r#   r;  ry   r,   rT   r{   rq   rb   r4  )r   r=  r!  r"  r7  r5  outmarkernowlast_tsre   rg   totals                r   maybe_auto_prune_checkpointsrG    sN     -oD'/C+ {{}} 	AA CM J**jjll==?? 	 0 0' 0 B B H H J JKK=#5#<<<%)C	NJ = Z(    #)) 
 
 

 H	Mc#hh9999 	M 	M 	MLLFLLLLLLLL	M '(6/+BB199KK;'('}%5         ?EEE3xxG  Jsl    F 2F 'AB0 /F 0CF CF $D F 
D3D.)F .D33AF 
G-F==G)r  TN)r  r<  TN)3r  r-   loggingr   r   r   rZ   pathlibr   hermes_constantsr   typingr   r   r   r   	getLoggerr   rT   r1   rz   maxminr   getenvr   __annotations__r   compiler   r,   r   r&   r    r4   dictrF   tupleri   r   r   r   r  r@  r  rA  r  r  r;  objectrG  r  r   r   <module>rU     s2    (   				 				            , , , , , , , , , , , , , , , , , ,		8	$	$ "/##m3   0 CCCCC		2Mt(T(T$U$UVVWWc W W W 
 "*344s x}     3 S Xc]    03 3 3 3 3 3
&3 &4 & & & &$ S T    L  .2:# :#
s):#:# :# 	:#
 "#c(+:# :# :# :# :#z 4  c  hsm        F
# 
# 
 
 
 
"[] [] [] [] [] [] [] []|
T
 s s    l # d x}    4 E    , &*J JJJ d^J 
#s(^	J J J J\  &*	A AAA A d^	A
 
#v+A A A A A Ar   