§
    ‰eî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S )%uL  
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 )zñValidate 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/div/.hermes/hermes-agent/tools/checkpoint_manager.pyÚ_validate_commit_hashr   L   st   € ð ð #˜k×/Ò/Ñ1Ô1ð #Ø"Ð"Ø×Ò˜cÑ"Ô"ð QØPÀÐPÐPÐPÝ× Ò  Ñ-Ô-ð VØUÀkÐ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 )zŠValidate 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À   € ð
 ð !˜IŸOšOÑ-Ô-ð !Ø Ð å	„w‡}‚}�YÑÔð OØNÀÐNÐNÐNå! +Ñ.Ô.€KØ˜iÑ'×0Ò0Ñ2Ô2€Hð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Ô0€HÝŒ~˜hŸošoÑ/Ô/Ñ0Ô0×:Ò:Ñ<Ô<¸S¸b¸SÔA€HÝ˜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Ô6€CˆÑØ‡G‚GÐ˜dÑ#Ô#Ð#Ø‡G‚GˆO˜TÑ"Ô"Ð"Ø‡G‚GÐ.°Ñ5Ô5Ð5õ
  "œz€CÐÑÝ!œz€CÐÑØ!$€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ˆÝ�ŠÐ3°S·X²X¸u¸gÍÈTÉ
Ì
Ñ>RÑ5SÔ5SÐUXÑYÔYÐYØ�b˜#ˆ~ÐØ!×(Ò(Ñ*Ô*ð ØOÐ7MÐOÐOˆÝ�ŠÐ3°S·X²X¸u¸gÍÈTÉ
Ì
Ñ>RÑ5SÔ5SÐUXÑYÔYÐYØ�b˜#ˆ~Ðå
�;¥Ð$:Ñ ;Ô ;Ñ
<Ô
<€CØˆ'•D˜‘J”JÑ
€CØ-Ð6µ±´Ðð #Ý”ØØØØØÝÐ*Ñ+Ô+ð
ñ 
ô 
ˆð Ô !Ò#ˆØ”×$Ò$Ñ&Ô&ˆØ”×$Ò$Ñ&Ô&ˆØð 	�fÔ'Ð/BÐBÐBÝ�LŠLØ:Ø—’˜‘”˜vÔ0°&ñô ð ð �6˜6Ð!Ð!øÝÔ$ð ð ð Ø@ WÐ@Ð@°·²¸#±´Ð@Ð@ˆÝ�Š�S 4ˆÑ(Ô(Ð(Ø�b˜#ˆ~ÐÐÐÝð ð ð Ý   j°$Ñ7Ô7ˆØ˜UÒ"Ð"Ý�LŠLÐ7¸¿ºÀ#¹¼ÐQUˆLÑVÔVÐVØ-Ð-Ð-Ð-Ð-Ð-ØFÐ.DÐFÐFˆÝ�ŠÐCÀSÇXÂXÈcÁ]Ä]ÐTWÐbfˆÑgÔgÐgØ�b˜#ˆ~ÐÐÐÐÐÐøøøøÝð #ð #ð #Ý�ŠÐ:¸C¿HºHÀS¹M¼MÈ3ÐY]ˆÑ^Ô^Ð^Ø�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Ú
z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à×Ò˜d¨TÐÑ2Ô2Ð2å˜6˜( K°Ñ=Ô=�J€Bˆˆ3Øð 1Ø0¨3Ð0Ð0Ð0åÐ5Ð5Ð5°{ÀKÑPÔPÐPÝÐ9Ð9Ð9¸;ÈÑTÔTÐTõ Ð2Ð2Ð2°KÀÑMÔMÐMÝÐ/Ð/Ð/°¸kÑJÔJÐJà˜VÑ#€HØ‡N‚N˜D€NÑ!Ô!Ð!Ø�	Ñ×%Ò%Ø�	Š	Õ"Ñ#Ô# dÑ*°Wð &ñ ô ð ð Ð#Ñ#×/Ò/Ý�O˜KÑ(Ô(Ñ)Ô)¨DÑ0¸7ð 0ñ ô ð õ ‡L‚LÐ;¸[È+Ñ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   Úcountrz   s      r   Ú_dir_file_countr†     s‚   € à€EðÝ�d‘”×!Ò! #Ñ&Ô&ð 	ð 	ˆAØ�Q‰JˆEØ•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.
    Fé2   Ú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+   € ØˆŒØ*ˆÔÝ,/©E¬EˆÔØ.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   ry   r,   r    r   ÚhomerŽ   ÚaddÚ_takerb   )r�   r   r–   Úabs_dirÚes        r   Úensure_checkpointz#CheckpointManager.ensure_checkpoint5  s9  € ð Œ|ð 	Ø�5ð ÔÐ&Ý"(¤,¨uÑ"5Ô"5¸TÐ"AˆDÔØÔ&ð DÝ—’ÐBÑCÔCÐCØÔ"ð 	Ø�5å•o kÑ2Ô2Ñ3Ô3ˆð �s�C¥¤	¡¤Ñ,Ô,Ð-Ð-Ð-Ý�LŠLÐGÈÑQÔQÐQØ�5ð �dÔ-Ð-Ð-Ø�5àÔ×#Ò# GÑ,Ô,Ð,ð	Ø—:’:˜g vÑ.Ô.Ð.øÝð 	ð 	ð 	Ý�LŠLÐ<¸aÑ@Ô@Ð@Ø�5�5�5�5�5øøøøð	øøø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 )zÇList 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]   rz   ÚresultsÚlineÚpartsÚentryÚstat_okÚstat_outs                r   Úlist_checkpointsz"CheckpointManager.list_checkpointsY  s€  € õ •o kÑ2Ô2Ñ3Ô3ˆÝ" 7Ñ+Ô+ˆà˜‘×'Ò'Ñ)Ô)ð 	ØˆIå ØÐ+¨Tµ3°tÔ7IÑ3JÔ3JÐKØ�Gñ
ô 
‰ˆˆF�Að
 ð 	˜ð 	ØˆIàˆØ×%Ò%Ñ'Ô'ð 	&ð 	&ˆDØ—J’J˜s AÑ&Ô&ˆEÝ�5‰zŒz˜QŠˆà! !œHØ"'¨¤(Ø!& q¤Ø# AœhØ%&Ø"#Ø!"ðð �õ (0Ø˜]¨u°Q¬x¨O¨O¨O¸UÀ1¼XÐ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¯   € õ ŒI�m YÑ/Ô/ˆØð 	5Ý%(¨¯ª°©¬¡_¤_ˆE�/Ñ"ÝŒIÐ(¨)Ñ4Ô4ˆØð 	2Ý"% a§g¢g¨a¡j¤j¡/¤/ˆE�,ÑÝŒIÐ'¨Ñ3Ô3ˆØð 	1Ý!$ Q§W¢W¨Q¡Z¤Z¡¤ˆ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 )z†Show 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   rz   r{   Úok_statr¼   Úok_diffÚdiff_outs                r   r­   zCheckpointManager.diff’  s–  € õ )¨Ñ5Ô5ˆØð 	9Ø$¨xÐ8Ð8Ð8å•o kÑ2Ô2Ñ3Ô3ˆÝ" 7Ñ+Ô+ˆà˜‘×'Ò'Ñ)Ô)ð 	ZØ$Ð/XÐYÐYÐYõ Ø˜˜{Ð+¨V°Wñ
ô 
‰
ˆˆAˆsð ð 	XØ$Ð/V¸kÐ/VÐ/VÐ/VÐWÐWÐWõ 	�%˜� ¨½ÈÑ9IÐJÑJÔJÐJõ  (Ø�X˜{¨JÐ7Ø�Gñ 
ô  
Ñˆ�˜1õ  (Ø�[ *¨lÐ;Ø�Gñ 
ô  
Ñˆ�˜1õ 	Ð-Ð-Ð-¨v°wÑ?Ô?Ð?àð 	J˜wð 	JØ$Ð/HÐIÐIÐIð Ø 'Ð/�H�H¨RØ 'Ð/�H�H¨Rð
ð 
ð 	
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   ry   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   rz   r{   Úrestore_targetr]   Úok2Ú
reason_outr–   re   s                    r   ÚrestorezCheckpointManager.restoreÄ  sý  € õ )¨Ñ5Ô5ˆØð 	9Ø$¨xÐ8Ð8Ð8å•o kÑ2Ô2Ñ3Ô3ˆð ð 	=Ý*¨9°gÑ>Ô>ˆHØð =Ø#(°8Ð<Ð<Ð<å" 7Ñ+Ô+ˆà˜‘×'Ò'Ñ)Ô)ð 	ZØ$Ð/XÐYÐYÐYõ Ø˜˜{Ð+¨V°Wñ
ô 
‰
ˆˆAˆsð ð 	nØ$Ð/V¸kÐ/VÐ/VÐ/VÐadÐalÐhlÐmÐmÐmð 	�
Š
�7ÐUÀ;ÈrÐPQÈrÄ?ÐUÐUÐUÑVÔVÐVð '0Ð8˜˜°SˆÝ"Ø˜ d¨NÐ;Ø�G¥\°AÑ%5ð
ñ 
ô 
‰ˆˆF�Cð
 ð 	_Ø$Ð/GÀ#Ð/GÐ/GÐRUÐR]ÐY]Ð^Ð^Ð^õ &Ø�M 4¨Ð5°v¸wñ
ô 
ÑˆˆZ˜ð  #Ð1��¨	ˆð Ø& r¨ rœ?ØØ ð	
ð 
ˆð ð 	'Ø&ˆF�6‰NØˆ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Ð9¨A�E˜A‘I×%Ò%Ñ'Ô'Ð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Ñ)Ô)ˆØ�;Š;‰=Œ=ð 	$ØˆIˆIàœˆIðGð Gð GˆàˆØ�u”|Ò#Ð#ÝÐ9Ð9Ð9Ð9°Ð9Ñ9Ô9Ñ9Ô9ð "Ý˜5‘z”zÐ!Ø”LˆEð �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   ry   r†   r‚   ri   r   Ú_prune)	r�   r   r–   r¶   r{   rf   rz   rÕ   rÖ   s	            r   r�   zCheckpointManager._take$  s§  € å" ;Ñ/Ô/ˆõ   ¨Ñ4Ô4ˆØð 	Ý�LŠLÐ5°sÑ;Ô;Ð;Ø�5õ ˜;Ñ'Ô'­*Ò4Ð4Ý�LŠLÐ>Å
ÈKÑXÔXÐXØ�5õ Ø�DˆM˜6 ;½ÀqÑ8Hð
ñ 
ô 
‰
ˆˆAˆsð ð 	Ý�LŠLÐ8¸#Ñ>Ô>Ð>Ø�5õ  (Ø+Ð+Ð+ØØØ!" ð	 
ñ  
ô  
Ñˆ�˜1ð ð 	å�LŠLÐ?ÀÑMÔMÐMØ�5õ
 Ø�t˜VÐ%<¸oÐNØ�K­¸Ñ)9ð
ñ 
ô 
‰
ˆˆAˆsð ð 	Ý�LŠLÐ7¸Ñ=Ô=Ð=Ø�5å�ŠÐ1°;ÀÑGÔGÐGð 	�Š�F˜KÑ(Ô(Ð(àˆ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   ry   )r�   r5   r   rf   r]   rz   r…   s          r   rú   zCheckpointManager._pruneY  sž   € å Ø+Ð+Ð+¨[¸+ñ
ô 
‰ˆˆF�Að ð 	ØˆFð	Ý˜‘K”KˆEˆEøÝð 	ð 	ð 	ØˆFˆFð	øøøð �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 ð 3¸Sð 3ð 3ð 3ð 3ð(ð (ð (ð (ð"ð "¨Sð "¸#ð "È4ð "ð "ð "ð "ðH*¨Cð *°D¸´Jð *ð *ð *ð *ðX ð
1 Cð 
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Ø6¨9Ð6Ð6Ð6à3 YÐ3Ð3Ð3Ð4€EÝ˜;¨Ñ*Ô*ð Oñ O‰ˆˆ2à�Œ_ˆØ�"ˆ9ˆ9Ø—’˜#‘”˜qÔ!×'Ò'¨Ñ,Ô,¨QÔ/×5Ò5°cÑ:Ô:¸1Ô=¸b¸q¸bÔAˆBØ�k”?×(Ò(¨Ñ-Ô-¨aÔ0ˆDØ��˜2��ˆBð —’�¨Ñ*Ô*ˆØ�fŠf�\ 1Ñ%Ô%ˆØ�vŠv�k 1Ñ%Ô%ˆØð 	ØQ˜ÐQÐQ¨E°QªJ¨J S S¸BÐQÐQÀ3ÐQÐQÈ$ÐQÐQÐQˆDˆDàˆDà�ŠÐM˜!ÐMÐM˜r ,Ô/ÐMÐM°2ÐMÐM¸¸H¼ÐMÀtÐMÐMÑNÔNÐNÑNà	‡L‚LÐHÑIÔIÐIØ	‡L‚LÐQÑRÔRÐRØ	‡L‚LÐVÑWÔWÐWØ�9Š9�UÑÔÐr   )+rÿ   r-   Úloggingr   rÀ   r™   rZ   Úpathlibr   Úhermes_constantsr   Útypingr   r   r   r   Ú	getLoggerrü   rT   r1   rx   ÚmaxÚminrÂ   Úgetenvr   Ú__annotations__r‚   Úcompiler   r,   r   r&   r    r4   ÚdictrF   Útupleri   r}   r†   rˆ   r  r  r   r   ú<module>r     s  ððð ð ð( €€€Ø €€€Ø 	€	€	€	Ø 	€	€	€	Ø €€€Ø Ð Ð Ð Ø Ð Ð Ð Ð Ð Ø ,Ð ,Ð ,Ð ,Ð ,Ð ,Ø ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,Ð ,à	ˆÔ	˜8Ñ	$Ô	$€ð "�/Ñ#Ô# mÑ3€ðð ð Ð ð0 �C˜˜C˜C  C C¨	¨¬	Ð2MÈtÑ(TÔ(TÑ$UÔ$UÑVÔVÑWÔW€ˆcÐ WÐ WÑ Wð €
ð �"”*Ð3Ñ4Ô4€ð sð ¨x¸¬}ð ð ð ð ð  3ð °Sð ¸XÀc¼]ð ð ð ð ð03 ð 3¨ð 3ð 3ð 3ð 3ð
& 3ð &¨4ð &ð &ð &ð &ð˜$ð ¨Sð °Tð ð ð ð ðL  Ø.2ð:#ð :#Ø
ˆsŒ)ð:#àð:#ð ð:#ð ð	:#ð
 " # c¤(Ô+ð:#ð ð:#ð :#ð :#ð :#ðz  4ð  °cð  ¸hÀs¼mð  ð  ð  ð  ðF
˜#ð 
 #ð 
ð 
ð 
ð 
ð"[]ð []ð []ð []ð []ñ []ô []ð []ð|
¨¨T¬
ð ¸sð Àsð ð ð ð ð ð r   