
    *"hj                         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m	Z	 ddl
mZmZmZ ddlZddlmZ  ej"                  e      Z ej(                  d      Zh dZd	ed
eeeef      fdZ G d d      Zy)uR  
Skill Manager for SkillClaw skill retrieval, injection, and local persistence.

Loads skills from a directory of skill subdirectories using the AgentSkills-
compatible format (shared with OpenClaw / Pi coding-agent):

    memory_data/skills/
        skill-name/
            SKILL.md           <- YAML frontmatter + markdown body
            scripts/           <- optional executable scripts
            references/        <- optional reference docs
            assets/            <- optional assets (templates, images, etc.)
        another-skill/
            SKILL.md

Each SKILL.md must have YAML frontmatter with at least ``name`` and
``description``::

    ---
    name: debug-systematically
    description: "Use when diagnosing a bug. Gather evidence before forming
      hypotheses.  NOT for: simple typo fixes."
    metadata:
      {
        "openclaw": { "emoji": "🔍" },
        "skillclaw": { "category": "coding" }
      }
    ---

    # Debug Systematically
    ...

Frontmatter fields
------------------
Required:
  name         lowercase-hyphenated slug
  description  rich trigger description — what, when to use, when NOT to use

Optional:
  metadata     nested dict; ``openclaw`` block for OpenClaw gating/install,
               ``skillclaw`` block for SkillClaw-specific fields (``category``)
  category     (legacy) — if present and ``metadata.skillclaw.category`` is
               absent, used as fallback; defaults to ``"general"``

Valid categories: general, coding, research, data_analysis, security,
                  communication, automation, agentic, productivity, common_mistakes
    N)Counter)AnyDictOptional   )list_skill_bundle_pathsz^[a-z][a-z0-9-]{1,63}$>   namecategorymetadatadescriptionpathreturnc                    	 t        | d      5 }|j                         }ddd       j                  d      sy|j                  dd      }|dk(  ry|d| j                         }||d	z   d j                         }	 t        j                  |      xs i }t        |t              syt        |j                  dd            j                         }t        |j                  dd            j                         }	|r|	st        j	                  d|        y|j                  d      }
|
t        |
t              sd}
d}|
xs i j                  di       }t        |t              r.|j                  d      rt        |d         j                         }n-|j                  d      rt        |d         j                         }t        j                   |j#                               j%                         dd ||	||t&        j(                  j+                  |       d}|
r|
|d<   |j-                         D ci c]  \  }}|t.        vs|| }}}|r||d<   |S # 1 sw Y   1xY w# t        $ r!}t        j	                  d| |       Y d}~yd}~ww xY w# t        j                  $ r t        j	                  d
|        i }Y "w xY wc c}}w )a  Parse a SKILL.md file (AgentSkills / OpenClaw compatible format).

    Returns a dict with keys: name, description, category, content, and
    optionally metadata.  Extra frontmatter fields (e.g. ``homepage``,
    ``user-invocable``) are preserved verbatim so they survive round-trip
    writes.  Returns ``None`` if the file is missing required fields or
    has no frontmatter.

    Category resolution order:
      1. ``metadata.skillclaw.category``
      2. top-level ``category`` (legacy)
      3. ``"general"`` (default)
    utf-8encodingNz$[SkillManager] could not read %s: %sz---z
---      z-[SkillManager] invalid YAML frontmatter in %sr	    r   u:   [SkillManager] skipping %s — missing name or descriptionr   general	skillclawr
      )idr	   r   r
   content	file_path_extra_frontmatter)openreadOSErrorloggerwarning
startswithfindstripyaml	safe_load	YAMLError
isinstancedictstrgethashlibsha256encode	hexdigestosr   realpathitems_CORE_FM_KEYS)r   fraweend_idxfm_textbodyfmr	   r   r   r
   sc_metaresultkvextras                    2/root/.hermes/SkillClaw/skillclaw/skill_manager.py_parse_skill_mdrB   K   s|   $) 	Q&&(C	 >>% hhw"G"}!Gn""$Gw{}##%D^^G$*
 b$rvvfb!"((*Dbff]B/0668K{SUYZvvj!HJx$> H~2"";3G'4 W[[%<wz*+113	
	r*~&,,. nnT[[]+557<"WW%%d+F %z !hhjCdaA],BQTCEC',#$My	 	 =tQG  >> FML DsK   I II <J	 -J;>J;II 	J%JJ	+J87J8c                   n   e Zd ZdZ	 	 	 dEdedededee   fdZdefd	Zdeeeee	f   f   fd
Z
dFdZdFdZdee   ddfdZdee   deddfdZdedefdZdeeeee	f   f   fdZdeee	f   fdZdee   fdZdeeeeef   df   fdZdefdZdedefdZdedefdZdFdZdefdZd Ze deee	f   defd       Z!defd Z"d!ededefd"Z#	 dGd#ee   d$ed%ed&edee   f
d'Z$d(ed%edee   fd)Z%dHd(ed%edee   fd*Z&dee   fd+Z'deeeeef   f   fd,Z(dedefd-Z)e d.edefd/       Z*d0ee   defd1Z+d0ee   defd2Z,e 	 dId3ed4edefd5       Z-	 	 dJd6ed4edefd7Z.d8eddfd9Z/dedefd:Z0dKd;ee   d<edefd=Z1e dedefd>       Z2deddfd?Z3dLd@ee   ddfdAZ4de5fdBZ6de7fdCZ8defdDZ9y)MSkillManageru  Loads skills from a directory of AgentSkills / OpenClaw compatible
    skill folders.

    Each skill is a subdirectory containing a ``SKILL.md`` with YAML
    frontmatter (``name``, ``description``, optional ``metadata``) and a
    Markdown body.

    Supports two retrieval modes:
      * ``"template"`` – flat effectiveness-ranked retrieval, zero latency
      * ``"embedding"`` – cosine similarity via SentenceTransformer
    N
skills_dirpublic_skill_rootretrieval_modeembedding_model_pathc           	      l   |dvrt        d| d      t        j                  j                  |      st	        d|       || _        |j                         | _        || _        |xs d| _	        d | _
        d | _        d| _        | j                         | _        | j                         | _        | j#                         | _        d| _        | j)                         }t*        j-                  dt/        | j                  j1                  dg             ||t3        |             |d	k(  r| j5                          y y )
N)template	embeddingz7retrieval_mode must be 'template' or 'embedding', got ''zSkills directory not found: zQwen/Qwen3-Embedding-0.6Br   zA[SkillManager] loaded %d skills from %s | mode=%s | categories=%s
all_skillsrK   )
ValueErrorr1   r   isdirFileNotFoundError_skills_dirr%   _public_skill_rootrG   rH   _embedding_model_skill_embeddings_cache
generation_load_skillsskills_compute_skills_fingerprint_skills_fingerprint_load_stats_stats_stats_dirty_category_countsr!   infolenr,   r*   _compute_skill_embeddings)selfrE   rF   rG   rH   countss         rA   __init__zSkillManager.__init__   s%    !::VWeVffghiiww}}Z(#&B:,$OPP%"3"9"9";,$8$W<W! $7;$  !'')#'#C#C#E &&(&&(Ob12L	
 [(**, )    r   c                 V    t         j                  j                  | j                  d      S )Nzskill_stats.json)r1   r   joinrQ   ra   s    rA   _stats_pathzSkillManager._stats_path   s    ww||D,,.@AArd   c                 R   | j                         }t        j                  j                  |      si S 	 t	        |d      5 }t        j                  |      cd d d        S # 1 sw Y   y xY w# t
        j                  t        f$ r"}t        j                  d|       i cY d }~S d }~ww xY w)Nr   r   z'[SkillManager] failed to load stats: %s)rh   r1   r   existsr   jsonloadJSONDecodeErrorr    r!   r"   )ra   r   r5   r7   s       rA   rZ   zSkillManager._load_stats   s    !ww~~d#I	dW- $yy|$ $ $$$g. 	NNDaHI	s;   A+  A	A+ A($A+ (A+ +B&B!B&!B&c                    	 t        | j                         dd      5 }t        j                  | j                  |dd       d d d        y # 1 sw Y   y xY w# t
        $ r }t        j                  d|       Y d }~y d }~ww xY w)Nwr   r   F   ensure_asciiindentz'[SkillManager] failed to save stats: %s)r   rh   rk   dumpr[   r    r!   r"   )ra   r5   r7   s      rA   _save_statszSkillManager._save_stats   sq    	Id&&(#@ HA		$++quQGH H H 	INNDaHH	Is4   A $AA AA A 	B  A;;B c                 |    | xj                   dz  c_         | j                   dk\  r| j                          d| _         yy)z8Persist stats every 10 mutations to avoid excessive I/O.r   
   r   N)r\   ru   rg   s    rA   _maybe_flush_statszSkillManager._maybe_flush_stats   s:    Q" !D #rd   skill_namesc                     t        j                  d      }|D ]7  }| j                  j                  |ddddddd      }|dxx   dz  cc<   ||d<   9 | j	                          y	)
z6Record that these skills were injected into a request.z%Y-%m-%dT%H:%M:%Sr   r         ?)inject_countpositive_countnegative_countneutral_countlast_injected_ateffectivenessr|   r   r   N)timestrftimer[   
setdefaultrx   )ra   ry   nowr	   entrys        rA   record_injectionzSkillManager.record_injection   sy    mm/0 	,DKK**$%&'&'%&(*%(
E .!Q&!(+E$%	, 	!rd   scorec                    |D ]j  }| j                   j                  |      }|!|dkD  r|dxx   dz  cc<   n |dk  r|dxx   dz  cc<   n|dxx   dz  cc<   |d   }|dkD  r|d   |z  nd|d	<   l | j                          y)
z<Record PRM feedback for skills that were injected in a turn.Nr   r}   r   r~   r   r|   r{   r   )r[   r,   rx   )ra   ry   r   r	   r   totals         rA   record_feedbackzSkillManager.record_feedback  s     	[DKKOOD)E}qy&'1,'&'1,'o&!+&.)EHMPQ	U+;%<u%DWZE/"	[ 	!rd   
skill_namec                 b    | j                   j                  |      }|y|j                  dd      S )zEReturn the effectiveness score for a skill (default 0.5 for unknown).r{   r   )r[   r,   )ra   r   r   s      rA   get_effectivenesszSkillManager.get_effectiveness  s-    
+=yy#..rd   c                 ,    t        | j                        S )z%Return a copy of the full stats dict.)r*   r[   rg   s    rA   get_stats_summaryzSkillManager.get_stats_summary  s    DKK  rd   c                     dg i}| j                         }|s"t        j                  d| j                         |S |D ]$  }t	        |      }||d   j                  |       & |S )zKScan skills_dir for */SKILL.md files and parse each into a flat collection.rM   z,[SkillManager] no SKILL.md files found in %s)_skill_md_pathsr!   r"   rQ   rB   append)ra   r=   pathsr   skills        rA   rV   zSkillManager._load_skills&  sr    ".!3$$&NNI4K[K[\M 	/D#D)E}< ''.		/ rd   c                 J   | j                         rKt        j                  j                  | j                  dd      }t        t        j                  |d            S t        j                  j                  | j                  dd      }t        t        j                  |            S )Nz**SKILL.mdT)	recursive*)_is_hermes_skill_rootr1   r   rf   rQ   sortedglob)ra   patterns     rA   r   zSkillManager._skill_md_paths7  sp    %%'ggll4#3#3T:FG$))Gt<=='',,t//jAdii())rd   .c                 >   g }| j                         D ]o  }	 t        j                  |      }|j	                  t        j
                  j                  |      t        |j                        t        |j                        f       q t        |      S # t        $ r Y w xY wN)r   r1   statr    r   r   r2   intst_mtime_nsst_sizetuple)ra   fingerprintr   r   s       rA   rX   z(SkillManager._compute_skills_fingerprint>  s    24((* 	Dwwt} GG$$T*(()%	 [!!  s   B	BBc                    t         j                  j                  | j                        t         j                  j                  t         j                  j	                  t         j                  j                  d      dd            k(  S )N~z.hermesrW   )r1   r   r2   rQ   rf   
expanduserrg   s    rA   r   z"SkillManager._is_hermes_skill_rootN  sX    ww 0 01RWW5E5EGGLL++C0)XF6
 
 	
rd   r   c                    t        |j                  dd      xs d      j                         }t        |j                  dd      xs d      j                         }| j                         r2|r0|dk7  r+t        j
                  j                  | j                  ||      S t        j
                  j                  | j                  |      S )Nr	   unknownr
   r   )r+   r,   r%   r   r1   r   rf   rQ   )ra   r   r	   r
   s       rA   _skill_dir_pathzSkillManager._skill_dir_pathS  s    599VY/<9=CCEuyyY7D9EKKM%%'HY9N77<< 0 0(DAAww||D,,d33rd   c                 `    t         j                  j                  | j                  |      d      S )Nr   )r1   r   rf   r   )ra   r   s     rA   _skill_md_pathzSkillManager._skill_md_pathZ  s"    ww||D007DDrd   c                 .   | j                          | j                         | _        | j                         | _        | j                         | _        d| _        | j                  dk(  r| j                          t        j                  d| j                         y)zARe-scan the skills directory and rebuild the internal skill bank.NrK   z&[SkillManager] reloaded skills from %s)ru   rV   rW   rX   rY   rZ   r[   rT   rG   r`   r!   r^   rQ   rg   s    rA   reloadzSkillManager.reload]  sv    '')#'#C#C#E &&('+$+-**,<d>N>NOrd   c                     | j                         }|| j                  k(  ry| j                          | xj                  dz  c_        t        j                  d       y)z>Reload skills if the on-disk skill library changed externally.Fr   z>[SkillManager] detected local skill changes; refreshed libraryT)rX   rY   r   rU   r!   r^   )ra   currents     rA   refresh_if_changedzSkillManager.refresh_if_changedh  sH    224d...1TUrd   c                     | j                   >	 ddlm} t        j                  d| j                          || j                        | _         | j                   S # t        $ r t        d      w xY w)Nr   )SentenceTransformerzjsentence-transformers is required for embedding retrieval. Install with: pip install sentence-transformersz*[SkillManager] loading embedding model: %s)rS   sentence_transformersr   ImportErrorr!   r^   rH   )ra   r   s     rA   _get_embedding_modelz!SkillManager._get_embedding_modelv  sp      (E KKDdF_F_`$78Q8Q$RD!$$$  !F s   A A,c                    | j                  dd      j                         | j                  dd      j                         g}| j                  dd      j                         }|r|j                  |d d        dj                  d |D              S )Nr	   r   r   r      z. c              3   &   K   | ]	  }|s|  y wr    ).0ps     rA   	<genexpr>z.SkillManager._skill_to_text.<locals>.<genexpr>  s     /qQ/s   )r,   r%   r   rf   )r   partsr   s      rA   _skill_to_textzSkillManager._skill_to_text  s~     IIfb!'')IImR(..0
 ))Ir*002LL#'yy/E///rd   c                    | j                   | j                   S dd l}t        | j                  j	                  dg             }|D cg c]  }| j                  |       }}|s+g |j                  dt              d| _         | j                   S | j                         }|j                  |ddd      }||d| _         t        j                  d	t        |             | j                   S c c}w )
Nr   rM   )r   r   )dtype)r3   
embeddingsTFnormalize_embeddingsshow_progress_barconvert_to_numpyz)[SkillManager] cached %d skill embeddings)rT   numpylistrW   r,   r   zerosfloatr   r/   r!   r^   r_   )ra   np	all_itemsr   textsmodelr   s          rA   r`   z&SkillManager._compute_skill_embeddings  s    ''3///r:;	9BC$$U+CC hhvUh;,D( ///))+\\!%#!	 " 

 $(
$ 	?YP++++ Ds   C'
similarityc                 :    | j                  |      }|dd|z  z   z  S )z<Combine embedding similarity with effectiveness for ranking.g333333?gffffff?)r   )ra   r   r   effs       rA   _weighted_scorezSkillManager._weighted_score  s%    $$Z0S39_--rd   indicescachetop_k	thresholdc                     |sg S |d   }g }|D ]L  }d}|D ]  }	t        ||   ||	   z        }
|
|kD  sd} n |s|j                  |       t        |      |k\  sK |S  |S )zRemove near-duplicate skills from candidate list, keeping the one
        with higher weighted score. Fills freed slots with next candidates.r   FT)r   r   r_   )ra   r   r   r   r   embskeptidxis_dupkept_idxsims              rA   _deduplicate_by_embeddingz&SkillManager._deduplicate_by_embedding  s    
 I\" 
	CF  DIX67?!F	
 C 4yE!
	 rd   task_descriptionc                 f   
  j                         }|d   sg S  j                         }|j                  |gddd      d   }|d   |z  |d   
t        t	        t                    
 fdd      }|d t        |d	z  |       } j                  |||      }|D 	cg c]  }	
|	   	 c}	S c c}	w )
Nr3   TFr   r   r   c                 f    j                  t        |          |    j                  dd            S Nr	   r   )r   r   r,   )ir3   ra   simss    rA   <lambda>z2SkillManager._embedding_retrieve.<locals>.<lambda>  s,    $..uT!W~uQx||FTV?WX rd   keyreverser   )r`   r   r/   r   ranger_   maxr   )ra   r   r   r   r   	query_embrankedcandidate_indicesr   r   r3   r   s   `         @@rA   _embedding_retrievez SkillManager._embedding_retrieve  s    ..0W~I))+LL!%#!	 ! 

 	 \"Y.g#d)X

 ##:SE%:;--.?N"&'Qa'''s   B.c                       j                   dk(  r j                  ||      S t         j                  j	                  dg             }t        | fdd      }|d| S )a  Retrieve relevant skills for *task_description*.

        In embedding mode, skills are ranked by similarity * effectiveness and
        deduplicated by embedding similarity. In template mode, all skills are
        sorted by effectiveness without any category-specific routing.
        rK   rM   c                 F    j                  | j                  dd            S r   )r   r,   )sra   s    rA   r   z'SkillManager.retrieve.<locals>.<lambda>  s    $00vr1BC rd   Tr   N)rG   r   r   rW   r,   r   )ra   r   r   rM   all_skills_sorteds   `    rA   retrievezSkillManager.retrieve  sc     +-++,<eDD$++//,;<
"C

 !%((rd   c                     | j                   j                  dg       D cg c]&  }|j                  di       j                  dd      s|( c}S c c}w )zReturn a flat list of ALL loaded skills eligible for model invocation.

        Skills with ``disable-model-invocation: true`` in their frontmatter
        are filtered out (matching OpenClaw behaviour).
        rM   r   zdisable-model-invocationFrW   r,   ra   r   s     rA   get_all_skillszSkillManager.get_all_skills  sQ     [[__\26
55-r2667QSXY 
 	
 
s   +Ac           
         i }| j                         D ]m  }t        j                  j                  t	        |j                  dd      xs d            }|rt        |      ng }|xs dg}| j                  |      r.t        j                  j                  | j                  |            nd}|D ]  }g }|rL|j                  t        j                  j                  t        j                  j                  ||                   |rL|j                  t        j                  j                  t        j                  j                  ||                   |D ]-  }|s|j                  dd      |j                  dd      d||<   /  p |S )u   Return a mapping from bundle file path → {skill_id, skill_name}.

        Used by the server to resolve which skill a ``read`` tool call targets.
        r   r   r   r   r	   )skill_idr   )r   r1   r   dirnamer+   r,   r   _public_skill_pathr   r2   rf   )	ra   path_mapr   	skill_dirbundle_paths
public_dirrel_path	locationsfps	            rA   get_skill_path_mapzSkillManager.get_skill_path_map  sC   
 /1$$& 	AAEE+r,B,Hb(IJIAJ29=PRL'7J<LHLH_H_`aHb)@)@)CDhjJ( 	$$RWW%5%5bggll9h6W%XY$$RWW%5%5bggll:x6X%YZ# B()dB*+%%*;(	" rd   c                     | j                   syt        |j                  dd            j                         }|syt        j
                  j                  | j                   |d      S )Nr   r	   r   )rR   r+   r,   r%   r1   r   rf   )ra   r   r	   s      rA   r   zSkillManager._public_skill_path  sO    &&599VR()//1ww||D33T:FFrd   textc                     | j                  dd      j                  dd      j                  dd      j                  dd      j                  d	d
      S )z>Escape XML special characters (matching OpenClaw's escapeXml).&z&amp;<z&lt;>z&gt;"z&quot;rL   z&apos;)replace)r  s    rA   _escape_xmlzSkillManager._escape_xml&  sG     LLg&WS&!WS&!WS(#WS(#	
rd   rW   c           
         |syt         j                  }g d}|D ]  }|j                  d       |j                  d ||j                  dd             d       |j                  d ||j                  dd             d	       |j                  d
 || j	                  |      xs |j                  dd             d       |j                  d        |j                  d       dj                  |      S )a5  Build the *full* ``<available_skills>`` XML catalog.

        Output matches ``formatSkillsForPrompt`` from
        ``@mariozechner/pi-coding-agent`` (used by OpenClaw): preamble text
        followed by ``<available_skills>`` with ``<name>``, ``<description>``,
        and ``<location>`` per skill.
        r   )K

The following skills provide specialized instructions for specific tasks.zOUse the read tool to load a skill's file when the task matches its description.When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.r   <available_skills>	  <skill>
    <name>r	   </name>z    <description>r   z</description>    <location>r   </location>
  </skill></available_skills>
rD   r  r   r,   r   rf   ra   rW   escapelinesr   s        rA   format_skills_for_promptz%SkillManager.format_skills_for_prompt1  s     ))
  	'ELL%LL:fUYYvr-B&C%DGLMLL,VEIImR4P-Q,RR`abLL (?(?(F(d%))T_acJd!e ffqr LL&	' 	*+yyrd   c           
         |syt         j                  }g d}|D ]  }|j                  d       |j                  d ||j                  dd             d       |j                  d || j	                  |      xs |j                  dd             d	       |j                  d
        |j                  d       dj                  |      S )zBuild the *compact* ``<available_skills>`` XML catalog.

        Omits ``<description>`` to save tokens.  Matches OpenClaw's
        ``formatSkillsCompact``.
        r   )r  zHUse the read tool to load a skill's file when the task matches its name.r  r   r  r  r  r	   r  r  r   r  r  r  r  r  r  s        rA   format_skills_compactz"SkillManager.format_skills_compactP  s     ))
  	'ELL%LL:fUYYvr-B&C%DGLMLL (?(?(F(d%))T_acJd!e ffqr LL&	' 	*+yyrd   skills_promptread_tool_namec                 d    | j                         }|sydj                  ddd| dddd	d
|dg	      S )zWrap a skills catalog string with the ``## Skills (mandatory)``
        instruction block.

        Matches OpenClaw's ``buildSkillsSection`` in ``system-prompt.ts``.
        r   r  z## Skills (mandatory)z?Before replying: scan <available_skills> <description> entries.zN- If exactly one skill clearly applies: read its SKILL.md at <location> with `z`, then follow it.zM- If multiple could apply: choose the most specific one, then read/follow it.z2- If none clearly apply: do not read any SKILL.md.zPConstraints: never read more than one skill up front; only read after selecting.z- When a skill drives external API writes, assume rate limits: prefer fewer larger writes, avoid tight one-item loops, serialize bursts when possible, and respect 429/Retry-After.)r%   rf   )r!  r"  trimmeds      rA   build_skills_sectionz!SkillManager.build_skills_sectionl  s^      %%'yy'Q$$2#33EG_Db/ 
 	
rd   	max_charsc                     | j                         }|sy| j                  |      }t        |      |k  r|}n| j                  |      }| j	                  ||      S )a/  One-call helper: catalog all skills and wrap with instructions.

        Uses the full format (name + description + location) when the catalog
        fits within *max_chars*; falls back to compact format (name + location)
        otherwise.  Returns the empty string when no skills are loaded.
        r   )r   r  r_   r   r%  )ra   r&  r"  rW   full_promptcatalogs         rA   build_injection_promptz#SkillManager.build_injection_prompt  s]     $$&33F;{y(!G008G((.AArd   r	   c                     | j                   j                  dg       D cg c]  }|j                  d      |k7  s| c}| j                   d<   yc c}w )z9Remove a skill from in-memory structures (not from disk).rM   r	   Nr   )ra   r	   r   s      rA   _remove_skill_from_memoryz&SkillManager._remove_skill_from_memory  sB    04b0Q$k1UVUZUZ[aUbfjUjQ$kL!$ks
   AAc                 0   |j                  dd      j                         }|st        j                  d       yt        j                  |      st        j                  d|       y| j                         }||v rP|j                  d      r(| j                  |       t        j                  d|       nt        j                  d|       y|j                         D ci c]  \  }}|j                  d	      r|d
k(  s||! }}}d|vr7t        j                  |j                               j                         dd |d<   d|vr1t        j                   j#                  | j%                  |            |d<   t'        |j                  dd      xs d      j                         |d<   | j(                  j+                  dg       j-                  |       d| _        | j1                  |       | j3                         | _        t        j                  d|       yc c}}w )a  
        Add a new skill to the in-memory bank and write its SKILL.md file.

        Returns True if the skill was added, False if it already exists
        (unless ``skill["_replace"]`` is truthy, in which case the old
        version is overwritten).
        r	   r   z1[SkillManager] add_skill called with missing nameFz.[SkillManager] rejected invalid skill name: %s_replacez+[SkillManager] replacing existing skill: %sz+[SkillManager] skipping duplicate skill: %s_r   r   Nr   r   r
   r   rM   z[SkillManager] added skill: %sT)r,   r%   r!   r"   _SAFE_NAME_REmatch_get_all_skill_namesr,  r^   r3   r#   r-   r.   r/   r0   r1   r   r2   r   r+   rW   r   r   rT   _write_skill_mdrX   rY   )ra   r   r	   existingr>   r?   clean_skills          rA   	add_skillzSkillManager.add_skill  s    yy$**,NNNO""4(NNKTR,,.8yy$..t4I4PI4P(-l1Q\\#=NRSWkRkq!tll{" 't{{} = G G I#2 NKk)')ww'7'78K8KK8X'YK$"%kooj)&L&YPY"Z"`"`"bJ|R077D'+$[)#'#C#C#E 4d; ms   H;H
new_skillsr
   c                     d}|D ]%  }d|vri |d|i}| j                  |      s!|dz  }' |dkD  r| xj                  dz  c_        |S )zAdd multiple skills; returns count actually added.

        Increments ``self.generation`` when at least one skill is successfully
        added, signalling callers that the local skill library changed.
        r   r
   r   )r6  rU   )ra   r7  r
   addedr   s        rA   
add_skillszSkillManager.add_skills  sb      	E&757*h7~~e$
		
 19OOq Ord   c                 T   g }| j                  dd      }| j                  dd      | j                  d      }| j                  di       }|j                  d|        t        fdd	D              }|rHj                  d
d      j                  dd      j                  dd      }|j                  d| d       n|j                  d        |j	                         D ]Q  \  }}|j                  d      rt        j                  ||iddd      j                         }	|j                  |	       S |rbt        |t              rRt        j                  |dd      }
dj                  d |
j                         D              }|j                  d|        dj                  |      S )uL  Serialize skill frontmatter as YAML, OpenClaw-compatible.

        Produces output like::

            name: my-skill
            description: "Rich description with: colons and special chars."
            homepage: https://example.com
            metadata:
              {
                "openclaw": { "emoji": "🔧" },
                "skillclaw": { "category": "coding" }
              }

        Extra frontmatter fields (``homepage``, ``user-invocable``, etc.)
        are written between ``description`` and ``metadata`` to match the
        OpenClaw SKILL.md convention.
        r	   r   r   r   r   r   zname: c              3   &   K   | ]  }|v  
 y wr   r   )r   cr   s     rA   r   z3SkillManager._format_frontmatter.<locals>.<genexpr>  s     MA,Ms   z:{}[],"'#&*!|>%@`
\z\\r  z\"r  z\nzdescription: "zdescription: r/  FTi'  )default_flow_styleallow_unicodewidthrp   rq   c              3   &   K   | ]	  }d |z     yw)z  Nr   )r   lns     rA   r   z3SkillManager._format_frontmatter.<locals>.<genexpr>
  s      Kr Ks   z
metadata:
)r,   r   anyr  r3   r#   r&   rt   r%   r)   r*   rk   dumpsrf   
splitlines)r   r  r	   r   extra_fmneeds_quotingescapedr   valuedumpedjson_strindentedr   s               @rA   _format_frontmatterz SkillManager._format_frontmatter  s   & yy+iir299Z(99126vdV_%M6LMM!))$7??UKSSTXZ_`GLL>'!45LL=67 #..* 		!JC~~c"YYe#("	
 eg  LL 		! 
8T2zz(qIHyy KX5H5H5J KKHLL;xj12yyrd   c                 $   |j                  dd      }| j                  |      }t        j                  j	                  |      }|j                  t        j                  j	                  | j                        t        j                  z         st        j                  d|       yt        j                  |d       | j                  |      }|j                  dd      }|j                  d	d
      }t        |j                  d      xs i       }|r|dk7  r||j                  di       d<   ||j                  dd
      d}	|j                  d      }
|
r|
|	d<   |r||	d<   | j                  |	      }d| d| d}	 t        |dd      5 }|j!                  |       ddd       t        j                  j	                  |      |d<   t        j#                  d|       y# 1 sw Y   BxY w# t$        $ r!}t        j                  d||       Y d}~yd}~ww xY w)a  Persist a single skill to its SKILL.md file (AgentSkills / OpenClaw
        compatible format).

        Category is stored in ``metadata.skillclaw.category`` (not as a bare
        top-level frontmatter field) so the output is fully OpenClaw-compatible.
        Extra frontmatter fields (e.g. ``homepage``) that were parsed from an
        existing SKILL.md are preserved in the output.
        r	   r   z7[SkillManager] blocked path traversal in skill name: %sNT)exist_okr
   r   r   r   r   r   r   )r	   r   r   z---
z
---

r  ro   r   r   r   z#[SkillManager] wrote skill file: %sz%[SkillManager] could not write %s: %s)r,   r   r1   r   r2   r#   rQ   sepr!   r"   makedirsr   r*   r   rN  r   writer^   r    )ra   r   r	   r   	canonicalfilepathr
   r   r   write_skillrG  r9   r  r5   r7   s                  rA   r3  zSkillManager._write_skill_md  s    yy+((/	GG$$Y/	##BGG$4$4T5E5E$F$OPNNTVZ[
I-&&u-99Z3))Ir*		*-34I-?GHR0<  99]B7'
 991208K,-&.K
#**;7wiy	4	Qhg6 !!#!1!1(!;E+KK=xH   	QNNBHaPP	Qs1   8G% GA G% G"G% %	H.H

Hr   c                     t        | j                  j                  dg             }|D ]  }| j                  |        | j	                         | _        t        j                  dt        |      | j                         y)z
        Persist all in-memory skills back to .md files.

        ``path`` is ignored (kept for backward compatibility); files are always
        written to skills_dir.
        rM   z$[SkillManager] saved %d skills to %sN)
r   rW   r,   r3  rX   rY   r!   r^   r_   rQ   )ra   r   rM   r   s       rA   savezSkillManager.save<  sf     $++//,;<
 	(E  '	(#'#C#C#E :C
OTM]M]^rd   c                     | j                   j                  dg       D ch c].  }|j                  d      st        |j                  d            0 c}S c c}w )NrM   r	   )rW   r,   r+   r   s     rA   r2  z!SkillManager._get_all_skill_namesI  sA    ,0KKOOL",M_qQRQVQVW]Q^AEE&M"___s
   AAc                 Z    t        d | j                  j                  dg       D              S )Nc              3   X   K   | ]"  }t        |j                  d       xs d       $ yw)r
   r   N)r+   r,   )r   r   s     rA   r   z0SkillManager._category_counts.<locals>.<genexpr>M  s#     fqs155,9	:fs   (*rM   )r   rW   r,   rg   s    rA   r]   zSkillManager._category_countsL  s$    fDKKOOT`bdDefffrd   c                     t        | j                  j                  dg             t        | j	                               dS )NrM   )r   by_category)r_   rW   r,   r*   r]   rg   s    rA   get_skill_countzSkillManager.get_skill_countO  s4    r:; 5 5 78
 	
rd   )r   rJ   N)r   N)g?)   )r   )i0u  r   )r   r   ):__name__
__module____qualname____doc__r+   r   rc   rh   r   r   rZ   ru   rx   r   r   r   r   r   r   rV   r   r   r   rX   boolr   r*   r   r   r   r   r   staticmethodr   r`   r   r   r   r   r   r  r   r  r  r   r%  r*  r,  r6  r:  rN  r3  rX  setr2  r   r]   r^  r   rd   rA   rD   rD      s   
 "$(.2'-'- '- 	'-
 'sm'-ZBS B	T#tCH~"56 	I""DI "$ "&"49 "U "t " /C /E /!4T#s(^(;#< !d38n "*c *"U5c33G3L-M " 
t 

4T 4c 4ED ES E	PD % 0d38n 0 0 0,4 ,<.% .S .U . ORCy)-69FK	c,(C ( (T
 (8) )S )d ),

T
 

Dd38n)<$= 2G G G 
# 
# 
 
 tDz  c  > DJ  3  8  %


 

 
>  $BB B 
	B*lc ld l%t % %NT$Z 3 s   2 4 2 C 2  2 h+QT +Qd +QZ_# _$ _`c `g' g
 
rd   rD   )rc  r   r-   rk   loggingr1   rer   collectionsr   typingr   r   r   r&   skill_bundler   	getLoggerr`  r!   compiler0  r4   r+   rB   rD   r   rd   rA   <module>rn     s   .`     	 	   & &  1			8	$

45 @K# K(4S>": Kfu

 u

rd   