
    Pmj                     B   U d 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
mZmZmZ ddlmZmZmZ  ej        e          Zdddd	Z ed
          Z ed          ZdZdZdZdZdedee         fdZdedefdZ dedee         fdZ!dddee         defdZ"dddee         defdZ#da$defdZ%dedee	eef         ef         fdZ&dedefdZ'de	eef         defdZ( eh d           Z)i Z*e	eef         e+d!<   d"edefd#Z,de	eef         defd$Z-i Z.e	eee/e/f         e	eef         f         e+d%<   dGd&Z0de	eef         fd'Z1dHd(edz  dee         fd)Z2dee         fd*Z3i Z4e	eee/f         e
e         f         e+d+<   dGd,Z5de
e         fd-Z6de
e         fd.Z7d/edefd0Z8defd1Z9defd2Z:de	eef         de	ee
f         fd3Z;de	eef         de
e	eef                  fd4Z<de
e	eef                  fd5Z=d6Z>d7e	eef         d8efd9Z?d:e
e	eef                  de	eef         fd;Z@d<ZAde	eef         defd=ZBde	eef         defd>ZCde	eef         defd?ZDded@efdAZE ejF        dB          ZGdCedeee         ef         fdDZHdEee         defdFZIdS )Ia  Lightweight skill metadata utilities shared by prompt_builder and skills_tool.

This module intentionally avoids importing the tool registry, CLI config, or any
heavy dependency chain.  It is safe to import at module level without triggering
tool registration or provider resolution.
    N)Path)AnyDictListOptionalSetTuple)get_config_pathget_skills_dir	is_termuxdarwinlinuxwin32)macosr   windows)z.gitz.githubz.hubz.archivez.venvvenvnode_moduleszsite-packages__pycache__z.toxz.noxz.pytest_cachez.mypy_cachez.ruff_cache)
references	templatesassetsscripts_orgz.active_orgz.org-provenance.jsonz.org-baseline.json
skills_dirreturnc                     	 | t           z  t          z  }|                                sdS |                    d                                          }|pdS # t
          $ r Y dS w xY w)zBThe org id whose mirror may resolve, or None (no org skills load).Nutf-8encoding)ORG_MIRROR_DIR_NAMEORG_ACTIVE_MARKERexists	read_textstripOSError)r   markervals      7/home/thesage/.hermes/hermes-agent/agent/skill_utils.pyread_active_org_idr)   F   s{    114EE}} 	4006688{d   tts   &A +A 
A$#A$c                 0   	 t          |                                                               t          |                                                    }n# t          t          f$ r Y dS w xY wt          |j                  o|j        d         t          k    S )z6True when *path* is inside the org mirror (``_org/``).Fr   )r   resolverelative_tor%   
ValueErrorboolpartsr    pathr   rels      r(   is_org_mirror_pathr3   R   s    4jj  ""..tJ/?/?/G/G/I/IJJZ    uu	??Bsy|/BBB   AA A+*A+c                 V   	 t          |                                                               t          |                                                    }n# t          t          f$ r Y dS w xY wt          |j                  dk    r#|j        d         t          k    r|j        d         S dS )z@The ``<org_id>`` segment for a path under ``_org/<org_id>/...``.N   r      )r   r+   r,   r%   r-   lenr/   r    r0   s      r(   org_id_of_pathr9   [   s    4jj  ""..tJ/?/?/G/G/I/IJJZ    tt
39~~sy|/BBBy|4r4   rootr;   c                    	 | j         }n3# t          $ r& ddlm}  |t	          |                     j         }Y nw xY wt          d |D                       pt          | |          S )ax  True if *path* should be skipped by active skill scanners.

    Use this on every ``SKILL.md`` path produced by direct ``rglob`` scans to
    prune dependency, virtualenv, VCS, cache, and progressive-disclosure
    support-package paths. Centralising the check here keeps every
    skill-scanning site in sync with the shared exclusion set.

    Accepts a Path or string.
    r   )PurePathc              3   (   K   | ]}|t           v V  d S N)EXCLUDED_SKILL_DIRS).0parts     r(   	<genexpr>z)is_excluded_skill_path.<locals>.<genexpr>u   s(      ==tt**======    r:   )r/   AttributeErrorpathlibr=   stranyis_skill_support_path)r1   r;   r/   r=   s       r(   is_excluded_skill_pathrJ   f   s    *
 * * *$$$$$$T##)* ==u===== AV4B B B s   
 -::c                `   t          | t                    r| nt          t          |                     }|j        }t	          |dd                   D ][\  }}|t
          vs|dk    rt          |d|          }||                                s||z  }|dz                                  r dS \dS )a  True if *path* is under a support dir of an actual skill root.

    ``references/``, ``templates/``, ``assets/``, and ``scripts/`` are
    progressive-disclosure support areas when they sit directly inside a skill
    directory containing ``SKILL.md``. They are not active discovery roots for
    standalone skills. A preserved package such as
    ``some-skill/references/old-skill-package/SKILL.md`` is documentation data
    unless the caller explicitly loads it via ``file_path``.

    Legitimate categories or skill names such as ``skills/scripts/foo`` remain
    discoverable because their ``scripts`` component is not directly under a
    directory that contains ``SKILL.md``.
    Nr   SKILL.mdTF)
isinstancer   rG   r/   	enumerateSKILL_SUPPORT_DIRSis_absoluter"   )r1   r;   path_objr/   idxrB   
skill_roots          r(   rI   rI   z   s     "$--Btt4D		??HNE uSbSz**  	T)))SAXX5#;'
H$8$8$:$:
*J#++-- 	44	5rD   contentc                     t           ,ddlt          dd          pj        dt          ffd}|a t          |           S )z7Parse YAML with lazy import and CSafeLoader preference.Nr   CSafeLoadervaluec                 2                         |           S )N)Loader)load)rX   loaderyamls    r(   _loadzyaml_load.<locals>._load   s    99U69222rD   )_yaml_load_fnr]   getattr
SafeLoaderrG   )rU   r^   r\   r]   s     @@r(   	yaml_loadrb      sl     }d33Ft	3 	3 	3 	3 	3 	3 	3 	3 !!!rD   c                    i }|                      d          r
| dd         } | }|                      d          s||fS t          j        d| dd                   }|s||fS | d|                                dz            }| |                                dz   d         }	 t          |          }t          |t                    r|}n# t          $ rt |	                                
                    d          D ]I}d|vr|
                    dd          \  }}|	                                ||	                                <   JY nw xY w||fS )	u  Parse YAML frontmatter from a markdown string.

    Uses yaml with CSafeLoader for full YAML support (nested metadata, lists)
    with a fallback to simple key:value splitting for robustness.

    A single leading UTF-8 BOM (U+FEFF) is stripped before parsing. Windows
    GUI editors (Notepad, PowerShell ``>``) prepend one when saving a SKILL.md
    as UTF-8, and ``read_text(encoding="utf-8")`` preserves it (only
    ``utf-8-sig`` strips it). Left in place, the BOM defeats the ``---`` fence
    check below and the whole frontmatter is silently discarded — name,
    description, ``platforms`` gating, env-var setup, and conditional
    activation all vanish. See CONTRIBUTING.md "File encoding".

    Returns:
        (frontmatter_dict, remaining_body)
    u   ﻿r7   Nz---z
\n---\s*\n   
:)
startswithresearchstartendrb   rN   dict	Exceptionr$   split)	rU   frontmatterbody	end_matchyaml_contentparsedlinekeyrX   s	            r(   parse_frontmatterrv      s   " #%K (## !""+De$$ !D  	-55I !D  1y001445L9==??Q&(()D
5<((fd## 	! K 5 5 5 &&((..t44 	5 	5D$C++JC',{{}}K		$$		5 	55 s   &C A;EE	platformsc                 p   | sdS t          | t                    s| g} t          j        }t	                      }| D ]|}t          |                                                                          }t          	                    ||          }|
                    |          r dS |r	|dk    r dS |r|dv r dS }dS )z?Return True when *platforms* is compatible with the current OS.Tr   )termuxandroidF)rN   listsysplatformr   rG   lowerr$   PLATFORM_MAPgetrg   )rw   currentrunning_in_termuxr}   
normalizedmappeds         r(   skill_matches_platform_listr      s     ti&&  K	lG!  ]]((**0022
!!*j99f%% 	44
  	7!2!244 	+@!@!@445rD   ro   c                 F    t          |                     d                    S )av  Return True when the skill is compatible with the current OS.

    Skills declare platform requirements via a top-level ``platforms`` list
    in their YAML frontmatter::

        platforms: [macos]          # macOS only
        platforms: [macos, linux]   # macOS and Linux

    If the field is absent or empty the skill is compatible with **all**
    platforms (backward-compatible default).

    Termux note: on Termux/Android, ``sys.platform`` is ``"linux"`` on
    older Pythons but became ``"android"`` on Python 3.13+. Termux is a
    Linux userland riding on the Android kernel, so skills tagged
    ``linux`` are treated as compatible in Termux regardless of which
    ``sys.platform`` value Python reports. Individual Linux commands
    inside a skill may still misbehave (no systemd, BusyBox utils, no
    apt/dnf, etc.) but that is on the skill, not on platform gating.
    rw   )r   r   )ro   s    r(   skill_matches_platformr      s    ( '{{'C'CDDDrD   >   s6dockerkanban_ENV_DETECT_CACHEenvc                    | t           v rt           |          S d}| dk    r\t          j        d          st          j        d          rd}n	 ddlm} t           |                      }n# t          $ r d}Y nrw xY w| dk    r$	 dd	lm}  |            }nV# t          $ r d}Y nHw xY w| d
k    r>t          j	        
                    d          pt          j	        
                    d          }|t           | <   |S )zReturn True when the named runtime environment is currently active.

    Cached per process. Unknown env names return True (fail-open: never hide a
    skill because of a tag we don't understand).
    Tr   HERMES_KANBAN_TASKHERMES_KANBAN_BOARDr   )_profile_has_kanban_toolsetFr   )is_containerr   z/run/s6z/package/admin/s6-overlay)r   osgetenvtools.kanban_toolsr   r.   rm   hermes_constantsr   r1   isdir)r   resultr   r   s       r(   _detect_environmentr   !  sF     %%F
h 9)** 	bi8M.N.N 	FFJJJJJJ99;;<<   		555555!\^^FF 	 	 	FFF		
 y)) 
RW]]'.
 .
 $cMs$   A) )A87A8B B"!B"c                    |                      d          }|sdS t          |t                    s|g}|D ]V}t          |                                                                          }|s8|t          vr dS t          |          r dS WdS )uR  Return True when the skill is relevant to the current runtime environment.

    Skills may declare an ``environments`` list in their YAML frontmatter::

        environments: [kanban]        # only relevant when kanban is active
        environments: [s6]            # only relevant inside the s6 Docker image
        environments: [docker]        # only relevant inside any container

    If the field is absent or empty the skill is relevant in **all**
    environments (backward-compatible default).

    This is an OFFER-time filter: it controls whether a skill shows up in the
    skills index / autocomplete / slash-command list. It is intentionally NOT
    enforced by ``skill_view`` or ``--skills`` preloading — an explicit load is
    explicit consent, and load-bearing force-loads (e.g. a dispatcher pinning
    a task to a specialist skill via ``--skills``) must always succeed
    regardless of how the offer surfaces filter the skill.

    A skill matches when ANY of its declared environments is currently active
    (OR semantics, mirroring ``platforms``). Unknown env tags fail open.
    environmentsTF)r   rN   r{   rG   r~   r$   _KNOWN_ENVIRONMENTSr   )ro   r   r   r   s       r(   skill_matches_environmentr   O  s    , ??>22L tlD)) &$~  XX^^%%++--
 	00044z** 	44	5rD   _RAW_CONFIG_CACHEc                  8    t                                            dS )u/   Test hook — drop the shared raw config cache.N)r   clear rD   r(   _raw_config_cache_clearr   |  s    rD   c                  B   t                      } |                                 si S 	 |                                 }t          |           |j        |j        f}n# t          $ r d}Y nw xY w|t                              |          }||S 	 t          | 
                    d                    }n5# t          $ r(}t                              d| |           i cY d}~S d}~ww xY wt          |t                    si S |#t                                           |t          |<   |S )a  Read config.yaml with a shared mtime+size keyed cache.

    This module intentionally avoids importing ``hermes_cli.config`` on the
    skill prompt/build path. A tiny local cache gives the same repeated-read
    win without pulling the heavier CLI config stack into startup.
    Nr   r   z"Could not read skill config %s: %s)r
   r"   statrG   st_mtime_nsst_sizer%   r   r   rb   r#   rm   loggerdebugrN   rl   r   )config_pathr   	cache_keycachedrs   es         r(   _load_raw_configr     sN    "##K 	!!%%t'7F		   			 "&&y11M;00'0BBCC   9;JJJ						 fd## 	!!!'-)$Ms/   0A A&%A&
#B. .
C 8CC C r}   c                    t                      }|st                      S |                    d          }t          |t                    st                      S ddlm} | pt          j        d          p
 |d          }t          |                    d                    }|r>|                    d          pi                     |          }||t          |          z  S |S )a
  Read disabled skill names from config.yaml.

    Args:
        platform: Explicit platform name (e.g. ``"telegram"``).  When
            *None*, resolves from ``HERMES_PLATFORM`` or
            ``HERMES_SESSION_PLATFORM`` env vars.  Returns the global
            disabled list, unioned with the platform-specific list when a
            platform is resolved (a globally-disabled skill stays disabled
            on every platform).

    Reads the config file directly (no CLI config imports) to stay
    lightweight.
    skillsr   )get_session_envHERMES_PLATFORMHERMES_SESSION_PLATFORMdisabledplatform_disabled)
r   setr   rN   rl   gateway.session_contextr   r   r   _normalize_string_set)r}   rs   
skills_cfgr   resolved_platformglobal_disabledr   s          r(   get_disabled_skill_namesr     s     F uuH%%Jj$'' uu777777 	69&''	6?455 
 ,JNN:,F,FGGO N'^^,?@@FBKK
 
 ("%:;L%M%MMMrD   c                 j    | t                      S t          | t                    r| g} d | D             S )Nc                     h | ]D}t          |                                          #t          |                                          ES r   )rG   r$   )rA   vs     r(   	<setcomp>z(_normalize_string_set.<locals>.<setcomp>  s9    ===qc!ffllnn=CFFLLNN===rD   )r   rN   rG   )valuess    r(   r   r     s=    ~uu&# ==F====rD   _EXTERNAL_DIRS_CACHEc                  T    t                                            t                       dS )u(   Test hook — drop the in-process cache.N)r   r   r   r   rD   r(   _external_dirs_cache_clearr     s&       rD   c                  P   t                      } |                                 sg S 	 |                                 }t          |           |j        f}n# t
          $ r d}Y nw xY w|+t                              |          }|t          |          S t                      }|sg S |                    d          }t          |t                    sg S |                    d          }|sg }|t          |          t          |<   |S t          |t                    r|g}t          |t                    sg S ddlm}  |            }	t                                                      }
t!                      }g }|D ]}t          |                                          }|s't$          j                            t$          j                            |                    }t-          |          }|                                s|	|z                                  }n|                                }||
k    r||v r|                                r+|                    |           |                    |           t6                              d|           |t          |          t          |<   |S )u3  Read ``skills.external_dirs`` from config.yaml and return validated paths.

    Each entry is expanded (``~`` and ``${VAR}``) and resolved to an absolute
    path.  Only directories that actually exist are returned.  Duplicates and
    paths that resolve to the local ``~/.hermes/skills/`` are silently skipped.

    Cached in-process, keyed on ``config.yaml`` mtime — the function is
    called once per skill during banner / tool-registry scans, and YAML
    parsing a non-trivial config dominates ``hermes`` cold-start time
    when the cache is absent.
    Nr   external_dirsr   )get_hermes_homez0External skills dir does not exist, skipping: %s)r
   r"   r   rG   r   r%   r   r   r{   r   rN   rl   r   r   r   r+   r   r$   r   r1   
expanduser
expandvarsr   rQ   is_diraddappendr   r   )r   r   r   r   rs   r   raw_dirsr   r   hermes_homelocal_skillsseenentryexpandedps                  r(   get_external_skills_dirsr     s    "##K 	!!&)+&6&68H%I		   			 %)))44<<F 	H%%Jj$'' 	~~o..H  .26ll +(C   :h%% 	000000!/##K!##++--LeeDF P PE

  "" 	7%%bg&8&8&?&?@@NN}} 	q))++AA		A9988:: 	PHHQKKKMM!LLKQOOOO*.v,,Y'Ms   *A A A c                  f    t                      g} |                     t                                 | S )u   Return all skill directories: local ``~/.hermes/skills/`` first, then external.

    The local dir is always first (and always included even if it doesn't exist
    yet — callers handle that).  External dirs follow in config order.
    )r   extendr   )dirss    r(   get_all_skills_dirsr   6  s0     DKK(**+++KrD   
identifierc                    | pd                                 }|s|S t          |                                          }|                                s|                    d          S 	 ddlm} t          |j                  }n# t          $ r t                      }Y nw xY w|g}	 |
                    t                                 n# t          $ r Y nw xY w|D ]6}	 t          |                    |                    c S # t          $ r Y 3w xY w	 t          |                                                    |                                                    S # t          $ r  t                               d|           |cY S w xY w)a  Normalize a skill identifier to a ``skill_view()``-safe relative path.

    Slash commands and cron jobs may store absolute paths to skills that live
    under ``~/.hermes/skills/`` (including via symlinks) or configured
    ``skills.external_dirs``. ``skill_view()`` rejects absolute names for
    security, so callers must translate trusted absolute paths to their
    relative form first.
     /r   )skills_toolu~   Skill identifier %r is an absolute path outside trusted skills roots — passing through unchanged (skill_view will reject it))r$   r   r   rQ   lstriptoolsr   
SKILLS_DIRrm   r   r   r   rG   r,   r-   r+   r   r   )r   raw_identifieridentifier_path_skills_toolprimary_roottrusted_rootsr;   s          r(   normalize_skill_lookup_namer   A  s    !&B--//N >**5577O&&(( *$$S)))(555555L344 ( ( (%''( "NM5778888      	2248899999 	 	 	H	?**,,889M9M9O9OPPQQQ   N	
 	
 	

 sI   &B BB#!C 
CC!C>>
D
DAE 'E?>E?c                 0   t          | t                    r| nt          t          |                     }	 |                                                                S # t
          t          f$ r) |                                                                cY S w xY wr?   )rN   r   rG   r   r+   r%   RuntimeErrorabsolute)r1   rR   s     r(   _resolve_for_skill_ownershipr   z  s    !$--Btt4D		??H0""$$,,...\" 0 0 0""$$--/////0s   %A 7BBc                     t          |           }t                      D ]9}t          |          }	 |                    |            dS # t          $ r Y 6w xY wdS )a  Return True when ``path`` lives under a configured external skills dir.

    ``skills.external_dirs`` are externally owned: Hermes can discover and view
    their skills, and foreground user-directed tool calls may still edit them,
    but autonomous lifecycle maintenance must treat them as read-only. This
    helper centralizes the ownership boundary so curator/reporting/tool paths do
    not each need to re-interpret the config.
    TF)r   r   r,   r-   )r1   	candidater;   resolved_roots       r(   is_external_skill_pathr     sx     -T22I(**  4T::	!!-00044 	 	 	H	5s   A
AAc                 d   |                      d          }t          |t                    si }|                     d          pi }t          |t                    si }|                     dg           |                     dg           |                     dg           |                     dg           dS )z>Extract conditional activation fields from parsed frontmatter.metadatahermesfallback_for_toolsetsrequires_toolsetsfallback_for_toolsrequires_tools)r   r   r   r   )r   rN   rl   )ro   r   r   s      r(   extract_skill_conditionsr     s    z**Hh%% \\(##)rFfd## !',CR!H!H#ZZ(;R@@$jj)=rBB **%5r::	  rD   c                    |                      d          }t          |t                    sg S |                     d          }t          |t                    sg S |                     d          }|sg S t          |t                    r|g}t          |t                    sg S g }t	                      }|D ]3}t          |t                    st          |                     dd                                                    }|r||v rUt          |                     dd                                                    }|s||d}	|                     d          }
|
|
|	d<   |                     d
          }t          |t
                    r,|                                r|                                |	d
<   n||	d
<   |                    |           |                    |	           5|S )a   Extract config variable declarations from parsed frontmatter.

    Skills declare config.yaml settings they need via::

        metadata:
          hermes:
            config:
              - key: wiki.path
                description: Path to the LLM Wiki knowledge base directory
                default: "~/wiki"
                prompt: Wiki directory path

    Returns a list of dicts with keys: ``key``, ``description``, ``default``,
    ``prompt``.  Invalid or incomplete entries are silently skipped.
    r   r   configru   r   description)ru   r   defaultNprompt)	r   rN   rl   r{   r   rG   r$   r   r   )ro   r   r   rawr   r   itemru   descr   r   prompt_texts               r(   extract_skill_config_varsr     s     z**Hh%% 	\\(##Ffd## 	
**X

C 	#t ec4   	#%FD  $%% 	$((5"%%&&,,.. 	cTkk488M2..//5577 	!
 !
 ((9%%&E)hhx((k3'' 	#K,=,=,?,? 	#)//11E(OO"E(OeMrD   c                  r   g } t                      }t                      }t                      D ]}|                                st	          |d          D ]}	 |                    d          }t          |          \  }}n# t          $ r Y 8w xY w|                    d          p|j	        j
        }t          |          |v rot          |          st          |          }	|	D ]N}
|
d         |vrBt          |          |
d<   |                     |
           |                    |
d                    O
| S )aY  Scan all enabled skills and collect their config variable declarations.

    Walks every skills directory, parses each SKILL.md frontmatter, and returns
    a deduplicated list of config var dicts.  Each dict also includes a
    ``skill`` key with the skill name for attribution.

    Disabled and platform-incompatible skills are excluded.
    rM   r   r   nameru   skill)r   r   r   r   iter_skill_index_filesr#   rv   rm   r   parentr   rG   r   r   r   r   )all_vars	seen_keysr   r   
skill_filer   ro   _
skill_nameconfig_varsvars              r(   discover_all_skill_config_varsr    si    &(HUUI'))H)++ . .
  "" 	0ZHH 	. 	.J **G*<<!23!7!7QQ    %00JJ4E4JJ:(**)+66 3K@@K" . .u:Y..#&z??CLOOC(((MM#e*---	.	.( Os   (B
BBzskills.configr   
dotted_keyc                     |                     d          }| }|D ]&}t          |t                    r||v r	||         }$ dS |S )zPWalk a nested dict following a dotted key.  Returns None if any part is missing..N)rn   rN   rl   )r   r  r/   r   rB   s        r(   _resolve_dotpathr    sZ    S!!EG  gt$$ 	dmGG44NrD   r	  c                    t                      }i }| D ]}|d         }t           d| }t          ||          }|)t          |t                    r*|                                s|                    dd          }t          |t                    rDd|v sd|v r<t          j        	                    t          j        
                    |                    }|||<   |S )aV  Resolve current values for skill config vars from config.yaml.

    Skill config is stored under ``skills.config.<key>`` in config.yaml.
    Returns a dict mapping **logical** keys (as declared by skills) to their
    current values (or the declared default if the key isn't set).
    Path values are expanded via ``os.path.expanduser``.
    ru   r  Nr   r   ~z${)r   SKILL_CONFIG_PREFIXr  rN   rG   r$   r   r   r1   r   r   )r	  r   resolvedr
  logical_keystorage_keyrX   s          r(   resolve_skill_config_valuesr  !  s     F!H & &%j,<<{<< 55=Zs33=EKKMM=GGIr**E eS!! 	Bse||tu}}G&&rw'9'9%'@'@AAE %OrD   <   c                     |                      dd          }|r4t          |                                                              d          ndS )z@Normalize a skill's description field for comparison/truncation.r   r   z'")r   rG   r$   )ro   raw_descs     r(   _normalize_skill_descriptionr  D  sE    }b11H19A3x==  &&u---rArD   c                     t          |           }|sdS t          |          t          k    r|dt          dz
           dz   S |S )zCExtract a system-prompt-length description from parsed frontmatter.r   Nrd   z...r  r8   SKILL_PROMPT_DESC_LIMITro   r   s     r(   extract_skill_descriptionr  J  sO    '44D r
4yy***0,q001E99KrD   c                 P    t          |           }t          |          t          k    S )zMTrue when the description will be truncated in the system prompt skill index.r  r  s     r(   )is_skill_description_truncated_for_promptr!  T  s!    '44Dt99...rD   filenamec              #   >  	
K   t          |           }t          |           	t          j                            |t
                    }g }t          j        |d          D ]\  }}}d|v 
||k    r&t
          |v r	|                    t
                     n||k    r	fd|D             |dd<   
fd|D             |dd<   ||v r3|                    t          j                            ||                     t          |          D ]}t          |          V  dS )u  Walk skills_dir yielding sorted paths matching *filename*.

    Excludes Hermes metadata, VCS, virtualenv/dependency, cache, and skill
    support directories. Support directories (references/templates/assets/
    scripts) can contain arbitrary markdown and even archived package
    ``SKILL.md`` files, but they are progressive-disclosure data loaded through
    ``skill_view(..., file_path=...)`` rather than active skill roots.

    M2 org mirrors (``_org/``): TOKEN-GATED resolution. Only the active org's
    subdir (per the sync-client-written ``.active_org`` marker) is walked;
    every other ``_org/<id>/`` (stale mirror from a previous org, or no
    marker at all) is pruned — leave an org and its skills stop resolving,
    without any manual cleanup.
    T)followlinksrM   Nc                      g | ]
}|k    |S r   r   )rA   d
active_orgs     r(   
<listcomp>z*iter_skill_index_files.<locals>.<listcomp>v  s    :::Q!z//q///rD   c                 <    g | ]}|t           vrr	|t          v |S r   )r@   rP   )rA   r&  has_skill_mds     r(   r(  z*iter_skill_index_files.<locals>.<listcomp>w  sE     
 
 
+++! ,&'+=&=&=  '>&=&=rD   )rG   r)   r   r1   joinr    walkremover   sortedr   )r   r"  skills_dir_strorg_rootmatchesr;   r   filesr1   r'  r*  s            @@r(   r  r  ]  s]      __N#J//Jw||N,?@@HGW^FFF 9 9dE!U*>!!&9T&A&AjFXKK+,,,,X::::$:::DG
 
 
 

 
 
QQQ uNN27<<h77888w  4jj rD   z^[a-zA-Z0-9_-]+$r   c                 X    d| vrd| fS t          |                     dd                    S )z~Split ``'namespace:skill-name'`` into ``(namespace, bare_name)``.

    Returns ``(None, name)`` when there is no ``':'``.
    rf   Nr7   )tuplern   )r   s    r(   parse_qualified_namer5    s3    
 $TzC##$$$rD   r   c                 X    | sdS t          t                              |                     S )zDCheck whether *candidate* is a valid namespace (``[a-zA-Z0-9_-]+``).F)r.   _NAMESPACE_REmatch)r   s    r(   is_valid_namespacer9    s+     u##I..///rD   )r   Nr?   )J__doc__loggingr   rh   r|   rF   r   typingr   r   r   r   r   r	   r   r
   r   r   	getLogger__name__r   r   	frozensetr@   rP   r    r!   ORG_PROVENANCE_FILEORG_BASELINE_FILErG   r)   r.   r3   r9   rJ   rI   r_   rb   rv   r   r   r   r   __annotations__r   r   r   intr   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r  r  r  r!  r  compiler7  r5  r9  r   rD   r(   <module>rE     s      				 				 



       8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 G G G G G G G G G G		8	$	$
    i  . YOPP   ! ,  ) 	4 	HSM 	 	 	 	C C$ C C C CT hsm     <@   (4. D    ( ;?   $ 4    > "s " " " "$.s .uT#s(^S-@'A . . . .h3 4    2ES#X E4 E E E EB  i : : :;; %' 4T	? ' ' '+S +T + + + +\$4S> $d $ $ $ $T AC 4c3m,d38n<= B B B   
 $sCx.        F# #sTz #SX # # # #L>SX > > > >  ;= d5c?DJ67 < < <   P$t* P P P PfT$Z    6C 6C 6 6 6 6r0$ 0 0 0 0D    .$sCx. T#t)_    (84S> 8d4S>>R 8 8 8 8v$T#s(^(< $ $ $ $T & 	T#s(^ 	 	 	 	 	d38n%	#s(^   @  Bd38n B B B B B4S> c    /4S> /d / / / /#t #s # # # #P 
.//%s %uXc]C-?'@ % % % %0(3- 0D 0 0 0 0 0 0rD   