Skip to content

Commit d829a15

Browse files
committed
refactor: streamline skill loading and rendering
- Update skill loading to include body in SkillMetadata. - Remove load_skill_body function and related references. - Refactor skill rendering to use render_compact_skills. - Adjust tests to reflect changes in skill loading and rendering logic. - Simplify project configuration by modifying extend-exclude patterns. Signed-off-by: Frost Ming <me@frostming.com>
1 parent 9c04aea commit d829a15

15 files changed

Lines changed: 50 additions & 135 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,7 @@ testpaths = ["tests"]
9292
target-version = "py312"
9393
line-length = 120
9494
fix = true
95-
extend-exclude = [
96-
"src/bub/skills",
97-
"backup",
98-
]
95+
extend-exclude = ["src/bub/skills/**/scripts/*.py"]
9996

10097
[tool.ruff.lint]
10198
select = [

src/bub/app/runtime.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from bub.config.settings import Settings
2424
from bub.core import AgentLoop, InputRouter, LoopResult, ModelRunner
2525
from bub.integrations.republic_client import build_llm, build_tape_store, read_workspace_agents_prompt
26-
from bub.skills import SkillMetadata, discover_skills, load_skill_body
26+
from bub.skills.loader import SkillMetadata, discover_skills
2727
from bub.tape import TapeService
2828
from bub.tools import ProgressiveToolView, ToolRegistry
2929
from bub.tools.builtin import register_builtin_tools
@@ -98,11 +98,6 @@ def discover_skills(self) -> list[SkillMetadata]:
9898
return discovered
9999
return [skill for skill in discovered if skill.name.casefold() in self._allowed_skills]
100100

101-
def load_skill_body(self, skill_name: str) -> str | None:
102-
if self._allowed_skills is not None and skill_name.casefold() not in self._allowed_skills:
103-
return None
104-
return load_skill_body(skill_name, self.workspace)
105-
106101
def get_session(self, session_id: str) -> SessionRuntime:
107102
existing = self._sessions.get(session_id)
108103
if existing is not None:
@@ -128,7 +123,6 @@ def get_session(self, session_id: str) -> SessionRuntime:
128123
tool_view=tool_view,
129124
tools=registry.model_tools(),
130125
list_skills=self.discover_skills,
131-
load_skill_body=self.load_skill_body,
132126
model=self.settings.model,
133127
max_steps=self.settings.max_steps,
134128
max_tokens=self.settings.max_tokens,

src/bub/core/model_runner.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from bub.core.router import AssistantRouteResult, InputRouter
1515
from bub.skills.loader import SkillMetadata
16-
from bub.skills.view import render_skill_prompt
16+
from bub.skills.view import render_compact_skills
1717
from bub.tape.service import TapeService
1818
from bub.tools.progressive import ProgressiveToolView
1919
from bub.tools.view import render_tool_prompt_block
@@ -56,7 +56,6 @@ def __init__(
5656
tool_view: ProgressiveToolView,
5757
tools: list[Tool],
5858
list_skills: Callable[[], list[SkillMetadata]],
59-
load_skill_body: Callable[[str], str | None],
6059
model: str,
6160
max_steps: int,
6261
max_tokens: int,
@@ -69,14 +68,13 @@ def __init__(
6968
self._tool_view = tool_view
7069
self._tools = tools
7170
self._list_skills = list_skills
72-
self._load_skill_body = load_skill_body
7371
self._model = model
7472
self._max_steps = max_steps
7573
self._max_tokens = max_tokens
7674
self._model_timeout_seconds = model_timeout_seconds
7775
self._base_system_prompt = base_system_prompt.strip()
7876
self._get_workspace_system_prompt = get_workspace_system_prompt
79-
self._expanded_skills: dict[str, str] = {}
77+
self._expanded_skills: set[str] = set()
8078

8179
def reset_context(self) -> None:
8280
"""Clear volatile model-side context caches within one session."""
@@ -192,7 +190,7 @@ def _render_system_prompt(self) -> str:
192190
blocks.append(_runtime_contract())
193191
blocks.append(render_tool_prompt_block(self._tool_view))
194192

195-
compact_skills = render_skill_prompt(self._list_skills())
193+
compact_skills = render_compact_skills(self._list_skills(), self._expanded_skills)
196194
if compact_skills:
197195
blocks.append(compact_skills)
198196
return "\n\n".join(block for block in blocks if block.strip())
@@ -206,11 +204,7 @@ def _activate_hints(self, text: str) -> None:
206204
skill = skill_index.get(hint.casefold())
207205
if skill is None:
208206
continue
209-
if skill.name in self._expanded_skills:
210-
continue
211-
body = self._load_skill_body(skill.name)
212-
if body:
213-
self._expanded_skills[skill.name] = body
207+
self._expanded_skills.add(skill.name)
214208

215209
def _build_skill_index(self) -> dict[str, SkillMetadata]:
216210
return {skill.name.casefold(): skill for skill in self._list_skills()}

src/bub/core/router.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@ def _resolve_internal_name(name: str) -> str:
316316
aliases = {
317317
"tool": "tool.describe",
318318
"tape": "tape.info",
319-
"skill": "skills.describe",
320319
}
321320
return aliases.get(name, name)
322321

src/bub/skills/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Skill discovery package."""
22

3-
from bub.skills.loader import SkillMetadata, discover_skills, load_skill_body
4-
from bub.skills.view import render_skill_prompt
3+
from bub.skills.loader import SkillMetadata, discover_skills
4+
from bub.skills.view import render_compact_skills
55

6-
__all__ = ["SkillMetadata", "discover_skills", "load_skill_body", "render_skill_prompt"]
6+
__all__ = ["SkillMetadata", "discover_skills", "render_compact_skills"]

src/bub/skills/loader.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class SkillMetadata:
1919
name: str
2020
description: str
2121
location: Path
22+
body: str
2223
metadata: dict[str, Any] | None = None
2324
source: str = "unknown"
2425

@@ -49,19 +50,6 @@ def discover_skills(workspace_path: Path) -> list[SkillMetadata]:
4950
return sorted(by_name.values(), key=lambda item: item.name.casefold())
5051

5152

52-
def load_skill_body(name: str, workspace_path: Path) -> str | None:
53-
"""Load full SKILL.md body for one skill name."""
54-
55-
lowered = name.casefold()
56-
for skill in discover_skills(workspace_path):
57-
if skill.name.casefold() == lowered:
58-
try:
59-
return skill.location.read_text(encoding="utf-8")
60-
except OSError:
61-
return None
62-
return None
63-
64-
6553
def _read_skill(skill_dir: Path, *, source: str) -> SkillMetadata | None:
6654
skill_file = skill_dir / SKILL_FILE_NAME
6755
if not skill_file.is_file():
@@ -72,33 +60,36 @@ def _read_skill(skill_dir: Path, *, source: str) -> SkillMetadata | None:
7260
except OSError:
7361
return None
7462

75-
metadata = _parse_frontmatter(content)
63+
metadata, body = _parse_frontmatter(content)
7664
name = str(metadata.get("name") or skill_dir.name).strip()
7765
description = str(metadata.get("description") or "No description provided.").strip()
7866
meta = cast(dict[str, Any], metadata.get("metadata"))
7967

8068
if not name:
8169
return None
8270

83-
return SkillMetadata(name=name, description=description, location=skill_file.resolve(), source=source, metadata=meta)
71+
return SkillMetadata(
72+
name=name, description=description, location=skill_file.resolve(), source=source, metadata=meta, body=body
73+
)
8474

8575

86-
def _parse_frontmatter(content: str) -> dict[str, object]:
76+
def _parse_frontmatter(content: str) -> tuple[dict[str, object], str]:
8777
lines = content.splitlines()
8878
if not lines or lines[0].strip() != "---":
89-
return {}
79+
return {}, content
9080

9181
for idx, line in enumerate(lines[1:], start=1):
9282
if line.strip() == "---":
9383
payload = "\n".join(lines[1:idx])
9484
try:
9585
parsed = yaml.safe_load(payload)
9686
except yaml.YAMLError:
97-
return {}
87+
parsed = {}
88+
body = "\n".join(lines[idx + 1 :])
9889
if isinstance(parsed, dict):
99-
return {str(key).lower(): value for key, value in parsed.items()}
100-
return {}
101-
return {}
90+
return {str(key).lower(): value for key, value in parsed.items()}, body
91+
return {}, body
92+
return {}, content
10293

10394

10495
def _builtin_skills_root() -> Path:

src/bub/skills/skill-creator/scripts/quick_validate.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,7 @@ def validate_skill(skill_path):
7777
if len(name) > MAX_SKILL_NAME_LENGTH:
7878
return (
7979
False,
80-
f"Name is too long ({len(name)} characters). "
81-
f"Maximum is {MAX_SKILL_NAME_LENGTH} characters.",
80+
f"Name is too long ({len(name)} characters). Maximum is {MAX_SKILL_NAME_LENGTH} characters.",
8281
)
8382

8483
description = frontmatter.get("description", "")

src/bub/skills/telegram/scripts/telegram_edit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def main():
9090
sys.exit(1)
9191

9292
try:
93-
result = edit_message(bot_token, args.chat_id, args.message_id, args.text)
93+
edit_message(bot_token, args.chat_id, args.message_id, args.text)
9494
print(f"✅ Message {args.message_id} edited successfully")
9595
except requests.HTTPError as e:
9696
print(f"❌ HTTP Error: {e}")

src/bub/skills/telegram/scripts/telegram_send.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
A simple script to send messages via Telegram Bot API.
1414
Uses telegramify_markdown to convert markdown to Telegram MarkdownV2 format.
1515
"""
16+
1617
import argparse
1718
import os
1819
import sys
@@ -120,9 +121,7 @@ def send_message(
120121

121122
def main():
122123
parser = argparse.ArgumentParser(description="Send messages via Telegram Bot API (auto-converts to MarkdownV2)")
123-
parser.add_argument(
124-
"--chat-id", "-c", required=True, help="Target chat ID"
125-
)
124+
parser.add_argument("--chat-id", "-c", required=True, help="Target chat ID")
126125
parser.add_argument(
127126
"--message",
128127
"-m",

src/bub/skills/view.py

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,27 @@
55
from bub.skills.loader import SkillMetadata
66

77

8-
def render_skill_prompt(skills: list[SkillMetadata]) -> str:
8+
def render_compact_skills(skills: list[SkillMetadata], expanded_skills: set[str]) -> str:
99
"""Render compact skill metadata for system prompt."""
1010

1111
if not skills:
1212
return ""
13-
14-
channel_skills: dict[str, SkillMetadata] = {}
15-
16-
lines = ["<available_skills>"]
13+
channel_skills: list[SkillMetadata] = [
14+
skill for skill in skills if skill.metadata and skill.metadata.get("channel")
15+
]
16+
lines = ["<basic_skills>"]
1717
for skill in skills:
1818
if skill.metadata and skill.metadata.get("channel"):
19-
channel_skills[skill.metadata["channel"]] = skill
20-
else:
21-
lines.extend([
22-
f" <skill>",
23-
f" <name>{skill.name}</name>",
24-
f" <description>{skill.description}</description>",
25-
f" <location>{skill.location}</location>",
26-
f" </skill>",
27-
])
28-
lines.append("</available_skills>")
29-
19+
continue
20+
lines.append(f"### [{skill.name}]({skill.location})\n{skill.description} ###")
21+
if skill.name in expanded_skills:
22+
lines.append(f"{skill.body.rstrip()}\n")
23+
lines.append("</basic_skills>")
3024
if channel_skills:
31-
channel_lines = ["<channel_skills>"]
32-
for channel, skill in channel_skills.items():
33-
channel_lines.extend([
34-
f" <channel_skill>",
35-
f" <name>{channel}</name>",
36-
f" <description>{skill.description}</description>",
37-
f" <location>{skill.location}</location>",
38-
f" </channel_skill>",
39-
])
40-
channel_lines.append("</channel_skills>")
41-
lines = channel_lines + lines
25+
lines.append("<channel_skills>")
26+
for skill in channel_skills:
27+
lines.append(f"### [{skill.name}]({skill.location})\n{skill.description} ###")
28+
if skill.name in expanded_skills:
29+
lines.append(f"{skill.body.rstrip()}\n")
30+
lines.append("</channel_skills>")
4231
return "\n".join(lines)

0 commit comments

Comments
 (0)