Coverage for src / agent / skills / documentation_index.py: 100%
23 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 14:30 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 14:30 +0000
1# Copyright 2025-2026 Microsoft Corporation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""In-memory skill documentation index for runtime context injection.
17This module provides SkillDocumentationIndex for runtime documentation management,
18separate from SkillRegistry which handles persistent install metadata.
19"""
21from dataclasses import dataclass
22from typing import Any
24from agent.skills.manifest import SkillManifest
27@dataclass
28class SkillDocumentation:
29 """Runtime documentation for a single skill."""
31 name: str
32 brief_description: str
33 triggers: dict[str, list[str]] # {keywords: [], verbs: [], patterns: []}
34 instructions: str
36 def to_dict(self) -> dict[str, Any]:
37 """Convert to dictionary for context provider."""
38 return {
39 "name": self.name,
40 "brief_description": self.brief_description,
41 "triggers": self.triggers,
42 "instructions": self.instructions,
43 }
46class SkillDocumentationIndex:
47 """In-memory index of skill documentation for context injection.
49 Separate from SkillRegistry to avoid mixing persistent install
50 metadata with runtime documentation. This index is built at agent
51 initialization and used by SkillContextProvider for progressive disclosure.
53 Example:
54 >>> skill_docs = SkillDocumentationIndex()
55 >>> skill_docs.add_skill("osdu-quality", manifest)
56 >>> skill_docs.has_skills()
57 True
58 >>> skill_docs.count()
59 1
60 >>> metadata = skill_docs.get_all_metadata()
61 """
63 def __init__(self) -> None:
64 self._skills: dict[str, SkillDocumentation] = {}
66 def add_skill(self, name: str, manifest: SkillManifest) -> None:
67 """Add skill documentation from manifest.
69 Args:
70 name: Canonical skill name (normalized)
71 manifest: Parsed SkillManifest with instructions and triggers
72 """
73 # Convert triggers to dict format with consistent structure
74 triggers_dict = {
75 "keywords": manifest.triggers.keywords if manifest.triggers else [],
76 "verbs": manifest.triggers.verbs if manifest.triggers else [],
77 "patterns": manifest.triggers.patterns if manifest.triggers else [],
78 }
80 self._skills[name] = SkillDocumentation(
81 name=name,
82 brief_description=manifest.brief_description or manifest.description[:80],
83 triggers=triggers_dict,
84 instructions=manifest.instructions,
85 )
87 def get_all_metadata(self) -> list[dict[str, Any]]:
88 """Get all skill metadata for matching.
90 Returns:
91 List of skill metadata dictionaries with name, brief_description,
92 triggers, and instructions fields.
93 """
94 return [skill.to_dict() for skill in self._skills.values()]
96 def has_skills(self) -> bool:
97 """Check if any skills are loaded.
99 Returns:
100 True if at least one skill is in the index.
101 """
102 return bool(self._skills)
104 def count(self) -> int:
105 """Get number of loaded skills.
107 Returns:
108 Number of skills in the index.
109 """
110 return len(self._skills)