
    sj$                        U d Z ddlZddlZddlZddlZddlZddl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e0e0f         e
ee	f         f         e,d%<   d[d&Z1de
ee	f         fd'Z2d\d(edz  dee         fd)Z3dee         fd*Z4dee         fd+Z5i Z6e
eee0f         ee         f         e,d,<   d[d-Z7dee         fd.Z8dee         fd/Z9ej:        ;                    d0d1          ej:        ;                    d2d1          fZ<d3Z=d\d4ee         dee         fd5Z>dee         fd6Z?dede fd7Z@dedee         fd8ZAdee         fd9ZBdeeee0f                  fd:ZCdee         fd;ZDd<ZEi ZFe
ee f         e,d=<   defd>ZGde fd?ZHd[d@ZIdAefdBZJdCedefdDZKdefdEZLde fdFZMde
ee	f         de
eef         fdGZNde
ee	f         dee
ee	f                  fdHZOdee
ee	f                  fdIZPdJZQdKe
ee	f         dLefdMZRdNee
ee	f                  de
ee	f         fdOZSdPZTde
ee	f         defdQZUde
ee	f         defdRZVde
ee	f         de fdSZWdedTefdUZX ejY        dV          ZZdWedeee         ef         fdXZ[dYee         de fdZZ\dS )]a  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).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      5/home/agent/.hermes/hermes-agent/agent/skill_utils.pyread_active_org_idr*   G   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_pathr4   S   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.   lenr0   r!   r1   s      r)   org_id_of_pathr:   \   s    4jj  ""..tJ/?/?/G/G/I/IJJZ    tt
39~~sy|/BBBy|4r5   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>v   s(      ==tt**======    r;   )r0   AttributeErrorpathlibr>   stranyis_skill_support_path)r2   r<   r0   r>   s       r)   is_excluded_skill_pathrK   g   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   rH   r0   	enumerateSKILL_SUPPORT_DIRSis_absoluter#   )r2   r<   path_objr0   idxrC   
skill_roots          r)   rJ   rJ   {   s     "$--Btt4D		??HNE uSbSz**  	T)))SAXX5#;'
H$8$8$:$:
*J#++-- 	44	5rE   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)rY   loaderyamls    r)   _loadzyaml_load.<locals>._load   s    99U69222rE   )_yaml_load_fnr^   getattr
SafeLoaderrH   )rV   r_   r]   r^   s     @@r)   	yaml_loadrc      sl     }d33Ft	3 	3 	3 	3 	3 	3 	3 	3 !!!rE   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   ﻿r8   Nz---z
\n---\s*\n   
:)
startswithresearchstartendrc   rO   dict	Exceptionr%   split)	rV   frontmatterbody	end_matchyaml_contentparsedlinekeyrY   s	            r)   parse_frontmatterrw      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)rO   listsysplatformr   rH   lowerr%   PLATFORM_MAPgetrh   )rx   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5rE   rp   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.
    rx   )r   r   )rp   s    r)   skill_matches_platformr      s    ( '{{'C'CDDDrE   >   s6dockerkanban_ENV_DETECT_CACHEenvc                 F   | dk    r| t           v rt           |          S d}| dk    rt          j        d          st          j        d          r$	 ddlm}  |            }n# t
          $ r d}Y nw xY w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 )aq  Return True when the named runtime environment is currently active.

    Cached per process, EXCEPT ``kanban``: that verdict is context-dependent
    (a delegate_task child or an in-process cron job sees the worker's
    HERMES_KANBAN_* vars without owning them), so caching it process-wide would
    freeze whichever context asked first and leak it to the others.
    r   THERMES_KANBAN_TASKHERMES_KANBAN_BOARDr   )"is_dispatcher_owned_worker_contextF)_profile_has_kanban_toolsetr   )is_containerr   z/run/s6z/package/admin/s6-overlay)r   osgetenvagent.delegation_contextr   rn   tools.kanban_toolsr   r/   hermes_constantsr   r2   isdir)r   resultr   _owns_dispatcher_taskr   r   s         r)   _detect_environmentr   "  s    h3"333 %%F
h 9)** 	*bi8M.N.N 	*-      )K(J(L(L%% - - -(,%%%- %*!  	FFJJJJJJ99;;<<   		555555!\^^FF 	 	 	FFF		
 y)) 
RW]]'.
 .
 $cMs6   A A.-A.9B B&%B&0C CC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   rO   r|   rH   r   r%   _KNOWN_ENVIRONMENTSr   )rp   r   r   r   s       r)   skill_matches_environmentr   `  s    , ??>22L tlD)) &$~  XX^^%%++--
 	00044z** 	44	5rE   _RAW_CONFIG_CACHEc                  8    t                                            dS )u/   Test hook — drop the shared raw config cache.N)r   clear rE   r)   _raw_config_cache_clearr     s    rE   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#   statrH   st_mtime_nsst_sizer&   r   r   rc   r$   rn   loggerdebugrO   rm   r   )config_pathr   	cache_keycachedrt   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   rO   rm   gateway.session_contextr   r   r   _normalize_string_set)r~   rt   
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rE   c                    | g S t          | t                    r||                                 }|                    d          rP	 t	          j        |          }n# t          t          f$ r d}Y nw xY wt          |t                    rd |D             S | gS t          | t          t          t          t          f          rd | D             S g S )a  Normalize a config value that may hold a JSON-array string into a list.

    ``hermes config set`` and JSON-mode editor saves store lists as quoted
    JSON strings (``'["a","b"]'`` or the Python-literal ``"['a']"``). Treating
    such a string as a single name makes a curated disabled list silently
    filter nothing (#86661); parsing it restores the intended list. A scalar
    string still means one name (#13026).
    N[c                 ,    g | ]}t          |          S r   rH   rB   items     r)   
<listcomp>z,parse_config_string_list.<locals>.<listcomp>  s    555dD		555rE   c                 ,    g | ]}t          |          S r   r   r   s     r)   r   z,parse_config_string_list.<locals>.<listcomp>  s    ,,,dD		,,,rE   )rO   rH   r%   rh   astliteral_evalr.   SyntaxErrorr|   tupler   	frozenset)rY   strippedrt   s      r)   parse_config_string_listr     s     }	% 	;;==s## 	6)(33,   &$'' 655f5555w%$sI677 -,,e,,,,Is   A A/.A/c                 4    d t          |           D             S )Nc                 ^    h | ]*}|                                 |                                 +S r   )r%   )rB   names     r)   	<setcomp>z(_normalize_string_set.<locals>.<setcomp>  s-    VVVTVDJJLLVVVrE   )r   )valuess    r)   r   r     s    VV%=f%E%EVVVVrE   _EXTERNAL_DIRS_CACHEc                  T    t                                            t                       dS )u(   Test hook — drop the in-process cache.N)r   r   r   r   rE   r)   _external_dirs_cache_clearr     s&       rE   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   rH   r   r&   r   r   r|   r   rO   rm   r   r   r   r,   r   r%   r   r2   
expanduser
expandvarsr   rR   is_diraddappendr   r   )r   r   r   r   rt   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.

    NOTE: trusted project-local dirs (``./.hermes/skills`` at the git root) are
    NOT part of this list — they have *higher* precedence than the local dir,
    so callers that need them use :func:`get_project_skills_dirs` and scan
    those roots first. See ``get_scan_ordered_skills_dirs`` for the full
    precedence-ordered list.
    )r   extendr   dirss    r)   get_all_skills_dirsr   ]  s0     DKK(**+++KrE   z.hermesr   z.agents@   rk   c                 
   	 | Ct           j                            d          }|rt          |          nt          j                    } t          |                                           }n# t          $ r Y dS w xY wt          j                                                    }t          t                    D ]O}	 |dz  
                                r||k    r dS |c S n# t          $ r Y  dS w xY w|j        |k    r dS |j        }PdS )u  Locate the enclosing project root: nearest ancestor containing ``.git``.

    Returns None when cwd is not inside a git checkout. ``.git`` may be a dir
    (normal clone) or a file (worktree/submodule) — both count.

    When *start* is not given, the surface's working directory wins over the
    process cwd: ``TERMINAL_CWD`` is the same per-surface workdir the terminal
    tool and cron jobs use (a cron job sets it from its per-job ``workdir``
    without chdir'ing the scheduler process). This is what lets
    non-interactive surfaces inherit a prior interactive trust decision by
    project identity — and a surface with no workdir in a trusted repo simply
    resolves no project and loads nothing (#48975).
    NTERMINAL_CWDr   )r   environr   r   cwdr,   r&   homerange_PROJECT_ROOT_MAX_DEPTHr#   parent)rk   env_cwdcurr   _s        r)   find_project_rootr     s(   =jnn^44G%,<DMMM$(**E5kk!!##   tt9;;  D*++  	f$$&&  $;;44


  	 	 	444	:44j4s*   A&A) )
A76A76CC
C*)C*c            
         t                      } | st                      S |                     d          }t          |t                    st                      S |                    d          }t          |t
                    r|g}t          |t                    st                      S t                      }|D ]}t          |                                          }|s&	 |                    t          t          j                            t          j                            |                                                                         # t          $ r Y w xY w|S )zKResolved set of trusted project roots from ``skills.trusted_project_dirs``.r   trusted_project_dirs)r   r   r   rO   rm   rH   r|   r%   r   r   r   r2   r   r   r,   r&   )rt   r   rawr   r   s        r)   !_project_trusted_dirs_from_configr     s=   F uuH%%Jj$'' uu
../
0
0C#s ec4   uuF  E

  "" 		JJtBG..rw/A/A%/H/HIIJJRRTTUUUU 	 	 	H	Ms   A.E
EEc                     	 t          |                                           t                      v S # t          $ r Y dS w xY w)z>True when *root* is listed in ``skills.trusted_project_dirs``.F)r   r,   r   r&   r;   s    r)   is_project_root_trustedr     sM    Dzz!!##'H'J'JJJ   uus   .1 
??c                 0   t                                                      }g }t          D ]k}| |z  }	 |                                r?|                                |k    r'|                    |                                           \# t
          $ r Y hw xY w|S )a  Existing skill dirs under *root*, excluding the profile's own skills dir.

    The exclusion matters when HERMES_HOME itself lives inside a git checkout:
    ``<root>/.hermes/skills`` would otherwise double as both the profile-local
    and the project tier.
    )r   r,   PROJECT_SKILLS_SUBDIRSr   r   r&   )r<   r   r   subcands        r)   _candidate_project_skills_dirsr    s     "##++--LD%  cz	{{}} ,<!?!?DLLNN+++ 	 	 	H	Ks   AB
BBc                  6   t                      } t          | t                    r|                     d          nd}t          |t                    r|                    d          du rg S t	                      }|g S t          |          sg S t          |          S )a   Trusted project-local skill dirs for the current cwd (may be empty).

    Empty when: not in a git checkout, no project skills dirs exist, project
    discovery is disabled (``skills.project_discovery: false``), or the
    project root is not trusted.
    r   Nproject_discoveryF)r   rO   rm   r   r   r   r  )rt   r   r<   s      r)   get_project_skills_dirsr    s     F)3FD)A)AKH%%%tJ*d## 
7J(K(Ku(T(T	D|	"4(( 	)$///rE   c            	         t                      } t          | t                    r|                     d          nd}t          |t                    r|                    d          du rdS t	                      }|t          |          rdS d}t          |          D ]=}	 |t          d t          |d          D                       z  }.# t          $ r Y :w xY w|dk    rdS ||fS )a  When cwd's project has skills but is NOT trusted: (root, skill_count).

    Used by the CLI to print a one-line notice pointing at
    ``hermes skills trust``. Returns None when there is nothing to notify
    about (no project, no skills, already trusted, or discovery disabled).
    r   Nr  Fr   c              3      K   | ]}d V  dS )r8   Nr   )rB   r   s     r)   rD   z4get_untrusted_project_skills_root.<locals>.<genexpr>  s"      JJqJJJJJJrE   rN   )
r   rO   rm   r   r   r   r  sumiter_skill_index_filesr&   )rt   r   r<   countds        r)   !get_untrusted_project_skills_rootr    s    F)3FD)A)AKH%%%tJ*d## 
7J(K(Ku(T(TtD|.t44|tE+D11  	SJJ$:1j$I$IJJJJJJEE 	 	 	H	zzt;s   *C		
CCc                      t          t                                } |                     t                                 |                     t                                 | S )u   All skill dirs in precedence order: project → local → external.

    First-wins name deduplication over this order gives project skills
    priority over profile-local and external ones.
    )r|   r  r   r   r   r   r   s    r)   get_scan_ordered_skills_dirsr    sN     '))**DKK  !!!KK(**+++KrE   zproject-local_PROJECT_QUARANTINE_CACHEc                  .    ddl m}   |             dz  dz  S )Nr   r   cacheproject_skill_scans)r   r   r   s    r)   _project_scan_cache_dirr  @  s-    000000?w&)>>>rE   c                    t          |           j        }	 t          |                                          }n# t          $ r t          |          }Y nw xY wt
                              |          }||S 	 ddlm}  ||t          t                                \  }}|j        dk    }|r!t                              d||j                   n/# t          $ r" t                              d|d	           d}Y nw xY w|t
          |<   |S )
u  True when a project skill's scan verdict is ``dangerous``.

    Fail-closed: a scanner crash or missing scanner quarantines the skill
    (repo-sourced content with no completed scan must not load). Non-project
    callers should not call this — it scans unconditionally.
    Nr   )scan_skill_cached)source	cache_dir	dangerousu8   Project skill quarantined (verdict=dangerous): %s — %su<   Project skill scan failed — quarantining (fail closed): %sT)exc_info)r   r   rH   r,   r&   r  r   tools.skills_guardr  _PROJECT_SCAN_SOURCEr  verdictr   warningsummaryrn   )skill_md	skill_dirrv   r   r  r   _provquarantineds           r)   is_quarantined_project_skillr$  F  sR    X%I)##%%&&   )nn&**3//F888888))'-//
 
 

 n3 	NNJ  
    J 	 	
 	
 	

  &1c"s"   !8 AA6AC )C98C9c                  8    t                                            dS )z
Test hook.N)r  r   r   rE   r)   _project_quarantine_cache_clearr&  o  s    ##%%%%%rE   project_dirc              #   X   K   t          | d          D ]}t          |          r|V  dS )a!  Yield non-quarantined SKILL.md files under a trusted project dir.

    The single iteration chokepoint for the project tier: every consumer
    (index, skills_list, slash commands) iterates through here so the
    quarantine cannot be bypassed by a new call site forgetting the check.
    rN   N)r
  r$  )r'  r   s     r)   iter_project_skill_filesr)  t  sI       +;
CC  '11 	 rE   
identifierc                 j   | 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	 |
                    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   rR   lstriptoolsr.  
SKILLS_DIRrn   r   r   r  r   rH   r-   r.   r,   r   r   )r*  raw_identifieridentifier_path_skills_toolprimary_roottrusted_rootsr<   s          r)   normalize_skill_lookup_namer7    s$    !&B--//N >**5577O&&(( *$$S)))(555555L344 ( ( (%''( "NM4667777   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	
 	
 	

 s[   &B BB#!C 
CC!C8 8
DD!D11
D>=D>AF 'F21F2c                 0   t          | t                    r| nt          t          |                     }	 |                                                                S # t
          t          f$ r) |                                                                cY S w xY wr@   )rO   r   rH   r   r,   r&   RuntimeErrorabsolute)r2   rS   s     r)   _resolve_for_skill_ownershipr;    s    !$--Btt4D		??H0""$$,,...\" 0 0 0""$$--/////0s   %A 7BBc                 8   t          |           }t          t                                }	 |                    t	                                 n# t
          $ r Y nw xY w|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   r  rn   r-   r.   )r2   	candidaterootsr<   resolved_roots        r)   is_external_skill_pathr@    s     -T22I57788E,..////     4T::	!!-00044 	 	 	H	5s#   !A 
AA2B


BB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)rD  rE  rF  rG  )r   rO   rm   )rp   rB  rC  s      r)   extract_skill_conditionsrH    s    z**Hh%% \\(##)rFfd## !',CR!H!H#ZZ(;R@@$jj)=rBB **%5r::	  rE   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.
    rB  rC  configrv   r,  description)rv   rK  defaultNprompt)	r   rO   rm   r|   r   rH   r%   r   r   )rp   rB  rC  r   r   r   r   rv   descr   rL  prompt_texts               r)   extract_skill_config_varsrP    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MrE   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.
    rN   r   r   r   rv   skill)r   r   r   r   r
  r$   rw   rn   r   r   r   rH   r   rP  r   r   )all_vars	seen_keysr   r   
skill_filer   rp   r   
skill_nameconfig_varsvars              r)   discover_all_skill_config_varsrY  3  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.configrJ  
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)ro   rO   rm   )rJ  rZ  r0   r   rC   s        r)   _resolve_dotpathr]  `  sZ    S!!EG  gt$$ 	dmGG44NrE   rW  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``.
    rv   r\  NrL  r,  ~z${)r   SKILL_CONFIG_PREFIXr]  rO   rH   r%   r   r   r2   r   r   )rW  rJ  resolvedrX  logical_keystorage_keyrY   s          r)   resolve_skill_config_valuesrd  l  s     F!H & &%j,<<{<< 55=Zs33=EKKMM=GGIr**E eS!! 	Bse||tu}}G&&rw'9'9%'@'@AAE %OrE   <   c                     |                      dd          }|r4t          |                                                              d          ndS )z@Normalize a skill's description field for comparison/truncation.rK  r,  z'")r   rH   r%   )rp   raw_descs     r)   _normalize_skill_descriptionrh    sE    }b11H19A3x==  &&u---rArE   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,  Nre   z...rh  r9   SKILL_PROMPT_DESC_LIMITrp   rN  s     r)   extract_skill_descriptionrm    sO    '44D r
4yy***0,q001E99KrE   c                 P    t          |           }t          |          t          k    S )zMTrue when the description will be truncated in the system prompt skill index.rj  rl  s     r)   )is_skill_description_truncated_for_promptro    s!    '44Dt99...rE   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)followlinksrN   Nc                      g | ]
}|k    |S r   r   )rB   r  
active_orgs     r)   r   z*iter_skill_index_files.<locals>.<listcomp>  s    :::Q!z//q///rE   c                 <    g | ]}|t           vrr	|t          v |S r   )rA   rQ   )rB   r  has_skill_mds     r)   r   z*iter_skill_index_files.<locals>.<listcomp>  sE     
 
 
+++! ,&'+=&=&=  '>&=&=rE   )rH   r*   r   r2   joinr!   walkremover   sortedr   )r   rp  skills_dir_strorg_rootmatchesr<   r   filesr2   rt  rv  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 rE   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 ``':'``.
    rg   Nr8   )r   ro   )r   s    r)   parse_qualified_namer    s3    
 $TzC##$$$rE   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_namespacer    s+     u##I..///rE   )r   Nr@   )]__doc__r   loggingr   ri   r}   rG   r   typingr   r   r   r   r   r	   r   r
   r   r   	getLogger__name__r   r   r   rA   rQ   r!   r"   ORG_PROVENANCE_FILEORG_BASELINE_FILErH   r*   r/   r4   r:   rK   rJ   r`   rc   rw   r   r   r   r   __annotations__r   r   r   intr   r   r   r   r   r   r   r   r   r2   rw  r   r   r   r   r   r  r  r  r  r  r  r  r$  r&  r)  r7  r;  r@  rH  rP  rY  r`  r]  rd  rk  rh  rm  ro  r
  compiler  r  r  r   rE   r)   <module>r     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tCy    4WSX W W W W ;= d5c?DJ67 < < <   P$t* P P P PfT$Z    ` GLLH%%GLLH%%   # #Xd^ #x~ # # # #L3t9    2$ 4     $t*    &0d 0 0 0 0&8E$)4D+E    2	d4j 	 	 	 	< '  .0 4T	? / / /? ? ? ? ?&d & & & &R& & & &

$ 
 
 
 
:C :C : : : :z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 0rE   