
    `gjW                     8   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ZddlZddl	m
Z
 ddlmZ ddlmZ ddlmZmZmZmZmZ ddlmZ  ej.                  e      Z e       dz  Zd	Zd
ZdZdZdZg dZ  e!d e"d edd                  Z#e$e%d<   dZ& ejN                  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-dHdee
   de
fd Z.de)de
fd!Z/d	e
d"e)de
fd#Z0d"e)de)fd$Z1d	e
d"e)de
fd%Z2	 dHd	e
de)d&ee
   de3fd'Z4d	e
ddfd(Z5e#ddfd)ee)   d	e
de)d*e$d+eee$      d&ee
   dee6e)e)f   fd,Z7de
dee
   fd-Z8d	e
de)dee)   fd.Z9d	e
de)ddfd/Z:d	e
de)ddfd0Z;d	e
dee   fd1Z<d2e)de$fd3Z=d2e
de$fd4Z>d5e
de)dee)   fd6Z? G d7 d8      Z@dee   d9e)de)fd:ZAd;ZBd	e
d<e)de6fd=ZC	 	 	 	 dId>e$d?e6d@ee
   dAe$dee)e$f   f
dBZD	 	 	 	 	 dJd>e$dCe$d?e6d@ee
   dAe$dee)eEf   fdDZFdHd@ee
   defdEZGdHd@ee
   dee)e$f   fdFZHdHd@ee
   dee)e$f   fdGZIy)KuV	  
Checkpoint Manager — Transparent filesystem snapshots via a single shared
shadow git store.

Creates automatic snapshots of working directories before file-mutating
operations (``write_file``, ``patch``, ``terminal`` with destructive flags),
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.

Storage layout (single shared store, git objects deduplicated across projects)
-----------------------------------------------------------------------------

    ~/.hermes/checkpoints/
        store/                          — single bare-ish git repo
            HEAD, config, objects/      — standard git internals (shared)
            refs/hermes/<hash16>        — per-project branch tip
            indexes/<hash16>            — per-project git index
            projects/<hash16>.json      — {workdir, created_at, last_touch}
            info/exclude                — default excludes (shared)
        .last_prune                     — auto-prune idempotency marker
        legacy-<timestamp>/             — archived pre-v2 per-project shadow
                                          repos (auto-migrated on first init)

Why a single store?
-------------------

The pre-v2 design kept a full shadow repo per working directory.  Each one
re-stored most of the project's files under its own ``objects/`` tree, with
zero sharing across worktrees of the same project.  A single user with a
dozen worktrees of the same repo burned ~40 MB each (~500 MB total) storing
the same blobs over and over.  A single shared store lets git's content-
addressable object DB deduplicate across projects and across turns, so adding
a new worktree costs near-zero.

The shadow store uses ``GIT_DIR`` + ``GIT_WORK_TREE`` + ``GIT_INDEX_FILE``
so no git state leaks into the user's project directory.

Auto-maintenance
----------------

Shadow state accumulates over time.  ``prune_checkpoints`` deletes refs whose
recorded working directory no longer exists (orphan) or whose last touch is
older than ``retention_days`` (stale), then runs ``git gc --prune=now`` to
reclaim object storage.  A size-cap pass drops the oldest checkpoints per
project until total store size is under ``max_total_size_mb``.
    N)Path)get_hermes_home)windows_hide_flags)DictListOptionalSetTuple)env_intcheckpointsstorezrefs/hermesindexesprojectszlegacy-)2znode_modules/zdist/zbuild/ztarget/zout/z.next/z.nuxt/z__pycache__/z*.pycz*.pyoz.cache/z.pytest_cache/z.mypy_cache/z.ruff_cache/z	coverage/z	.coveragez.venv/zvenv/zenv/z.git/z.hg/z.svn/z.worktrees/z*.soz*.dylibz*.dllz*.oz*.az*.jarz*.classz*.exez*.objz*.mp4z*.movz*.mkvz*.webmz*.zipz*.tarz*.tar.gzz*.tgzz*.7zz*.rarz*.isoz.envz.env.*z
.env.localz.env.*.localz	.DS_Storez	Thumbs.dbz*.log
   <   HERMES_CHECKPOINT_TIMEOUT   _GIT_TIMEOUTiP  z^[0-9a-fA-F]{4,64}$commit_hashreturnc                     | r| j                         sy| j                  d      rd| S t        j                  |       sd| S y)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    K/root/.hermes/venv/lib/python3.12/site-packages/tools/checkpoint_manager.py_validate_commit_hashr      sN     k//1"c"@PP  -Ek_UU    	file_pathworking_dirc                     | r| j                         syt        j                  j                  |       rd| S t	        |      }|| z  j                         }	 |j                  |       y# t        $ r d| cY S w xY w)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- 	ww}}Y@NN!+.Ki'002HW[)   WHVVWs   A) )A:9A:
path_valuec                 P    t        |       j                         j                         S )z;Return a canonical absolute path for checkpoint operations.)r   
expanduserr'   )r-   s    r   r&   r&      s    
&&(0022r   c                     t        t        |             }t        j                  |j	                               j                         dd S )z6Deterministic per-project hash: sha256(abs_path)[:16].N   )strr&   hashlibsha256encode	hexdigest)r!   abs_paths     r   _project_hashr8      s7    ?;/0H>>(//+,668"==r   basec                 $    | xs t         t        z  S )z+Return the single shared shadow store path.)CHECKPOINT_BASE_STORE_DIRNAME)r9   s    r   _store_pathr=      s    #O~55r   c                     t               S )u  Return the shared store path.

    Retained for backward-compatibility with callers / tests that imported
    this helper.  Under v2 the shadow git storage is shared across all
    projects — per-project isolation lives in refs and indexes, not in
    separate repo directories.
    )r=   )r!   s    r   _shadow_repo_pathr?      s     =r   dir_hashc                     | t         z  |z  S N)_INDEXES_DIRNAMEr   r@   s     r   _index_pathrE      s    ##h..r   c                     t          d|  S )N/)_REFS_PREFIX)r@   s    r   	_ref_namerI      s    ^1XJ''r   c                      | t         z  | dz  S )Nz.json)_PROJECTS_DIRNAMErD   s     r   _project_meta_pathrL      s    $$(5'999r   
index_filec                 t   t        |      }t        j                  j                         }t	        |       |d<   t	        |      |d<   |j                  dd       |j                  dd       |t	        |      |d<   n|j                  dd       t        j                  |d<   t        j                  |d<   d	|d
<   |S )uJ  Build env dict that redirects git to the shared store.

    The shared store 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.

    ``index_file``, if given, forces git to use a per-project index under
    ``store/indexes/<hash>`` so projects don't race on a shared index.
    GIT_DIRGIT_WORK_TREEGIT_NAMESPACEN GIT_ALTERNATE_OBJECT_DIRECTORIESGIT_INDEX_FILEGIT_CONFIG_GLOBALGIT_CONFIG_SYSTEM1GIT_CONFIG_NOSYSTEM)r&   r#   environcopyr2   popdevnull)r   r!   rM   normalized_working_direnvs        r   _git_envr^      s    * -[9
**//
CZC	N56CGGOT"GG.5 #J $'!zzC!zzC!$CJr   c                     dD ]B  }| |z  }|j                         r	 |j                  dd       t        j                  d|       D y# t        $ r!}t        j                  d||       Y d}~kd}~ww xY w)a  Recreate refs/ and branches/ dirs that ``git gc`` may have removed.

    ``git gc --prune=now`` on a bare repo with only packed refs can remove
    the empty ``refs/heads/`` directory.  Git 2.34+ requires ``refs/`` (and
    some versions require ``branches/``) to exist even when all refs are
    packed in ``packed-refs``.  Without them, ``git add -A`` returns
    ``fatal: not a git repository`` and all checkpoint operations fail
    silently.
    )z
refs/headsbranchesTparentsexist_okz'Repaired missing %s in checkpoint storez(Cannot create %s in checkpoint store: %sN)existsmkdirloggerdebugOSErrorwarning)r   subdirr$   excs       r   _repair_bare_repo_dirsrl     ss     - 	v~{{}

4$
7FO	  > s   )A			A3A..A3argstimeoutallowed_returncodesc                 h   t        |      }|j                         s=d| }t        j                  ddj	                  dgt        |       z         |       dd|fS |j                         s=d| }t        j                  ddj	                  dgt        |       z         |       dd|fS t        |t        |      |      }dgt        |       z   }	|xs
 t               }	 t        j                  |	d	d	||t        |      t        j                  t               
      }
|
j                  dk(  }|
j                  j!                         }|
j"                  j!                         }|s?|
j                  |vr1t        j                  ddj	                  |	      |
j                  |       |||fS # t        j$                  $ r6 d| ddj	                  |	       }t        j                  |d	       dd|fcY S t&        $ rz}t)        |dd      }|dk(  r,t        j                  ddj	                  |	      d	       Y d}~yd| }t        j                  ddj	                  |	      |d	       dd|fcY d}~S d}~wt*        $ r@}t        j                  ddj	                  |	      |d	       ddt        |      fcY d}~S d}~ww xY w)a5  Run a git command against the shared store.  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: rM   T)capture_outputtextrn   r]   cwdstdincreationflagsr   z(Git command failed: %s (rc=%d) stderr=%szgit timed out after zs: )exc_infofilenameNzGit executable not found: %s)Frs   zgit not foundz,Git command failed before execution: %s (%s)z#Unexpected git error running %s: %s)r&   rd   rf   errorjoinlistis_dirr^   r2   set
subprocessrunDEVNULLr   
returncodestdoutr   stderrTimeoutExpiredFileNotFoundErrorgetattr	Exception)rm   r   r!   rn   ro   rM   r\   msgr]   cmdresultokr   r   rk   missing_targets                   r   _run_gitr   *  s    -[9!((*-.D-EF3SXXugT
>R5SUXYb#~!((*67M6NO3SXXugT
>R5SUXYb#~
5#45*
MC'DJ
C-6%#*+$$ -.
 !#$$&$$&f''/BBLL:v00& 66!!$$ $WIS#@S4(b#~  j$7U"LL7#QULV--.D-EFCSXXc]TWbfgb#~ #:CHHSM3Y]^b#c(""#s>   CF   AJ1(J109I%.1I%J1%J115J,&J1,J1c                    | j                         syt        |       }d}t        t        h}t	        | j                               D ]  }|j                  }||v s|j                  t              r)|6t        j                  d      }| t         | z  }	 |j                  dd       ||z  }	 t        j                   t#        |      t#        |              |}	|t        j%                  d|       |S # t        $ r!}t        j                  d|       Y d}~ yd}~ww xY w# t        $ r!}t        j                  d||       Y d}~d}~ww xY w)a<  Move pre-v2 per-project shadow repos into a ``legacy-<ts>/`` dir.

    The pre-v2 layout had one shadow git repo per working directory directly
    under ``CHECKPOINT_BASE``.  The v2 layout wants a single ``store/`` dir.
    Rather than delete the old data (users might want to recover), rename
    everything except our own v2 entries into ``legacy-<timestamp>/``.  The
    legacy dir is subject to the same retention sweep and can be manually
    cleared with ``hermes checkpoints clear-legacy``.

    Returns the legacy-archive path, or None if nothing to migrate.
    Nz%Y%m%d-%H%M%STra   z'Could not create legacy archive dir: %sz*Could not archive legacy checkpoint %s: %sz_Migrated pre-v2 checkpoint repos to %s. Clear with `hermes checkpoints clear-legacy` when safe.)rd   r=   r<   _PRUNE_MARKER_NAMEr~   iterdirnamer   _LEGACY_PREFIXtimestrftimere   rh   rf   ri   shutilmover2   info)
r9   r   legacy_rootreservedchildr   stamprk   dest_s
             r   _migrate_legacy_storer   r  sE    ;;=E"&K 23Hdlln% Uzz8t~> MM/2EN#3E7!;;K!!$!> T!	UKKE
CI.!U( 	AF	

 !  H#N  	UNNGPSTT	Us0   C1+(D1	D:DD	E'EEc           
         | j                   }| j                         s	 |j                  dd       t	        |       | dz  j                         ry| j                  dd       | t
        z  j                  d       | t        z  j                  d       t        j                  j                         }t        j                  |d<   t        j                  |d<   d	|d
<   dD ]  }|j                  |d        	 t        j                  dddt        |       gdd|t        t        j                   t#                     }|j$                  dk7  rd|j&                  j)                          S 	 t        |      }t/        g d| |       t/        g d| |       t/        g d| |       t/        g d| |       t/        g d| |       | dz  }|j                  d       |dz  j1                  dj3                  t4              dz   d       t6        j9                  d|        y# t        $ r}d| cY d}~S d}~ww xY w# t        j*                  t,        f$ r}d| cY d}~S d}~ww xY w)zInitialise the shared shadow store if needed.  Returns error or None.

    Also performs one-time migration of pre-v2 per-directory shadow repos
    into ``legacy-<timestamp>/``.
    Tra   z"Could not create checkpoint base: NHEAD)rc   rT   rU   rV   rW   )rO   rP   rS   rQ   rR   rr   initz--bare)ru   rv   r]   rn   rx   ry   r   zShadow store init failed: )configz
user.emailzhermes@local)r   z	user.namezHermes Checkpoint)r   zcommit.gpgsignfalse)r   ztag.gpgSignr   )r   zgc.auto0r   exclude
utf-8encodingz"Initialised checkpoint store at %s)parentrd   re   rh   r   rC   rK   r#   rX   rY   r[   rZ   r   r   r2   r   r   r   r   r   r   r   r   r   
write_textr}   DEFAULT_EXCLUDESrf   rg   )	r   r!   r9   rk   init_envkr   cfg_wdinfo_dirs	            r   _init_storer     s:    <<D<<>	>JJtdJ3
 	d# 	KKtK,
$$d$3
%%t%4
 zz H$&JJH !$&JJH !&)H"#2 Q2FHc%j1d,$$,.
 !/0C0C0E/FGG " YF5ufE95&I2E6B/?'7v~HNNDN!	%%		"#d*W &  LL5u=m  	>7u==	>D %%'89 2+C5112s<   H 8A.H3 	H0!H+%H0+H03IIIIc                 L   t        |      }t        | |      }t        j                         }t        t	        |            ||d}|j                         rK	 t        j                  |j                  d            }t        |t              r|j                  d|      |d<   	 |j                  j                  dd       |j!                  t        j"                  |      d       y# t        t        f$ r Y Vw xY w# t        $ r!}t$        j'                  d||       Y d}~yd}~ww xY w)	zDCreate or update ``projects/<hash>.json`` with workdir + timestamps.)workdir
created_at
last_touchr   r   r   Tra   z'Could not write project metadata %s: %sN)r8   rL   r   r2   r&   rd   jsonloads	read_text
isinstancedictgetrh   r)   r   re   r   dumpsrf   rg   )r   r!   r@   	meta_pathnowmetaexistingrk   s           r   _register_projectr     s   [)H"5(3I
))+C !=> #38D	zz)"5"5w"5"GHH(D)%-\\,%D\"Ptd;TZZ-@	 $ 		
  P>	3OOPs,   A
C$  AC9 $C65C69	D#DD#c                 8   t        |      }t        | |      }|j                         st        | |       y	 t	        j
                  |j                  d            }t        |t              si }t        t        |            |d<   t        j                         |d<   |j                  d|d          	 |j                  t	        j                   |      d       y# t        t        f$ r i }Y w xY w# t        $ r!}t"        j%                  d||       Y d}~yd}~ww xY w)z7Update last_touch for a project, preserving created_at.Nr   r   r   r   r   z(Could not update project metadata %s: %s)r8   rL   rd   r   r   r   r   rh   r)   r   r   r2   r&   r   
setdefaultr   r   rf   rg   )r   r!   r@   r   r   rk   s         r   _touch_projectr     s    [)H"5(3I%-zz)--w-?@ dD!/+67DODOOL$|"45QTZZ-@ Z    Q?CPPQs)   %C 1&C/ C,+C,/	D8DDc                 H   | t         z  }|j                         sg S g }|j                  d      D ][  }|j                  }	 t	        j
                  |j                  d            }t        |t              sF||d<   |j                  |       ] |S # t        t        f$ r Y qw xY w)z/Return all registered projects under the store.z*.jsonr   r   _hash)rK   rd   globstemr   r   r   rh   r)   r   r   append)r   projects_diroutr   r@   r   s         r   _list_projectsr     s    ,,L 	C!&&x0 		>>	::i1171CDD $% W

4	 J $ 		s   %BB! B!r$   c                     d}	 t        |       j                  d      D ]  }|dz  }|t        kD  s|c S  	 |S # t        t        f$ r Y |S w xY w)z;Quick file count estimate (stops early if over _MAX_FILES).r   *   )r   rglob
_MAX_FILESPermissionErrorrh   )r$   countr   s      r   _dir_file_countr   $  se    Ed!!#& 	AQJEz!	 L W% Ls   +8 8 8 A
Ac                     d}	 | j                  d      D ]0  }	 |j                         r||j                         j                  z  }2 	 |S # t        $ r Y Aw xY w# t        $ r Y |S w xY w)z9Best-effort recursive size in bytes.  Returns 0 on error.r   r   )r   is_filestatst_sizerh   )r$   totalps      r   _dir_size_bytesr   1  sy    EC 	A99;QVVX---E	 L	   Ls4   A -AA 	AA AA 	A'&A'shadow_repoc                     t        | |      }|r|S t        | |       	 | dz  j                  t        t	        |            dz   d       y# t
        $ r Y yw xY w)a  Backwards-compatible initialiser.

    In v1 ``shadow_repo`` was a per-project dir; in v2 it's the shared
    ``store/`` path (or a test path that we respect).  We initialise the
    store at ``shadow_repo``, create per-project markers, and return None
    on success.
    HERMES_WORKDIRr   r   r   N)r   r   r   r2   r&   rh   )r   r!   errs      r   _init_shadow_repor   E  sp     k;
/C

k;/	'	'33,-4w 	4 	

   s   +A
 
	AAc            	          e Zd ZdZ	 	 	 	 ddede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eddfdZded
ededdfdZdeddfdZy)!CheckpointManageraB  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.
    max_total_size_mb : int
        Hard ceiling on total store size.  Oldest checkpoints per project
        are dropped when the store exceeds this after a commit.
    max_file_size_mb : int
        Skip adding any single file larger than this to a checkpoint.
        (Implemented via ``.gitignore`` excludes + a post-stage size check.)
    enabledmax_snapshotsmax_total_size_mbmax_file_size_mbc                     || _         t        dt        |            | _        t        dt        |            | _        t        dt        |            | _        t               | _        d | _        y )Nr   r   )	r   maxintr   r   r   r   _checkpointed_dirs_git_available)selfr   r   r   r   s        r   __init__zCheckpointManager.__init__v  s[      C$67!$Q,=(>!? #As+;'< =,/E.2r   r   Nc                 8    | j                   j                          y)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   r!   reasonc                 2   | j                   sy| j                  =t        j                  d      du| _        | j                  st        j                  d       | j                  syt        t        |            }|dt        t        j                               hv rt        j                  d|       y|| j                  v ry| j                  j                  |       	 | j                  ||      S # t        $ r }t        j                  d|       Y d}~y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.
        FNrr   z#Checkpoints disabled: git not foundrG   z,Checkpoint skipped: directory too broad (%s)z!Checkpoint failed (non-fatal): %s)r   r   r   whichrf   rg   r2   r&   r   homer   add_taker   )r   r!   r   abs_dires        r   ensure_checkpointz#CheckpointManager.ensure_checkpoint  s     ||&"(,,u"5T"AD&&BC""ok23 sC		,--LLGQd---##G,	::gv.. 	LL<a@	s   C- -	D6DDc           	      2   t        t        |            }t        t              }|dz  j	                         sg S t        t        |            }t        d|ddt        | j                        g||ddh      \  }}}|r|sg S g }|j                         D ]  }	|	j                  dd	      }
t        |
      d
k(  s$|
d   |
d   |
d   |
d	   dddd}t        dd|
d    d|
d   g||ddh      \  }}}|r|r| j                  ||       |j                  |        |S )z?List available checkpoints for a directory (most recent first).r   logz--format=%H|%h|%aI|%sz-n      ro   |      r   r      )hash
short_hash	timestampr   files_changed
insertions	deletionsdiffz--shortstatz~1)r2   r&   r=   r;   rd   rI   r8   r   r   
splitlinessplitlen_parse_shortstatr   )r   r!   r   r   refr   r   r   resultslinepartsentrystat_okstat_outs                 r   list_checkpointsz"CheckpointManager.list_checkpoints  sH   ok23O,&&(Ig./ C0$D<N<N8OP7!$c

FA I %%' 	&DJJsA&E5zQ!!H"'(!&q#Ah%&"#!" (0]uQxjOU1XF7),c
($1
 x))(E:u%'	&( r   	stat_liner  c                 D   t        j                  d|       }|rt        |j                  d            |d<   t        j                  d|       }|rt        |j                  d            |d<   t        j                  d|       }|rt        |j                  d            |d<   yy)	z-Parse git --shortstat output into entry dict.z
(\d+) filer   r	  z(\d+) insertionr
  z(\d+) deletionr  N)researchr   group)r  r  ms      r   r  z"CheckpointManager._parse_shortstat  s     IImY/%(_E/"II()4"%aggaj/E,II'3!$QWWQZE+ r   r   c                    t        |      }|rd|dS t        t        |            }t        t              }|dz  j                         sdddS t        dd|g||      \  }}}|s	dd| ddS t        |      }	t        ||	      }
t        d	d
g||t        dz  |
       t        dd|dg|||
      \  }}}t        d|ddg|||
      \  }}}t        |	      }t        d|g|||
dh       |s|sdddS d|r|nd|r|dS ddS )z<Show diff between a checkpoint and the current working tree.Fsuccessr|   r   'No checkpoints exist for this directorycat-file-tCheckpoint '' not foundr   -Ar  rn   rM   r  z--stat--cachedrt   z
--no-color	read-treer   rM   ro   zCould not generate diffTrs   )r!  r   r  )r   r2   r&   r=   r;   rd   r   r8   rE   r   rI   )r   r!   r   hash_errr   r   r   r   r   r@   rM   ok_statr  ok_diffdiff_outr  s                   r   r  zCheckpointManager.diff  sk   (5$x88ok23O,&&($/XYY{+UG

As $k]+/VWW ) 1
 	%w%)j	B  (X{J77z 
1  ([*l;7z 
1 !+s#UG&&)U	, w$/HII  'HR 'H
 	
 .0
 	
r   r    c                 2   t        |      }|rd|dS t        t        |            }|rt        ||      }|rd|dS t	        t
              }|dz  j                         sdddS t        dd|g||      \  }}	}
|sdd| d|
xs d	d
S | j                  |d|d	d  d       t        |      }t        ||      }|r|nd}t        d|d|g||t        dz  |      \  }}}
|sdd|
 |
xs d	d
S t        ddd|g||      \  }}}	|r|nd}d|d	d ||d}|r||d<   |S )z$Restore files to a checkpoint state.Fr   r   r"  r#  r$  r%  r&  N)r!  r|   rg   z$pre-rollback snapshot (restoring to    ).checkout--r  r(  zRestore failed: r   --format=%s-1unknownT)r!  restored_tor   	directoryfile)r   r2   r&   r,   r=   r;   rd   r   r   r8   rE   r   )r   r!   r   r    r,  r   path_errr   r   r   r   r@   rM   restore_targetr   ok2
reason_outr   r   s                      r   restorezCheckpointManager.restore  s   (5$x88ok23*9g>H#(8<<O,&&($/XYY{+UG

As $k]+/V [D* * 	

7B;rPQ?BSSTUV ) 1
&/S"dN;7L1$4!
FC $1A#/G [D* * &M45ug
Z  #	 &r? 	
 &F6Nr   c                    t        |      }|j                         r|}n|j                  }h d}|j                  k7  r;t        fd|D              rt	              S j                  j                  k7  r;t	        |      S )z?Resolve a file path to its working directory for checkpointing.>	   .hg.gitgo.modpom.xml
Cargo.tomlpackage.jsonpyproject.tomlGemfileMakefilec              3   D   K   | ]  }|z  j                           y wrB   )rd   ).0r  checks     r   	<genexpr>z=CheckpointManager.get_working_dir_for_path.<locals>.<genexpr>_  s     9AEAI%%'9s    )r&   r   r   anyr2   )r   r    r$   	candidatemarkersrM  s        @r   get_working_dir_for_pathz*CheckpointManager.get_working_dir_for_pathS  sx    y);;=IIGu||#9995z!LLE u||#
 9~r   c                    t        t              }t        ||      }|rt        j	                  d|       yt        ||       t        |      t        kD  rt        j	                  dt        |       yt        |      }t        ||      }t        |      }|j                         rDt        dd|dz   g||dh      \  }}	}
|r|	rt        d	|	g|||dh
       n/	 |j                          n|j                  j!                  dd       t        ddg||t"        dz  |      \  }}
}|st        j	                  d|       y| j$                  dkD  r| j'                  |||       t        dd|dz   g||dh      \  }}	}
|xr t)        |	      }|r2t        ddd|	g||dh|      \  }}
}
|rUt        j	                  d|       yt        ddg|||      \  }}}
|r'|j+                         st        j	                  d|       yt        dg|||      \  }}}|r|st        j	                  d|       yd|d|d g}|r	d|d!|	d|d g}t        ||||      \  }}}|r|st        j	                  d"|       yd#||g}|rd#|||	g}t        |||      \  }}
}|st        j	                  d$|       yt        j	                  d%|||d&d'        | j-                  |||       | j/                  |       y# t        $ r Y w xY w)(z*Take a snapshot.  Returns True on success.z Checkpoint store init failed: %sFz#Checkpoint skipped: >%d files in %s	rev-parsez--verifyz	^{commit}r   r  r*  r+  Tra   r   r'  r  r(  zCheckpoint git-add failed: %sr   z
diff-indexr)  --quietr   )ro   rM   z$Checkpoint skipped: no changes in %sls-filesrt   z$Checkpoint skipped: empty tree in %sz
write-treez Checkpoint write-tree failed: %scommit-tree-m--no-gpg-sign-pz!Checkpoint commit-tree failed: %s
update-refz Checkpoint update-ref failed: %szCheckpoint taken in %s: %s (%s)Nr1  )r=   r;   r   rf   rg   r   r   r   r8   rE   rI   rd   r   unlinkrh   r   re   r   r   _drop_oversize_from_indexboolr   _prune_enforce_size_cap)r   r!   r   r   r   r@   rM   r  ok_ref
ref_commitr   r   has_refr.  ok_lsls_outok_treetree_shacommit_args	ok_commitnew_shaupdate_args	ok_updates                          r   r   zCheckpointManager._takei  s[   O,%-LL;SAuk* ;'*4LL>
KX - 1
!
 $,j#*;<{%(E%!FJ
 * *-;)),	%%'
 ##D4#@ DM5+ 1$

As LL8#>  1$**5+zJ
 !)*cK&78;!$!

A
 -T*-$z9jA{%&C%	MGQ C[Q  (Z({% E61
 V\\^C[Q "*NE;!"
3 hLL;SA %hfoN((D*dFTcdK"*!#
	7C LL<cB $S'2'gzBK$
	1c LL;SA6VWUWVW[Y 	E;, 	u%y  s   K 	K('K(r   rM   c                 4   | j                   dz  dz  }|dk  ryt        g d|||      \  }}}|r|sy|j                  d      D cg c]  }|s|	 }	}t        |      }
g }|	D ]7  }	 |
|z  j	                         j
                  }||kD  s'|j                  |       9 |syt        j                  dt        |      | j                          d}t        dt        |      |      D ]   }||||z    }t        g d	|z   |||d
h       " yc c}w # t        $ r Y w xY w)zRemove any staged file larger than ``max_file_size_mb`` from the index.

        Lets the agent keep snapshotting source code while refusing to
        swallow generated assets (datasets, model weights, logs, videos).
           r   N)rV  r)  z-zrt    z<Checkpoint: dropping %d oversize file(s) (>%d MB) from index   )rmr)  rU  r5  r   r+  )r   r   r  r&   r   r   rh   r   rf   rg   r  range)r   r   r!   rM   capr   r   r   r   pathsr*   oversizerelsizeBATCHichunks                    r   r]  z+CheckpointManager._drop_oversize_from_index  sF    ##d*T1!8 *;:
FA  #LL06qA66%k2  	%C#c)//199 cz$	% JM400	
 q#h-/ 	AQq5y)E3e;{z%(E	' 7  s   DD'D	DDr  c                 d   t        dd|g||dh      \  }}}|sy	 t        |      }|| j                  k  ryt        dd|g||      \  }}	}|r|	sy|	j	                         }
|
| j                   d }d}|D ]f  }t        d| dg||      \  }}}|r|s yt        d	d
d|g||      \  }}}|r|r|nd}d|d|dg}|	d|d|d|dg}t        |||      \  }}}|r|s y|}h |yt        d||g||       t        g d||       t        g d||t
        dz         t        |       y# t        $ r Y yw xY w)u  Keep only the last ``max_snapshots`` commits on the per-project ref.

        v1's ``_prune`` was documented as a no-op (``git``'s pack mechanism
        was supposed to handle it, but only the log view was limited — loose
        objects accumulated forever).  v2 actually rewrites the ref to drop
        commits older than ``max_snapshots`` and then runs ``git gc`` on the
        store so unreachable objects are reclaimed.
        rev-list--countr   r  N	--reverserT  ^{tree}r   r6  r7  
checkpointrW  rX  rY  rZ  r[  reflogexpirez--expire=nowz--allgcz--prune=nowrU  r  rn   )r   r   r)   r   r  r   rl   )r   r   r!   r  r   r   r   r   ok_listlist_outcommitskeep
new_parentsharf  rg  ok_msgr   
commit_msgrm   ri  rj  s                         r   r_  zCheckpointManager._prune  s    !C(%!$
FA 	KE D&&&  (c*E; 
1 h%%'***+, %)
 	!C#+Y/0%$ GXq (%tS15+NFC !'3LJ!8T:OD%%xzj/;$,T5+$F!IwG J#	!& ,Z0%E 	9;	
 	,;q(8	
 	u%a  		s   D# #	D/.D/c           
      j   | j                   dk  ry| j                   dz  dz  }t        |      }||k  ryt        j                  d| j                   |dz         t	        ddt
        g|t        |j                        dh	      \  }}}|r|sy|j                         D cg c]  }|j                         s| }}d
}	t        d      D ]{  }t        |      }||k  r nh|D ][  }
t	        dd|
g|t        |j                        dh	      \  }}}	 |rt        |      nd}|dk  rDt	        dd|
g|t        |j                              \  }}}|r|sp|j                         }|dd }d}d
}|D ]  }t	        d| dg|t        |j                              \  }}}|r|sd} nrt	        ddd|g|t        |j                              \  }}}|r|r|nd}d|d|dg}|	d|d|d|dg}t	        ||t        |j                              \  }}}|r|sd} n|} |s|7t	        d|
|g|t        |j                               d}	^ |	r| n t	        g d|t        |j                               t	        g d|t        |j                        t        dz         t        |       yc c}w # t        $ r d}Y w xY w) zIf total store size exceeds ``max_total_size_mb``, drop oldest
        checkpoints across ALL projects until under the cap.
        r   Nrn  uA   Checkpoint store exceeded %d MB (actual %d MB) — pruning oldest   for-each-ref--format=%(refname)r   r  F   r|  r}  r   r~  rT  r  Tr   r6  r7  r  rW  rX  rY  rZ  r[  r  r  r  r  )r   r   rf   r   r   rH   r2   r   r  r   rr  r   r)   r   rl   )r   r   	cap_bytesrw  r   r   r   rrefsany_droppedr  ok_count	count_outr   r  r  r  r  r  failr  rf  rg  r  r   r  rm   ri  rj  s                                r   r`  z#CheckpointManager._enforce_size_cap`  s    !!Q&**T1D8	u%9O""D[$9	
 !2LA3u||$!$
FA
 !,,.<a!'')<<r 1	A"5)Dy  +#)1C0%U\\9J),*&)Q.6C	NAE A:'/c2E3u||;L($1 h"--/qr{,0
 )C+3$Y&78%U\\AR,(GXq #(#%-tS95#ellBS&NFC )/3LJ)8T:WD!- -xz $j/ C,4T5#ellBS,T)Iw$G#!(J')( :-,Z8%U\\ARS"W+#X c1	f 	93u||$	
 	,3u||$lQ.>	
 	u% = " Es   J3JJ##J21J2)Fr  i  r   )r   N)autorB   )__name__
__module____qualname____doc__r^  r   r   r   r2   r   r   r   r  staticmethodr  r  r@  rR  r   r   r]  r_  r`   r   r   r   r   `  st   . !$ "33 3 	3
 3$( S  #  4  D'C 'DJ 'R 
1C 
1 
1 
1 
10
 0
# 0
$ 0
d73 7S 7S 7TX 7r# # ,D Dc Dd DL,,(+,9=,	,\A&D A&s A& A& A&FV&t V& V&r   r   r:  c                 ~   | sd| S d| dg}t        | d      D ]  \  }}|d   }d|v rY|j                  d      d   j                  d      d   j                  d	      d   d
d }|d   j                  d      d   }| d| }|j                  dd      }|j                  dd      }|j                  dd      }	|rd| d|dk7  rdnd d| d|	 d	}
nd}
|j                  d| d|d    d| d|d    |
 	        |j                  d       |j                  d       |j                  d       dj	                  |      S )z+Format checkpoint list for display to user.zNo checkpoints found for u   📸 Checkpoints for z:
r   r  T+r   r   N   rq   r	  r
  r  z  (z filesrs   z, +z/-r2  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 Nr   )	enumerater  r   r   r}   )r   r:  linesry  cptsdatefilesinsdeler   s              r   format_checkpoint_listr    s|   *9+66$YKs34E;* O2_"9#q!'',Q/55c:1=bqABk?((-a0D62$B*ff\1%vvk1%uEQJSB$?s3%r$qQDDr!Br,/02$bHtfMNO" 
LLHI	LLQR	LLVW99Ur   z.last_pruner  c                 Z    t        dd|g| t        | j                        dh      \  }}}|S )z6Delete a ref from the store.  Returns True on success.r[  z-dr   r  )r   r2   r   )r   r  r   r   s       r   _delete_refr    s5    	tS!5#ell*; EHB1 Ir   retention_daysdelete_orphanscheckpoint_baser   c           
         |xs t         }dddddd}|j                         s|S t        |      }d}| dkD  rt        j                         | dz  z
  }|j	                         D ]  }|j                         s|j                  t        k(  r)|j                  j                  t              rc| dk  rN	 |j                         j                  }	|	|k\  ro	 t        |      }
t        j                  |       |dxx   |
z  cc<   |dxx   dz  cc<   |dz  j                         s|dxx   dz  cc<   d
}|rUd
}|dz  }|j                         r!	 |j#                  d      j%                         }|t)        |      j                         sd}|R| dkD  rMd}	 |j+                  d      D ])  }	 |j                         j                  }t-        ||      }+ 	 |dkD  r||k  rd}|}	 t        |      }
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z  j                         rut1        |      D ]$  }|j3                  d      xs d}|j3                  d      xs d}|s1|dxx   dz  cc<   d
}|r|rt)        |      j                         sd}n0| dkD  r+t5        |j3                  dd      xs d      }|dkD  r||k  rd}|t7        |      }t9        ||       	 t;        ||      }|j                         r|j=                          	 t?        ||      }|j                         r|j=                          |dk(  r|dxx   dz  cc<   |dxx   dz  cc<   ' tA        g d|tC        |             tA        g d|tC        |      tD        dz         tG        |       |dkD  r|dz  dz  }tI        d      D ]  }t        |      }
|
|k  r ntA        dd tJ        g|tC        |      d!h"      \  }}}|r/|jM                         D cg c]  }|j%                         s| c}ng }|s n.d#} |D ]  }tA        d$d%|g|tC        |      d!h"      \  }!}"}	 |!rtO        |"      nd}#|#dk  r:tA        d$d&|g|tC        |            \  }$}%}|$r|%s\|%jM                         }&|&dd
 }'d
}(d#})|'D ]  }*tA        d'|* d(g|tC        |            \  }+},}|+r|,sd)}) n^tA        d*d+d,|*g|tC        |            \  }-}	}|-r|	r|	nd-}.d.|,d/|.d0g}/|(	d.|,d1|(d/|.d0g}/tA        |/|tC        |            \  }0}1}|0r|1sd)}) n|1}( |)s|(tA        d2||(g|tC        |             d)} " | r n tA        g d|tC        |             tA        g d|tC        |      tD        dz         tG        |       t        |      }2||2z
  }3t-        |d   |3      |d<   |S # t        $ r Y w xY w# t        $ r/}|dxx   dz  cc<   t        j!                  d	||       Y d
}~d
}~ww xY w# t        t&        f$ r d
}Y w xY w# t        $ r Y w xY w# t        $ r Y w xY w# t        $ r9}|dxx   dz  cc<   t        j!                  d|j                  |       Y d
}~Fd
}~ww xY w# t        $ r Y w xY w# t        $ r Y nw xY wc c}w # tP        $ r d}#Y Yw xY w)3u  Delete stale/orphan checkpoints and reclaim store space.

    A project entry is deleted when either:

    * ``delete_orphans=True`` and its ``workdir`` no longer exists on disk
      (the original project was deleted / moved); OR
    * its ``last_touch`` is older than ``retention_days`` days.

    Additionally, if ``max_total_size_mb > 0`` and the store exceeds that
    after orphan/stale pruning, the oldest commit per remaining project is
    dropped until the store is under the cap.

    Legacy-archive dirs (``legacy-*``) older than ``retention_days`` are
    also deleted.

    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_freedg        iQ r  r  r   r  z&Failed to delete legacy archive %s: %sNr   r  r   r   r   orphanr   staler  z&Failed to prune checkpoint repo %s: %sr   rs   r   r   r  r  r  r  rn  r  r  r  r   r  Fr|  r}  r~  rT  r  Tr   r6  r7  r  rW  rX  rY  rZ  r[  ))r;   rd   r   r   r   r   r   r<   r   r   r   st_mtimerh   r   rmtreerf   ri   r   r   UnicodeDecodeErrorr   r   r   r=   r   r   floatrI   r  rE   r\  rL   r   r2   r   rl   rr  rH   r  r   r)   )4r  r  r  r   r9   r   size_beforecutoffr   r  rw  rk   r   r   	wd_markernewestr   mtr   r   r@   r   r  idxmpr  _ir   r   r   r  r  any_dropok_cr  r   ok_llor  r  r  r  r  ok_ttshaok_mr   rm   ok_cmrj  
size_afterdeltas4                                                       r   prune_checkpointsr    s   4 -oDF ;;=!$'K F~55 @V||~::'::  0"JJL)) F{U&u-e$}%-%'1,' &&(yQ $%)G 00I!#'1171CIIKG d7m&:&:&<!>nq0FS) !A!VVX..!$VR! zfvo >
	V"5)DMM% =!T)!!'(A-('1,'{@VF E "5) !	-Dxx(.BHhhy)/RG9"Fwd7m6J6J6L!!#"488L!#<#AB
>j6&9$F~H%Cs#!%2::<JJL'x899;IIK !'(A-('1,'C!	-H 	93t9	
 	,3t9lQ&6	
 	u% q )D047IBi :&u-9$ (#%:LI3t9),!FA
 IK6#4#4#6Da!'')DPR  +$C)1#Y4eSY-0E*&D)Q"26IA z "*#[#6s4y#KD"a  r  mmoG"12;D04J D# -(0(SE*;<eSY)dA  $4#'D!%-"M4=uc$i&
a $(Aa< -tT3P%1$14z$(#$@D,4T5#d),L)w$G#'D!%,
'-( z1 lC<eSYO#HW+$X  u:v =s4y 0s4y,*: #5) &J*$E} 5u=F=Mi    Ux A% GPSTTU  !34 #"G# # ! !   	V8!NNCUZZQTUU	V:    < E & " !"s   0W':W7	 X2Y%&Y
YAY*,Z/1,Z?-[[:['	W43W47	X/ $X**X/2YY
	YYYY	Y'&Y'*	Z,3.Z''Z,/	Z<;Z<?	[[[#"[#min_interval_hoursc                    |xs t         }ddi}	 |j                         sdddddd|d<   |S |t        z  }t        j                         }|j                         r=	 t	        |j                  d      j                               }	||	z
  |dz  k  rd	|d<   |S 	 t        | |||
      }
|
|d<   	 |j                  t        |      d       |
d   |
d   z   }|dkD  r%t        j                  d||
d   |
d   |
d   dz         |S # t        t        f$ r Y yw xY w# t        $ r }t        j                  d|       Y d}~pd}~ww xY w# t        $ r/}t        j!                  d|       t        |      |d<   Y d}~|S d}~ww xY w)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.

    Returns ``{"skipped": bool, "result": prune_checkpoints-dict,
    "error": optional str}``.
    skippedFr   r  r   r   r   i  T)r  r  r  r   z+Could not write checkpoint prune marker: %sNr  r  zZcheckpoint auto-maintenance: pruned %d entry(ies) (%d orphan, %d stale), reclaimed %.1f MBr  r  z&checkpoint auto-maintenance failed: %sr|   )r;   rd   r   r   r  r   r   rh   r)   r  r   r2   rf   rg   r   r   ri   )r  r  r  r  r   r9   r   markerr   last_tsr   rk   r   s                r   maybe_auto_prune_checkpointsr    s    -oD'/C, {{}AACM J**iik==? 0 0' 0 B H H JK=#5#<<%)C	NJ = #)) /	
 H	Mc#h9 '(6/+BB19KK;'('}%5 J= Z(   	MLLFLL	M   ?E3xGJ	 sj   E  -E  :C? E  ,D 5E  ?DE  DE  	D=D83E  8D==E   	E8	$E33E8c           
      ^   | xs t         }t        |      ddddg g d}|j                         s|S t        |      }|j                         rt	        |      |d<   |dz  j                         rt        |      D ]  }|j                  d      xs d}|j                  d      xs d}t        |      }t        dd	|g|t        |      d
h      \  }}	}
	 |rt        |	      nd}|d   j                  ||t        |      xr t        |      j                         |j                  d      |j                  d      |d        t        |d         |d<   |j                         D ]  }|j!                         s|j"                  j%                  t&              s4	 t	        |      }|dxx   |z  cc<   	 |j+                         j,                  }|d   j                  |j"                  ||d        t	        |      |d<   |S # t        $ r d}Y *w xY w# t(        $ r d}Y {w xY w# t(        $ r d}Y dw xY w)zReturn a summary of the shadow store.

    ``{"base": path, "store_size_bytes": N, "legacy_size_bytes": N,
       "total_size_bytes": N, "project_count": N, "projects": [...],
       "legacy_archives": [...]}``
    r   )r9   store_size_byteslegacy_size_bytestotal_size_bytesproject_countr   legacy_archivesr  r   r   rs   r   r|  r}  r   r  r   r   r   )r  r   rd   r   r   r  r  r  r  )r   
size_bytesmtimer  )r;   r2   rd   r=   r   r   r   rI   r   r   r)   r   r^  r   r  r   r   r   r   r   rh   r   r  )r  r9   r   r   r   r@   r   r  r   r  r   r  r   rw  r  s                  r   store_statusr  "  s=    -oDD	C ;;=
E||~"1%"8FN""$&u- 88G,2((9-3)#+C0%T),$ Iq 02c)nG J&&$&"7mFW0D0D0F"&((<"8"&((<"8&( ( s:/C <<>ejj33NC&u- #$,$ZZ\** !"))

"+ " .d3CJ= "  G    
  s6   G;H-H;H
	H
HHH,+H,c                     | xs t         }ddd}|j                         s|S t        |      }	 t        j                  |       ||d<   d|d<   |S # t
        $ r"}t        j                  d||       Y d}~|S d}~ww xY w)	z{Nuke the entire checkpoint base (store + legacy).  Irreversible.

    Returns ``{"bytes_freed": N, "deleted": bool}``.
    r   Fr  deletedr  Tr  z&Could not clear checkpoint base %s: %sN)r;   rd   r   r   r  rh   rf   ri   )r  r9   r   rw  rk   s        r   	clear_allr  e  s    
 -oD
.C;;=
4 DLd!MI J  L?sKKJLs   A 	A:A55A:c                    | xs t         }ddd}|j                         s|S t        |j                               D ]m  }|j	                         r|j
                  j                  t              s3	 t        |      }t        j                  |       |dxx   |z  cc<   |dxx   dz  cc<   o |S # t        $ r!}t        j                  d||       Y d}~d}~ww xY w)zhDelete all ``legacy-*`` archive directories.

    Returns ``{"bytes_freed": N, "deleted": count}``.
    r   r  r  r  r   z&Could not delete legacy archive %s: %sN)r;   rd   r~   r   r   r   r   r   r   r   r  rh   rf   ri   )r  r9   r   r   rw  rk   s         r   clear_legacyr  x  s    
 -oD
*C;;=
dlln% 	Q||~UZZ%:%:>%J	Q"5)DMM% $&	NaN	Q J  	QNNCUCPP	Qs   /:B--	C6CCrB   )   TNr   )r     TNr   )Jr  r3   r   loggingr#   r  r   r   r   pathlibr   hermes_constantsr   hermes_cli._subprocess_compatr   typingr   r   r   r	   r
   utilsr   	getLoggerr  rf   r;   r<   rH   rC   rK   r   r   r   minr   r   __annotations__r   compiler   r2   r   r,   r&   r8   r=   r?   rE   rI   rL   r   r^   rl   r^  r   r   r   r   r   r   r   r   r   r   r  r   r  r  objectr  r  r  r  r  r   r   <module>r     ss  0d    	 	     , < 3 3 			8	$ "#m3   = @ CG,G$LMNc N 
 "**34s x}  3 S Xc] ,3 3 3
>s >s >6htn 6 6
3 4 /t /s /t /( ( (:d :c :d : "&""" " 
	"J$ 4 4  .2!%A#
s)A#A# A# 	A#
 "#c(+A# A# 4c>A#P- -$ -`At A# A(3- AHPT P P P*Q$ QS QT Q,$ 4: &
# 
# 
$ 3 (4 c hsm 6V	& V	&rT
 s s J # t # $  &*	nnn d^n 	n
 
#s(^nd  &*@@@ @ d^	@
 @ 
#v+@N@(4. @D @Fx~ c3h &(4. DcN r   