+
    jHàjÎQ  ã                  óœ  a € 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
HtHt ^ RIHt ^ RIHtHtHt ^ RIHtHtHt ^ RIHt R R	 ltR
t]P8                  ! R4      t]P8                  ! R4      t]! 4       t R]!R&   Rkt"Rlt#Rt$Rt%R R lt&R R lt'R R lt(R R lt)RmR R llt*]PV                  RmR  R! ll4       t,] ! R" R#4      4       t-] ! R$ R%4      4       t.R& R' lt/R( R) lt0R* R+ lt1R,R-/R. R/ llt2R0RR1RRRRRRRRRRRR2R-/R3 R4 llt3R,R-/R5 R6 llt4R7 R8 lt5R9RRRRRRRRR/R: R; llt6R< R= lt7R>RR?R-/R@ RA llt8RB RC lt9RD RE lt:RF RG lt;RH RI lt<RJ RK lt=RL RM lt>RN RO lt?RP RQ lt@RR RS ltART RU ltBRV RW ltCRX RY ltDRZR-/R[ R\ lltER]R/R^ R_ lltFR`R-R]R/Ra Rb lltGRc Rd ltHR,R-/Re Rf lltIRgRh/Ri Rj lltJR# )nz¥Per-profile first-class Project store.

The schema is small and additive: column additions go through ``add_column_if_missing`` so
opening an old DB is always safe.
)ÚannotationsN)Ú	dataclassÚfield)ÚPath)ÚIterableÚListÚOptional)Úadd_column_if_missingÚopen_dbÚ	write_txn©Úget_hermes_homec               ó   € V ^8„  d   QhRR/# )é   Úreturnr   © )Úformats   "Ú;/home/ubuntu/.hermes/hermes-agent/hermes_cli/projects_db.pyÚ__annotate__r      s   € ÷ -ñ -˜$ñ -ó    c                 ó$   € \        4       R,          # )z@The per-profile projects DB path (``$HERMES_HOME/projects.db``).úprojects.dbr   r   r   r   Úprojects_db_pathr      s   € äÓ˜}Õ,Ð,r   a”  
CREATE TABLE IF NOT EXISTS projects (
    id            TEXT PRIMARY KEY,
    slug          TEXT NOT NULL UNIQUE,
    name          TEXT NOT NULL,
    description   TEXT,
    icon          TEXT,
    color         TEXT,
    board_slug    TEXT,
    primary_path  TEXT,
    created_at    INTEGER NOT NULL,
    archived      INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS project_folders (
    project_id  TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    path        TEXT NOT NULL,
    label       TEXT,
    is_primary  INTEGER NOT NULL DEFAULT 0,
    added_at    INTEGER NOT NULL,
    PRIMARY KEY (project_id, path)
);

CREATE INDEX IF NOT EXISTS idx_project_folders_path
    ON project_folders(path);

CREATE TABLE IF NOT EXISTS project_meta (
    key    TEXT PRIMARY KEY,
    value  TEXT
);

-- Git repos found by scanning the filesystem (desktop "repo-first" discovery).
-- Cached here so the overview is instant after the first scan instead of
-- re-walking the disk every time the Projects view opens.
CREATE TABLE IF NOT EXISTS discovered_repos (
    root          TEXT PRIMARY KEY,
    label         TEXT,
    last_seen     INTEGER NOT NULL
);
z^[a-z0-9][a-z0-9\-_]{0,63}$z[^a-z0-9._-]+zset[str]Ú_INITIALIZED_PATHSÚ
board_slugÚprimary_pathÚiconÚcolorÚdescriptionÚ	active_idÚrepo_discovery_policyc               ó    € V ^8„  d   QhRRRR/# )r   ÚnameÚstrr   r   )r   s   "r   r   r   T   s   € ÷ +ñ +�3ð +˜3ñ +r   c                óò   € \         P                  ! RR\        T ;'       g    R4      P                  4       P	                  4       4      P                  R4      pVR,          P                  R4      ;'       g    R# )z8Derive a slug candidate from a human name (best-effort).z
[^a-z0-9]+Ú-Ú ú-_:Né@   NÚproject)ÚreÚsubr#   ÚstripÚlower)r"   Úss   & r   Ú_slugifyr/   T   sV   € ä
�Šˆ}˜c¤3 t§z z¨r£?×#8Ñ#8Ó#:×#@Ñ#@Ó#BÓC×IÑIÈ$ÓO€AØˆS�6�<‰<˜Ó×*Ð* Ð*r   c               ó    € V ^8„  d   QhRRRR/# )r   ÚslugúOptional[str]r   r   )r   s   "r   r   r   Z   s   € ÷ ñ ˜ð ¨=ñ r   c                óÊ   € V e(   \        V 4      P                  4       P                  4       MRpV'       g   R# \        P	                  V4      '       g   \        RV : R24      hV# )z>Lowercase + strip a slug; validate; return ``None`` for empty.Nr&   zinvalid project slug zc: must be 1-64 chars, lowercase alphanumerics / hyphens / underscores, not starting with '-' or '_')r#   r,   r-   Ú_SLUG_REÚmatchÚ
ValueError)r1   r.   s   & r   Únormalize_slugr7   Z   s\   € à%)Ò%5ŒˆD‹	�‰Ó×ÑÔ!¸2€AßÙÜ�>‰>˜!×ÒÜØ# D¡8ð ,ð ó
ð 	
ð
 €Hr   c               ó   € V ^8„  d   QhRR/# )r   r   Úintr   )r   s   "r   r   r   h   s   € ÷ ñ ˆcñ r   c                 ó>   € \        \        P                  ! 4       4      # ©N)r9   Útimer   r   r   Ú_nowr=   h   s   € ÜŒt�yŠy‹{ÓÐr   c               ó    € V ^8„  d   QhRRRR/# ©r   Úpathr#   r   r   )r   s   "r   r   r   l   s   € ÷  ñ  ˜#ð   #ñ  r   c                óÞ   € \         P                  P                  \         P                  P                  \	        V 4      P                  4       4      4      pVP                  R4      ;'       g    T# )zEAbsolute, user-expanded, separator-normalized path (no trailing sep).ú/\)Úosr@   ÚabspathÚ
expanduserr#   r,   Úrstrip)r@   Úps   & r   Ú_normalize_pathrH   l   sD   € ä
�‰�‰œŸ™×*Ñ*¬3¨t«9¯?©?Ó+<Ó=Ó>€AØ�8‰8�E‹?×Ð˜aÐr   c               ó    € V ^8„  d   QhRRRR/# )r   Údb_pathúOptional[Path]r   úsqlite3.Connectionr   )r   s   "r   r   r   r   s   € ÷ \ñ \�^ð \Ð/Añ \r   c                ó†   a€ V e   T M	\        4       p\        VP                  4       4      oR V3R llp\        VRRVR7      # )züOpen (and initialize if needed) the per-profile projects DB.

WAL with DELETE fallback for network filesystems (``hermes_state`` helper). Schema init is
idempotent (``CREATE TABLE IF NOT EXISTS`` + additive migrations) and cached per-path per-process.
c               ó    € V ^8„  d   QhRRRR/# )r   ÚconnrL   r   ÚNoner   )r   s   "r   r   Úconnect.<locals>.__annotate__{   s   € ÷ )ñ )Ð,ð )°ñ )r   c                ó  <€ S\         9   d   R # V P                  \        4       V P                  R4       Uu0 uF  qR,          kK  	  pp\         F  pW29  g   K  \        V RW3 R24       K  	  \         P                  S4       R # u upi )NzPRAGMA table_info(projects)r"   Úprojectsz TEXT)r   ÚexecutescriptÚ
SCHEMA_SQLÚexecuteÚ_OPTIONAL_PROJECT_COLUMNSÚ_add_column_if_missingÚadd)rO   ÚrowÚcolsÚcolÚresolveds   &   €r   Ú_initializeÚconnect.<locals>._initialize{   sx   ø€ ØÔ)Ô)ÙØ×Ñœ:Ô&Ø'+§|¡|Ð4QÔ'RÓSÑ'R �F—�Ñ'RˆÐSß,ˆCØŽÜ& t¨Z¸ÀÀU¸mÖLñ -ô 	×Ñ˜xÖ(ùò	 Ts   ·Br   T)Údb_labelÚforeign_keysÚ
initialize)r   r#   Úresolver
   )rJ   r@   r^   r]   s   &  @r   Úconnectrd   r   sB   ø€ ð Ò)‰7Ô/?Ó/A€DÜ�4—<‘<“>Ó"€H÷)ð )ô �4 -¸dÈ{Ô[Ð[r   c               ó   € V ^8„  d   QhRR/# )r   rJ   rK   r   )r   s   "r   r   r   ‰   s   € ÷ ñ ˜^ñ r   c           	   #  ót  "  € \        V R7      p Vx € \        P                  ! \        4      ;_uu_ 4        VP	                  4        RRR4       R#   + '       g   i     R# ; i  \        P                  ! \        4      ;_uu_ 4        TP	                  4        RRR4       i   + '       g   i     i ; i; i5i)z®Open a projects DB connection and close it on exit (sqlite3's own context manager only
commits/rollbacks, so long-lived gateway/dashboard processes would leak fds otherwise).)rJ   N)rd   Ú
contextlibÚsuppressÚ	ExceptionÚclose)rJ   rO   s   & r   Úconnect_closingrk   ˆ   sk   é € ô ˜7Ô#€DðØŠ
ä× Ò ¤×+Õ+Ø�J‰JŒL÷ ,×+×+Ò+ûŒZ× Ò ¤×+Õ+Ø�J‰JŒL÷ ,×+×+Ð+ÿsJ   ‚B8�A% ”"B8¶AÁ
B8ÁA"	Á	B8Á%#B5ÂB"	Â	B5Â"B2Â-B5Â5B8c                  óX   € ] tR t^”t$ R]R&   RtR]R&   RtR]R&   ^ tR	]R
&   R R ltRt	R# )ÚProjectFolderr#   r@   Nr2   ÚlabelFÚboolÚ
is_primaryr9   Úadded_atc               ó   € V ^8„  d   QhRR/# ©r   r   Údictr   )r   s   "r   r   ÚProjectFolder.__annotate__›   s   € ÷ xñ x˜ñ xr   c                	óx   € R V P                   RV P                  R\        V P                  4      RV P                  /# )r@   rn   rp   rq   )r@   rn   ro   rp   rq   )Úselfs   &r   Úto_dictÚProjectFolder.to_dict›   s2   € Ø˜Ÿ	™	 7¨D¯J©J¸ÄdÈ4Ï?É?ÓF[Ð]gÐim×ivÑivÐwÐwr   r   )
Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__annotations__rn   rp   rq   rx   Ú__static_attributes__r   r   r   rm   rm   ”   s1   ‡ à
ƒIØ€Eˆ=ÓØ€J�ÓØ€HˆcÓ÷xñ xr   rm   c                  ó¼   € ] tR t^Ÿt$ R]R&   R]R&   R]R&   R]R&   RtR]R	&   RtR]R
&   RtR]R&   RtR]R&   Rt	R]R&   Rt
R]R&   ]! ]R7      tR]R&   R R ltRtR# )ÚProjectr#   Úidr1   r"   r9   Ú
created_atNr2   r   r   r   r   r   Fro   Úarchived)Údefault_factoryzlist[ProjectFolder]Úfoldersc               ó   € V ^8„  d   QhRR/# rs   r   )r   s   "r   r   ÚProject.__annotate__­   s   € ÷ Eñ E˜ñ Er   c                	óú   € . RO\         O Uu/ uF  q\        W4      bK  	  pp/ VCR\        V P                  4      RV P                  RV P
                   Uu. uF  q3P                  4       NK  	  up/C# u upi u upi )r‚   r„   rƒ   r†   )r‚   r1   r"   )Ú_OPTIONAL_ROW_FIELDSÚgetattrro   r„   rƒ   r†   rx   )rw   ÚkÚdÚfs   &   r   rx   ÚProject.to_dict­   sŸ   € Ú*WÔBVÑ*WÓXÑ*W Q”˜Ó Ò Ñ*WˆÐXð E�!ð  E�Z¤ d§m¡mÓ!4°lÀDÇOÁOÐU^Ðvz÷  wCò  wCó  aDñ  wCÐqr×ajÑajÖalñ  wCñ  aDñ  Eð  	Eùò Yùò aDs   �A3ÁA8r   )rz   r{   r|   r}   r~   r   r   r   r   r   r„   r   Úlistr†   rx   r   r   r   r   r�   r�   Ÿ   sn   ‡ àƒGØ
ƒIØ
ƒIØƒOØ!%€K�Ó%Ø€Dˆ-ÓØ€Eˆ=ÓØ $€J�Ó$Ø"&€L�-Ó&Ø€HˆdÓÙ#(¸Ô#>€GÐ Ó>÷Eñ Er   r�   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rO   rL   rZ   zsqlite3.Rowr   r�   r   )r   s   "r   r   r   ²   s"   € ÷ ñ Ð*ð °ð Àñ r   c                óæ  € VP                  4       pV P                  RVR,          34      P                  4       p\        RRVR,          RVR,          RVR,          RVR,          RRV9   d   \	        VR,          4      MRRV Uu. uF6  p\        VR	,          VR
,          \	        VR,          4      VR,          4      NK8  	  up/\         Uu/ uF  qUV9   g   K  WQV,          bK  	  upB # u upi u upi )z9Materialize a ``projects`` row together with its folders.zySELECT path, label, is_primary, added_at FROM project_folders WHERE project_id = ? ORDER BY is_primary DESC, added_at ASCr‚   r1   r"   rƒ   r„   Fr†   r@   rn   rp   rq   r   )ÚkeysrV   Úfetchallr�   ro   rm   rŠ   )rO   rZ   r“   r†   ÚrrŽ   s   &&    r   Ú_load_projectr–   ²   sþ   € à�8‰8‹:€DØ�l‰lð 	DØ	ˆT�ˆó÷ �hƒjð ô ñ Øˆt�9ðØ˜v�;ðØ-0°­[ðØEHÈÕEVðà*4¸Ô*<”�c˜*•oÔ&À%ðñ fmÓmÑelÐ`a”˜q �y¨!¨G­*´d¸1¸\½?Ó6KÈQÈzÍ]Ö[ÑelÑmðõ 3Ó
@Ñ2˜¸4±iŒ9ˆ1�!�fŠ9Ñ2Ñ
@ñ	ð ùò nùÚ
@s   Â<C) Ã
C.ÃC.c               ó$   € V ^8„  d   QhRRRRRR/# )r   rO   rL   Ú	candidater#   r   r   )r   s   "r   r   r   Á   s"   € ÷ ñ Ð)ð °cð ¸cñ r   c                óÊ   € ^Tr2V P                  RV34      P                  4       e=   V^,          pVR^@\        RV 24      ,
           P                  R4      RV 2,           pK_  V# )z=Return ``candidate`` or ``candidate-2``, ``-3`` ... if taken.z%SELECT 1 FROM projects WHERE slug = ?Nr%   r'   )rV   ÚfetchoneÚlenrF   )rO   r˜   Únr1   s   &&  r   Ú_unique_slugr�   Á   sf   € à�€tØ
�,‰,Ð>ÀÀÓ
H×
QÑ
QÓ
SÒ
_Ø	ˆQ�ˆØÐ,˜2¤ a¨ s G£Õ,Ð-×4Ñ4°TÓ:¸qÀÀ¸WÕDŠØ€Kr   c               ó    € V ^8„  d   QhRRRR/# r?   r   )r   s   "r   r   r   Ê   s   € ÷ 3ñ 3˜Cð 3 Cñ 3r   c                óR   € \         P                  P                  \        V 4      4      # )zGComparison key for primary-path dedup (absolute + case/sep-normalized).)rC   r@   ÚnormcaserH   )r@   s   &r   Ú_primary_path_keyr¡   Ê   s   € ä�7‰7×ÑœO¨DÓ1Ó2Ð2r   Úinclude_archivedFc               ó(   € V ^8„  d   QhRRRRRRRR/# ©	r   rO   rL   r@   r#   r¢   ro   r   úOptional[Project]r   )r   s   "r   r   r   Ï   s+   € ÷ 
ñ 
Ð1ð 
¸ð 
ÐSWð 
Ðduñ 
r   c               óR  € \        V4      pV'       d   \        WR7      MR F�  pVP                  ;'       gM    \        R VP                   4       VP                  '       d   VP                  ^ ,          P
                  MR4      pV'       g   Km  \        V4      V8X  g   K  Vu # 	  R# )z¦The first (oldest) project whose primary path matches ``path`` (separator/case normalized so
equivalent Windows spellings don't slip past the dedup check), else None.)r¢   c              3  ó`   "  € T F$  qP                   '       g   K  VP                  x € K&  	  R # 5ir;   )rp   r@   )Ú.0rŽ   s   & r   Ú	<genexpr>Ú'find_by_primary_path.<locals>.<genexpr>Õ   s   é € Ð:™\˜¯\­\ŒVˆQ�VŽV›\ùs   ‚.š.Nr   )r¡   Úlist_projectsr   Únextr†   r@   )rO   r@   r¢   ÚkeyÚprojÚprimarys   &&$   r   Úfind_by_primary_pathr°   Ï   s‹   € ô ˜DÓ
!€CßJM”˜dÕFÐSUÒUˆØ×#Ñ#÷ 
ð 
¤tÙ:˜TŸ\š\Ó:ÐTX×T`×T`ÐT`¸D¿L¹LÈ½O×<PÒ<PÐfjó(
ˆ÷ ‰7Ô(¨Ó1°SÖ8ØŠKñ Vñ r   r1   r†   Úallow_duplicate_pathc               óD   € V ^8„  d   QhRRRRRRRRR	RR
RRRRRRRRRRR/# )r   rO   rL   r"   r#   r1   r2   r†   zOptional[Iterable[str]]r   r   r   r   r   r±   ro   r   r   )r   s   "r   r   r   Ü   sn   € ÷ $ñ $Ø
ð$Ø'*ð$Ø2?ð$ØQhð$àð$à5Bð$àQ^ð$ð ð$ð .;ð$ð Z^ð$ð 	ñ	$r   c       	        óÊ  € \        T;'       g    R4      P                  4       pV'       g   \        R4      hV'       d   \        V4      M
\	        V4      p
R\
        P                  ! ^4      ,           p\        4       p\        \        P                  R \        \        T;'       g    . 4       4       4      4      pV'       d   \        V4      MRpV'       d   Wí9  d   VP                  ^ V4       Vf   V'       d
   V^ ,          pV'       d   V	'       g   \        W4      MRpVe'   \        RVP                   RVP                    R24      h\#        V 4      ;_uu_ 4        T P%                  R	T\'        W
4      YYgV'       d   \        V4      MRWì3	4       T P)                  R
V Uu. uF  pTTRVV8X  d   ^M^ V3NK  	  up4       RRR4       V# u upi   + '       g   i     T# ; i)zÄCreate a project and return its id. ``folders`` are normalized to absolute paths; ``primary_path``
is added to the folder set (if absent) and marked primary, else the first folder becomes primary.r&   úproject name must not be emptyÚp_c              3  ó8   "  € T F  q'       g   K  Vx € K  	  R # 5ir;   r   )r¨   rG   s   & r   r©   Ú!create_project.<locals>.<genexpr>é   s   é € Ð%ZÑ1T¨AÓXY§a¢aÓ1Tùs   ‚	�
Nz#folder already belongs to project 'z' (z/); switch to it instead of creating a duplicatez–INSERT INTO projects (id, slug, name, description, icon, color, board_slug,  primary_path, created_at, archived) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)zbINSERT INTO project_folders (project_id, path, label, is_primary, added_at) VALUES (?, ?, ?, ?, ?))r#   r,   r6   r7   r/   ÚsecretsÚ	token_hexr=   r�   rt   ÚfromkeysÚmaprH   Úinsertr°   r1   r‚   r   rV   r�   Úexecutemany)rO   r"   r1   r†   r   r   r   r   r   r±   Úslug_candidateÚpidÚnowÚfolder_pathsr¯   Úexistingr@   s   &$$$$$$$$$       r   Úcreate_projectrÃ   Ü   s“  € ô ˆt�zˆz�r‹?× Ñ Ó"€DßÜÐ9Ó:Ð:ß-1”^ DÔ)´xÀ³~€NØ
”×"Ò" 1Ó%Õ
%€CÜ
‹&€CÜœŸ™Ñ%Z´´_ÀgÇmÀmÐQSÔ1TÓ%ZÓZÓ[€Lß/;Œo˜lÔ+À€Gß�7Ô.Ø×Ñ˜A˜wÔ'Ø‚Ÿ<Ø˜q•/ˆß6=×FZÔ# DÔ2Ð`d€HØÒÜØ1°(·-±-°ÀÀHÇKÁKÀ=ð Q;ð ;ó
ð 	
ô 
�4��Ø�‰ð4à”,˜tÓ4°dÈß+5Œ^˜JÔ'¸4ÀðOô	
ð 	×ÑØpÙMYÓZÉ\ÀTˆc�4˜ D¨G¤O™q¸¸CÓ@É\ÑZô	
÷ 
ð €Jùò [÷ 
Žð €Jús   ÅAGÆ"GÆ;GÇGÇG"	c               ó$   € V ^8„  d   QhRRRRRR/# )r   rO   rL   r¢   ro   r   zlist[Project]r   )r   s   "r   r   r     s(   € ÷ Jñ JÐ*ð JÀð JÐR_ñ Jr   c               ó°   € R V'       d   RMR,           R,           pV P                  V4      P                  4        Uu. uF  p\        W4      NK  	  up# u upi )zSELECT * FROM projectsr&   z WHERE archived = 0z ORDER BY created_at ASC)rV   r”   r–   )rO   r¢   Úsqlr•   s   &$  r   r«   r«     sI   € Ø
"×,<¡bÐBWÕ
XÐ[uÕ
u€CØ,0¯L©L¸Ó,=×,FÑ,FÔ,HÓIÑ,H qŒM˜$Ö"Ñ,HÑIÐIùÒIs   ¼Ac               ó$   € V ^8„  d   QhRRRRRR/# )r   rO   rL   Ú
id_or_slugr#   r   r¥   r   )r   s   "r   r   r     s#   € ÷ =ñ =Ð(ð =°cð =Ð>Oñ =r   c                óæ   € V P                  RV34      P                  4       ;'       g9    V P                  R\        V4      P                  4       34      P                  4       pVf   R# \	        W4      # )z,Look up a project by id first, then by slug.z#SELECT * FROM projects WHERE id = ?z%SELECT * FROM projects WHERE slug = ?N)rV   rš   r#   r-   r–   )rO   rÈ   rZ   s   && r   Úget_projectrÊ     sm   € ð 	�‰Ð:¸Z¸MÓJ×SÑSÓU÷ 	hð 	hØ�<‰<Ð?Ä#ÀjÃ/×BWÑBWÓBYÐA[Ó\×eÑeÓgð ð ’;ˆ4Ð<¤M°$Ó$<Ð<r   r"   c               ó8   € V ^8„  d   QhRRRRRRRRRRR	RR
RRR/# )r   rO   rL   Ú
project_idr#   r"   r2   r   r   r   r   r   ro   r   )r   s   "r   r   r     sX   € ÷ zñ zØ
ðzØ*-ðzØ8EðzØ[hðzà
ðzà'4ðzàIVðzð 
ñzr   c          	     ó
  € Ve-   \        V4      P                  4       pV'       g   \        R4      hVe$   VP                  4       '       d   \        V4      MRpRW"3RW33RYD;'       g    R3RYU;'       g    R3RYf;'       g    R33 UUU	u. uF  w  rxp	Vf   K  WxV	3NK  	  p
ppp	V
'       g   R	# R
P	                  R V
 4       4      p\        T RV R2V
 Uu. uF  qÌ^,          NK  	  upV.,           4      ^ 8„  # u up	ppi u upi )u¹   Patch top-level project fields; only provided (non-None) fields change. ``icon``, ``color`` and
``board_slug`` take ``""`` to clear (store NULL) â€” ``None`` leaves the field untouched.Nr´   r&   r"   r   r   r   r   Fz, c              3  ó2   "  € T F  w  p q! R 2x € K  	  R# 5i)z = ?Nr   )r¨   r\   Ú_s   &  r   r©   Ú!update_project.<locals>.<genexpr>&  s   é € Ð:±6¡i c¨1¨a�u˜D•\³6ùs   ‚zUPDATE projects SET z WHERE id = ?)r#   r,   r6   r7   ÚjoinÚ_execute_rowcount)rO   rÌ   r"   r   r   r   r   r\   ÚgivenÚstoredÚfieldsÚsetsrŽ   s   &&$$$$$      r   Úupdate_projectr×     s*  € ð ÒÜ�4‹y�‰Ó ˆßÜÐ=Ó>Ð>ØÒØ3=×3CÑ3C×3EÒ3E”^ JÔ/È2ˆ
ð �TÐ  =°+Ð"KÈfÐVZ×\hÐ\hÐdhÐMiØ�eŸ]˜] dÐ+¨l¸J×HZÐHZÐVZÐ-[ñ8
õñ8
Ñ!3 ¨Vð ô 	ˆ�VÓñ 8
ð ò ÷ ÙØ�9‰9Ñ:±6Ó:Ó:€DÜ˜TÐ%9¸$¸¸}Ð#MÑ^dÓOeÑ^dÐYZÐRS×PTÐPTÑ^dÑOeÐisÐhtÕOtÓuÐxyÑyÐyùôùò Pfs   Â	C9ÂC9ÃD c               ó$   € V ^8„  d   QhRRRRRR/# )r   rO   rL   rÆ   r#   r   r9   r   )r   s   "r   r   r   *  s"   € ÷ ñ Ð.ð °Sð ÀSñ r   c                ó°   € \        V 4      ;_uu_ 4        V P                  W4      pRRR4       VP                  #   + '       g   i     XP                  # ; i)zIRun one write statement in its own txn and return the affected row count.N)r   rV   Úrowcount)rO   rÆ   ÚparamsÚcurs   &&& r   rÒ   rÒ   *  s:   € ä	�4��Ø�l‰l˜3Ó'ˆ÷ 
à�<‰<Ð÷ 
Žà�<‰<Ðús	   ”:ºA	rn   rp   c               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r   rO   rL   rÌ   r#   r@   rn   r2   rp   ro   r   r   )r   s   "r   r   r   1  s=   € ÷ ñ Ð'ð °Sð Àð È}ð Ðquð ð  CFñ r   c          
     ó°  € \        V4      pV'       g   \        R4      h\        W4      f   \        RV 24      h\        V 4      ;_uu_ 4        V P	                  RWV\        4       34       Ve   V P	                  RW1V34       V'       g$   V P	                  RV34      P                  4       f   \        WV4       RRR4       V#   + '       g   i     T# ; i)z7Add a folder to a project. Returns the normalized path.zfolder path must not be emptyNzno such project: zlINSERT OR IGNORE INTO project_folders (project_id, path, label, is_primary, added_at) VALUES (?, ?, ?, 0, ?)zFUPDATE project_folders SET label = ? WHERE project_id = ? AND path = ?zESELECT 1 FROM project_folders WHERE project_id = ? AND is_primary = 1)rH   r6   rÊ   r   rV   r=   rš   Ú_set_primary_locked)rO   rÌ   r@   rn   rp   Únorms   &&&$$ r   Ú
add_folderrá   1  sÅ   € ä˜4Ó €DßÜÐ8Ó9Ð9Ü�4Ó$Ò,ÜÐ,¨Z¨LÐ9Ó:Ð:Ü	�4��Ø�‰ØzØ˜u¤d£fÐ-ô	
ð ÒØ�L‰LÐaÐdiÐw{Ðc|Ô}ç˜Ÿ™ØSÐV`ÐUbó
ç
‰(‹*òô   °$Ô7÷ 
ð €K÷ 
Žð €Kús   Á=CÂ/CÃC	c               ó(   € V ^8„  d   QhRRRRRRRR/# ©r   rO   rL   rÌ   r#   r@   r   ro   r   )r   s   "r   r   r   G  s)   € ÷ ñ Ð*ð ¸ð À3ð È4ñ r   c                óà  € \        V4      p\        V 4      ;_uu_ 4        V P                  RW34      P                  4       pV P                  RW34      pVeo   VR,          '       d`   V P                  RV34      P                  4       pV'       d$   VR,          '       d   \	        WVR,          4       MV P                  RV34       RRR4       XP
                  ^ 8„  #   + '       g   i     L; i)zCRemove a folder from a project. Repoints primary if it was primary.zHSELECT is_primary FROM project_folders WHERE project_id = ? AND path = ?z=DELETE FROM project_folders WHERE project_id = ? AND path = ?Nrp   zSSELECT path FROM project_folders WHERE project_id = ? ORDER BY added_at ASC LIMIT 1r@   z4UPDATE projects SET primary_path = NULL WHERE id = ?)rH   r   rV   rš   rß   rÚ   )rO   rÌ   r@   rà   Úwas_primaryrÜ   Únxts   &&&    r   Úremove_folderrç   G  sÊ   € ä˜4Ó €DÜ	�4��Ø—l‘lØVÐYcÐXjó
ç
‰(‹*ð 	ð �l‰lÐZÐ]gÐ\nÓoˆØÒ" {°<×'@Ô'@Ø—,‘,ØeÐhrÐgtóç‰h‹jð ÷ �s˜6—{”{Ü# D°c¸&µkÕBà—‘ÐSÐV`ÐUbÔc÷ 
ð �<‰<˜!ÑÐ÷ 
�ús   ŸACÁ'(CÂCÂ'CÃC-	c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   rO   rL   rÌ   r#   r@   r   rP   r   )r   s   "r   r   r   Z  s0   € ÷ Zñ ZÐ0ð Z¸cð ZÈð ZÐQUñ Zr   c                óx   € V P                  RV34       V P                  RW34       V P                  RW!34       R# )z:Set the primary folder (caller already holds a write txn).z>UPDATE project_folders SET is_primary = 0 WHERE project_id = ?zKUPDATE project_folders SET is_primary = 1 WHERE project_id = ? AND path = ?z1UPDATE projects SET primary_path = ? WHERE id = ?N©rV   )rO   rÌ   r@   s   &&&r   rß   rß   Z  s:   € à‡L�LÐQÐT^ÐS`ÔaØ‡L�LÐ^ÐakÐ`rÔsØ‡L�LÐDÀtÐFXÖYr   c               ó(   € V ^8„  d   QhRRRRRRRR/# rã   r   )r   s   "r   r   r   a  s)   € ÷ ñ Ð(ð °cð Àð Èñ r   c                óð   € \        V4      p\        V 4      ;_uu_ 4        V P                  R W34      P                  4       f    RRR4       R# \	        WV4       RRR4       R#   + '       g   i     R# ; i)z?SELECT 1 FROM project_folders WHERE project_id = ? AND path = ?NFT)rH   r   rV   rš   rß   )rO   rÌ   r@   rà   s   &&& r   Úset_primaryrí   a  sd   € Ü˜4Ó €DÜ	�4��Ø�<‰<ÐYÐ\fÐ[mÓn×wÑwÓyò  BØ÷ 
‰ô 	˜D¨dÔ3÷ 
ñ ÷	 
Žñ ús   Ÿ%A$ÁA$Á$A5	c               ó$   € V ^8„  d   QhRRRRRR/# ©r   rO   rL   rÌ   r#   r   ro   r   )r   s   "r   r   r   j  ó'   € ÷ gñ gÐ,ð g¸#ð gÀ$ñ gr   c                ó$   € \        V R V34      ^ 8„  # )z-UPDATE projects SET archived = 1 WHERE id = ?©rÒ   ©rO   rÌ   s   &&r   Úarchive_projectrô   j  ó   € Ü˜TÐ#RÐU_ÐTaÓbÐefÑfÐfr   c               ó$   € V ^8„  d   QhRRRRRR/# rï   r   )r   s   "r   r   r   n  rð   r   c                ó$   € \        V R V34      ^ 8„  # )z-UPDATE projects SET archived = 0 WHERE id = ?rò   ró   s   &&r   Úrestore_projectrø   n  rõ   r   c               ó$   € V ^8„  d   QhRRRRRR/# rï   r   )r   s   "r   r   r   r  s'   € ÷ [ñ [Ð+ð [¸ð [Àñ [r   c                ó$   € \        V RV34      ^ 8„  # )z0Hard-delete a project and its folders (cascade).z!DELETE FROM projects WHERE id = ?rò   ró   s   &&r   Údelete_projectrû   r  s   € ä˜TÐ#FÈÈÓVÐYZÑZÐZr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   rO   rL   r­   r#   Úvaluer   rP   r   )r   s   "r   r   r   y  s)   € ÷ ñ Ð0ð °sð À3ð È4ñ r   c                ó,   € V P                  RW34       R# )z=Upsert a project_meta row (caller already holds a write txn).ziINSERT INTO project_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.valueNrê   )rO   r­   rý   s   &&&r   Ú_upsert_meta_lockedrÿ   y  s   € à‡L�LØsØ	ˆör   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rO   rL   r­   r#   r   r2   r   )r   s   "r   r   r   �  s"   € ÷ )ñ )Ð&ð )¨Sð )°]ñ )r   c                ój   € V P                  R V34      P                  4       pV'       d
   VR,          # R# )z,SELECT value FROM project_meta WHERE key = ?rý   N)rV   rš   )rO   r­   rZ   s   && r   Ú	_get_metar  �  s/   € Ø
�,‰,ÐEÈÀvÓ
N×
WÑ
WÓ
Y€Cßˆ3ˆw�<Ð( DÐ(r   c               ó$   € V ^8„  d   QhRRRRRR/# )r   rO   rL   rÌ   r2   r   rP   r   )r   s   "r   r   r   †  s'   € ÷ Dñ DÐ'ð D°]ð DÀtñ Dr   c                óÀ   € \        V 4      ;_uu_ 4        Vf   V P                  R\        34       M\        V \        V4       RRR4       R#   + '       g   i     R# ; i)z9Set (or clear, when ``None``) the active project pointer.Nz&DELETE FROM project_meta WHERE key = ?)r   rV   Ú_ACTIVE_META_KEYrÿ   ró   s   &&r   Ú
set_activer  †  s=   € ä	�4��ØÒØ�L‰LÐAÔDTÐCVÕWä Ô&6¸
ÔC÷	 
��Šús   ”.AÁA	c               ó    € V ^8„  d   QhRRRR/# ©r   rO   rL   r   r2   r   )r   s   "r   r   r   �  s   € ÷ -ñ -Ð*ð -¨}ñ -r   c                ó"   € \        V \        4      # r;   )r  r  ©rO   s   &r   Úget_active_idr  �  s   € Ü�TÔ+Ó,Ð,r   c               ó    € V ^8„  d   QhRRRR/# r  r   )r   s   "r   r   r   “  s   € ÷ 7ñ 7Ð#5ð 7¸-ñ 7r   c                ó"   € \        V \        4      # r;   )r  Ú_DISCOVERY_POLICY_META_KEYr
  s   &r   Úget_discovery_policy_keyr  “  s   € Ü�TÔ5Ó6Ð6r   c               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   rO   rL   Úclearro   Ú
policy_keyr2   r   rP   r   )r   s   "r   r   r   —  s0   € ÷ Jñ JÐ0ð J¸ð JÈ=ð JÐ]añ Jr   c                óf   € V'       d   V P                  R4       Ve   \        V \        V4       R# R# )zaOptionally wipe the scan cache, then record the policy key when given (caller holds a write txn).úDELETE FROM discovered_reposN)rV   rÿ   r  )rO   r  r  s   &&&r   Ú_clear_repos_lockedr  —  s*   € çØ�‰Ð3Ô4ØÒÜ˜DÔ"<¸jÖIñ r   Úpreserve_unversionedc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   rO   rL   r  r#   r  ro   r   r   )r   s   "r   r   r   Ÿ  s+   € ÷ 	ñ 	Ð,>ð 	ÈCð 	Ðjnð 	Ð{ñ 	r   c               óÊ   € \        V 4      pW18X  d   R# VRJ;'       g    V'       * p\        V 4      ;_uu_ 4        \        WV4       RRR4       V#   + '       g   i     T# ; i)z¬Clear cached scan rows when their discovery policy changes; pre-policy rows are retained only
for the backward-compatible default policy. Returns whether rows were cleared.FN)r  r   r  )rO   r  r  ÚcurrentÚcleareds   &&$  r   Ú!reconcile_discovered_repos_policyr  Ÿ  sW   € ô ' tÓ,€GØÔÙØ˜TÐ!×=Ð=Ð)=Ô%=€GÜ	�4��Ü˜D¨:Ô6÷ 
à€N÷ 
Žà€Nús   ºAÁA"	r  c               ó$   € V ^8„  d   QhRRRRRR/# )r   rO   rL   r  r2   r   rP   r   )r   s   "r   r   r   «  s#   € ÷ 4ñ 4Ð!3ð 4ÀMð 4Ð]añ 4r   c               ó€   € \        V 4      ;_uu_ 4        \        V R V4       RRR4       R#   + '       g   i     R# ; i)TN)r   r  )rO   r  s   &$r   Úclear_discovered_reposr  «  s#   € Ü	�4��Ü˜D $¨
Ô3÷ 
��Šús   ”,¬=	Úreplacec          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   rO   rL   Úreposz#Iterable[tuple[str, Optional[str]]]r  ro   r  r2   r   r9   r   )r   s   "r   r   r   °  s6   € ÷ ñ Ø
ðØ%HðØVZðàðð 	ñr   c               óÈ  € \        4       pR V 4        UUu. uFD  w  rVV'       g   K  YV;'       g*    \        P                  P                  V4      ;'       g    TV3NKF  	  ppp\	        V 4      ;_uu_ 4        V'       d   V P                  R4       V'       d   V P                  RV4       \        V RV4       RRR4       \        V4      # u uppi   + '       g   i     L!; i)zçPersist scanned ``(root, label)`` repo roots (normalized; label falls back to basename) and
return the row count. ``replace`` = authoritative fresh scan: stale rows are deleted first so old
eval/worktree noise doesn't live forever.c              3  ó@   "  € T F  w  r\        V4      V3x € K  	  R # 5ir;   )rH   )r¨   Úrootrn   s   &  r   r©   Ú*record_discovered_repos.<locals>.<genexpr>º  s   é € ÐTÉe¹{¸tœ_¨TÓ2°EÕ:Ëeùs   ‚r  z�INSERT INTO discovered_repos (root, label, last_seen) VALUES (?, ?, ?) ON CONFLICT(root) DO UPDATE SET label = excluded.label, last_seen = excluded.last_seenFN)	r=   rC   r@   Úbasenamer   rV   r½   r  r›   )rO   r!  r  r  rÀ   rà   rn   Úrowss   &&$$    r   Úrecord_discovered_reposr(  °  s¿   € ô ‹&€Cñ UÉeÔTôáT‰KˆDÔX\ô 	=ˆ×6Ð6œŸ™×(Ñ(¨Ó.×6Ð6°$¸Ó<ÙTð 	ñ ô 
�4��ßØ�L‰LÐ7Ô8ßØ×Ñðiàôô
 	˜D %¨Ô4÷ 
ô ˆt‹9Ðùó÷ 
�ús'   —C¨C±&CÁ	CÁ7!CÂCÃC!	c               ó    € V ^8„  d   QhRRRR/# )r   rO   rL   r   z
list[dict]r   )r   s   "r   r   r   É  s   € ÷ Eñ EÐ 2ð E°zñ Er   c                ó|   € V P                  R4      P                  4        Uu. uF  p\        V4      NK  	  up# u upi )z;All cached discovered repo roots, most-recently-seen first.zKSELECT root, label, last_seen FROM discovered_repos ORDER BY last_seen DESC)rV   r”   rt   )rO   r•   s   & r   Úlist_discovered_reposr+  É  sN   € à!Ÿ\™\Ð*wÓx÷  Bñ  Bô  Dó  Eñ  D˜ŒD�ŽGñ  Dñ  Eð  Eùò  Es   ¢9c               ó(   € V ^8„  d   QhRRRRRRRR/# r¤   r   )r   s   "r   r   r   Î  s0   € ÷ eñ eÐ-ð e°Sð eÈtð eÐ`qñ er   c               óŒ  a€ \        T;'       g    R4      P                  4       '       g   R# \        V4      oRpV'       g
   VR,          pR V3R llpV P                  V4      P	                  4        Uu. uF  qT! VR,          4      '       g   K  VNK  	  ppV'       d    \        V \        VR R	7      R
,          4      # R# u upi )zœReturn the project owning ``path``: a folder owns it when equal or an ancestor, and the longest
folder wins so nested projects resolve to the innermost one.r&   NznSELECT pf.project_id AS pid, pf.path AS folder FROM project_folders pf JOIN projects p ON p.id = pf.project_idz WHERE p.archived = 0c               ó    € V ^8„  d   QhRRRR/# )r   Úfolderr#   r   ro   r   )r   s   "r   r   Ú&project_for_path.<locals>.__annotate__Ø  s   € ÷ eñ e�Sð e˜Tñ er   c                óÌ   <€ V P                  R 4      pSV 8H  ;'       gG    SP                  V\        P                  ,           4      ;'       g    SP                  VR,           4      # )rB   Ú/)rF   Ú
startswithrC   Úsep)r/  ÚstemÚtargets   & €r   ÚownsÚproject_for_path.<locals>.ownsØ  sQ   ø€ Ø�}‰}˜UÓ#ˆØ˜Ñ×dÐd 6×#4Ñ#4°T¼B¿F¹Fµ]Ó#C×dÐdÀv×GXÑGXÐY]Ð`cÕYcÓGdÐdr   r/  c                ó&   € \        V R ,          4      # )r/  )r›   )r•   s   &r   Ú<lambda>Ú"project_for_path.<locals>.<lambda>Ý  s   € ´s¸1¸X½;Ô7Gr   )r­   r¿   )r#   r,   rH   rV   r”   rÊ   Úmax)rO   r@   r¢   rÆ   r7  rZ   Úownersr6  s   &&$    @r   Úproject_for_pathr>  Î  s©   ø€ ô ˆt�zˆz�r‹?× Ñ ×"Ò"ÙÜ˜TÓ"€FØ
z€CßØÐ&Õ&ˆ÷eð eð "Ÿ\™\¨#Ó.×7Ñ7Ô9ÓQÑ9�c¸TÀ#ÀhÅ-×=P�cˆcÑ9€FÐQßTZŒ;�tœS Ñ-GÔHÈÕOÓPÐdÐ`dÐdùò Rs   Á5CÂCÚtitler&   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r)   r�   Útask_idr#   r?  r   r   )r   s   "r   r   r   à  s(   € ÷ 0ñ 0˜Wð 0¨sð 0¸cð 0È3ñ 0r   c               óT  € V P                   ;'       g    \        V P                  4       RV 2pV'       da   \        P	                  R\        V4      P                  4       P                  4       4      P                  R4      R,          P                  R4      MRpV'       d   V RV 2# T# )zµDeterministic ``<project-slug>/<task-id>[-<title-slug>]`` branch name for a project-linked kanban
task (stable and human-meaningful, replacing the random ``wt/<task-id>`` fallback).r2  r%   :Né(   Nr&   )r1   r/   r"   Ú_BRANCH_SAFE_REr+   r#   r,   r-   )r)   rA  r?  ÚbaseÚtslugs   &&$  r   Úbranch_name_forrG  à  s‹   € ð �l‰l×4Ð4œh w§|¡|Ó4Ð5°Q°w°iÐ@€Dß_dŒO×Ñ ¤S¨£Z×%5Ñ%5Ó%7×%=Ñ%=Ó%?Ó@×FÑFÀsÓKÈCÕP×VÑVÐWZÔ[Ðjl€Eß %ˆdˆV�1�U�GÐÐ/¨4Ð/r   )r   r   r   r   )r   r   r   r   r   r;   )KÚ__conditional_annotations__Ú__doc__Ú
__future__r   rg   rC   r*   r¸   Úsqlite3r<   Údataclassesr   r   Úpathlibr   Útypingr   r   r   Úhermes_cli.sqlite_utilr	   rX   r
   r   Úhermes_constantsr   r   rU   Úcompiler4   rD  Úsetr   r~   rW   rŠ   r  r  r/   r7   r=   rH   rd   Úcontextmanagerrk   rm   r�   r–   r�   r¡   r°   rÃ   r«   rÊ   r×   rÒ   rá   rç   rß   rí   rô   rø   rû   rÿ   r  r  r  r  r  r  r  r(  r+  r>  rG  )rH  s   @r   Ú<module>rT     sZ  øðôõ #ã Û 	Û 	Û Û Û ß (Ý ß +Ñ +ç fÑ fÝ ,õ-ð
'€
ðV �:Š:Ð4Ó5€à—*’*Ð-Ó.€Ù"›uÐ �HÓ $ð LÐ àUÐ ØÐ Ø4Ð õ+õõõ ÷\ð, ×Ñöó ðð ÷xð xó ðxð ÷Eð Eó ðEõ$õõ3ð

ÐZ_÷ 
ð$ØBFð$Økoð$à"&ð$àEIð$àaeð$ð  ð$ð >Bð$ð af÷$ðNJÈ÷ Jõ
=ðzØHLðzØkoðzàðzà7;ðzàY]÷zõ2ðÐ_cð Ðx}÷ õ,õ&Zõõgõgõ[õõ)õ
Dõ-õ7õJð	Ðqv÷ 	ð4ÐTX÷ 4ð
Ø]bðà $÷õ2Eð
eÐV[÷ eð$0ÀB÷ 0ñ 0r   