lint fixes

This commit is contained in:
levxn
2026-03-17 00:32:14 +05:30
parent ce2525b59c
commit 6ed6e5b286
15 changed files with 167 additions and 156 deletions
@@ -8,7 +8,6 @@ from __future__ import annotations
import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
+2 -1
View File
@@ -1338,8 +1338,8 @@ class AgentRunner:
skills_catalog_prompt = ""
protocols_prompt = ""
try:
from framework.skills.config import SkillsConfig
from framework.skills.catalog import SkillCatalog
from framework.skills.config import SkillsConfig
from framework.skills.defaults import DefaultSkillManager
from framework.skills.discovery import DiscoveryConfig, SkillDiscovery
@@ -1355,6 +1355,7 @@ class AgentRunner:
# Trust-gate project-scope skills (AS-13)
from framework.skills.trust import TrustGate
discovered = TrustGate(interactive=self._interactive).filter_and_gate(
discovered, project_dir=self.agent_path
)
+1 -1
View File
@@ -11,7 +11,7 @@ from framework.skills.defaults import DefaultSkillManager
from framework.skills.discovery import DiscoveryConfig, SkillDiscovery
from framework.skills.models import TrustStatus
from framework.skills.parser import ParsedSkill, parse_skill_md
from framework.skills.trust import TrustGate, TrustedRepoStore
from framework.skills.trust import TrustedRepoStore, TrustGate
__all__ = [
"DefaultSkillConfig",
+1 -3
View File
@@ -65,9 +65,7 @@ class SkillCatalog:
"""
# Filter out framework-scope skills (default skills) — they're
# injected via the protocols prompt, not the catalog
community_skills = [
s for s in self._skills.values() if s.source_scope != "framework"
]
community_skills = [s for s in self._skills.values() if s.source_scope != "framework"]
if not community_skills:
return ""
+3 -5
View File
@@ -20,9 +20,7 @@ def register_skill_commands(subparsers) -> None:
skill_sub = skill_parser.add_subparsers(dest="skill_command", required=True)
# hive skill list
list_parser = skill_sub.add_parser(
"list", help="List discovered skills across all scopes"
)
list_parser = skill_sub.add_parser("list", help="List discovered skills across all scopes")
list_parser.add_argument(
"--project-dir",
default=None,
@@ -117,6 +115,6 @@ def cmd_skill_trust(args) -> int:
store.trust(repo_key, project_path=str(project_path))
print(f"✓ Trusted: {repo_key}")
print(f" Stored in ~/.hive/trusted_repos.json")
print(f" Skills from this repository will load without prompting in future runs.")
print(" Stored in ~/.hive/trusted_repos.json")
print(" Skills from this repository will load without prompting in future runs.")
return 0
+2 -1
View File
@@ -75,7 +75,8 @@ class SkillsConfig:
"""Build config from agent module-level variables.
Args:
default_skills: Dict from agent module (e.g. ``{"hive.note-taking": {"enabled": True}}``)
default_skills: Dict from agent module
(e.g. ``{"hive.note-taking": {"enabled": True}}``)
skills: List of pre-activated skill names from agent module
"""
all_disabled = False
+13 -12
View File
@@ -7,25 +7,26 @@ locations. Resolves name collisions deterministically.
from __future__ import annotations
import logging
from dataclasses import dataclass, field
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from framework.skills.parser import ParsedSkill, parse_skill_md
logger = logging.getLogger(__name__)
# Directories to skip during scanning
_SKIP_DIRS = frozenset({
".git",
"node_modules",
"__pycache__",
".venv",
"venv",
".mypy_cache",
".pytest_cache",
".ruff_cache",
})
_SKIP_DIRS = frozenset(
{
".git",
"node_modules",
"__pycache__",
".venv",
"venv",
".mypy_cache",
".pytest_cache",
".ruff_cache",
}
)
# Scope priority (higher = takes precedence)
_SCOPE_PRIORITY = {
+2 -4
View File
@@ -9,7 +9,7 @@ from __future__ import annotations
import logging
import re
from dataclasses import dataclass, field
from dataclasses import dataclass
from pathlib import Path
from typing import Any
@@ -52,9 +52,7 @@ def _try_fix_yaml(raw: str) -> str:
if m:
key_part, value_part = m.group(1), m.group(2)
# If value contains a colon and isn't already quoted
if ":" in value_part and not (
value_part.startswith('"') or value_part.startswith("'")
):
if ":" in value_part and not (value_part.startswith('"') or value_part.startswith("'")):
value_part = f'"{value_part}"'
fixed.append(f"{key_part}{value_part}")
else:
+9 -9
View File
@@ -14,7 +14,7 @@ import logging
import subprocess
import sys
from collections.abc import Callable
from dataclasses import dataclass, field
from dataclasses import dataclass
from datetime import UTC, datetime
from enum import StrEnum
from pathlib import Path
@@ -158,9 +158,7 @@ class ProjectTrustDetector:
def __init__(self, store: TrustedRepoStore | None = None) -> None:
self._store = store or TrustedRepoStore()
def classify(
self, project_dir: Path | None
) -> tuple[ProjectTrustClassification, str]:
def classify(self, project_dir: Path | None) -> tuple[ProjectTrustClassification, str]:
"""Return (classification, repo_key).
repo_key is empty string for ALWAYS_TRUSTED cases without a remote.
@@ -398,7 +396,8 @@ class TrustGate:
project_dir: Path | None,
repo_key: str,
) -> str:
"""Show the security notice (once) and consent prompt. Return 'session' | 'permanent' | 'denied'."""
"""Show the security notice (once) and consent prompt.
Return 'session' | 'permanent' | 'denied'."""
from framework.credentials.setup import Colors
if not sys.stdout.isatty():
@@ -459,7 +458,10 @@ class TrustGate:
p(" Options:")
p(f" {Colors.CYAN}1){Colors.NC} Trust this session only")
p(f" {Colors.CYAN}2){Colors.NC} Trust permanently — remember for future runs")
p(f" {Colors.DIM}3) Deny — skip all project-scope skills from this repo{Colors.NC}")
p(
f" {Colors.DIM}3) Deny"
f" — skip all project-scope skills from this repo{Colors.NC}"
)
p(f"{Colors.YELLOW}{'' * 60}{Colors.NC}")
def _prompt_consent(self, Colors) -> str: # noqa: N803
@@ -472,6 +474,4 @@ class TrustGate:
return mapping[choice]
except (KeyboardInterrupt, EOFError):
return "denied"
self._print(
f"{Colors.RED}Invalid choice. Enter 1, 2, or 3.{Colors.NC}"
)
self._print(f"{Colors.RED}Invalid choice. Enter 1, 2, or 3.{Colors.NC}")
+11 -13
View File
@@ -1,18 +1,20 @@
"""Tests for default skills — parsing, token budget, and configuration."""
from pathlib import Path
import pytest
from framework.skills.config import DefaultSkillConfig, SkillsConfig
from framework.skills.defaults import (
SKILL_REGISTRY,
SHARED_MEMORY_KEYS,
SKILL_REGISTRY,
DefaultSkillManager,
)
from framework.skills.parser import parse_skill_md
from pathlib import Path
_DEFAULT_SKILLS_DIR = Path(__file__).resolve().parent.parent / "framework" / "skills" / "_default_skills"
_DEFAULT_SKILLS_DIR = (
Path(__file__).resolve().parent.parent / "framework" / "skills" / "_default_skills"
)
class TestDefaultSkillFiles:
@@ -101,9 +103,7 @@ class TestDefaultSkillManager:
assert len(manager.active_skill_names) == 5
def test_disable_all_via_convention(self):
config = SkillsConfig.from_agent_vars(
default_skills={"_all": {"enabled": False}}
)
config = SkillsConfig.from_agent_vars(default_skills={"_all": {"enabled": False}})
manager = DefaultSkillManager(config)
manager.load()
@@ -111,6 +111,7 @@ class TestDefaultSkillManager:
def test_log_active_skills(self, caplog):
import logging
with caplog.at_level(logging.INFO, logger="framework.skills.defaults"):
manager = DefaultSkillManager()
manager.load()
@@ -120,6 +121,7 @@ class TestDefaultSkillManager:
def test_log_all_disabled(self, caplog):
import logging
config = SkillsConfig(all_defaults_disabled=True)
with caplog.at_level(logging.INFO, logger="framework.skills.defaults"):
manager = DefaultSkillManager(config)
@@ -159,15 +161,11 @@ class TestSkillsConfig:
assert config.skills == ["deep-research"]
def test_from_agent_vars_bool_shorthand(self):
config = SkillsConfig.from_agent_vars(
default_skills={"hive.note-taking": False}
)
config = SkillsConfig.from_agent_vars(default_skills={"hive.note-taking": False})
assert config.is_default_enabled("hive.note-taking") is False
def test_from_agent_vars_all_disabled(self):
config = SkillsConfig.from_agent_vars(
default_skills={"_all": {"enabled": False}}
)
config = SkillsConfig.from_agent_vars(default_skills={"_all": {"enabled": False}})
assert config.all_defaults_disabled is True
def test_get_default_overrides(self):
-2
View File
@@ -1,7 +1,5 @@
"""Tests for the skill catalog and prompt generation."""
import pytest
from framework.skills.catalog import SkillCatalog
from framework.skills.parser import ParsedSkill
+55 -40
View File
@@ -1,9 +1,8 @@
"""Tests for skill discovery."""
import pytest
from pathlib import Path
from framework.skills.discovery import SkillDiscovery, DiscoveryConfig
from framework.skills.discovery import DiscoveryConfig, SkillDiscovery
def _write_skill(base: Path, name: str, description: str = "A test skill.") -> Path:
@@ -24,11 +23,13 @@ class TestSkillDiscovery:
_write_skill(agents_skills, "skill-a")
_write_skill(agents_skills, "skill-b")
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
)
)
skills = discovery.discover()
names = {s.name for s in skills}
@@ -40,11 +41,13 @@ class TestSkillDiscovery:
hive_skills = tmp_path / ".hive" / "skills"
_write_skill(hive_skills, "hive-skill")
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
)
)
skills = discovery.discover()
assert len(skills) == 1
@@ -61,10 +64,12 @@ class TestSkillDiscovery:
monkeypatch.setattr(Path, "home", lambda: tmp_path / "home")
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path / "project",
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path / "project",
skip_framework_scope=True,
)
)
skills = discovery.discover()
matching = [s for s in skills if s.name == "shared-skill"]
@@ -80,11 +85,13 @@ class TestSkillDiscovery:
hive_skills = tmp_path / ".hive" / "skills"
_write_skill(hive_skills, "override-test", "Hive version")
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
)
)
skills = discovery.discover()
matching = [s for s in skills if s.name == "override-test"]
@@ -97,11 +104,13 @@ class TestSkillDiscovery:
_write_skill(skills_dir / "node_modules", "npm-skill")
_write_skill(skills_dir, "real-skill")
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
)
)
skills = discovery.discover()
names = {s.name for s in skills}
@@ -110,19 +119,23 @@ class TestSkillDiscovery:
assert "npm-skill" not in names
def test_empty_scan(self, tmp_path):
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
)
)
skills = discovery.discover()
assert skills == []
def test_framework_scope_loads_defaults(self):
"""Framework scope should find the built-in default skills."""
discovery = SkillDiscovery(DiscoveryConfig(
skip_user_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
skip_user_scope=True,
)
)
skills = discovery.discover()
framework_skills = [s for s in skills if s.source_scope == "framework"]
@@ -135,11 +148,13 @@ class TestSkillDiscovery:
deep = tmp_path / ".agents" / "skills" / "a" / "b" / "c" / "d" / "e"
_write_skill(deep, "too-deep")
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
max_depth=2,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
max_depth=2,
)
)
skills = discovery.discover()
assert not any(s.name == "too-deep" for s in skills)
+21 -17
View File
@@ -1,7 +1,5 @@
"""Integration tests for the skill system — prompt composition and backward compatibility."""
import pytest
from framework.graph.prompt_composer import compose_system_prompt
from framework.skills.catalog import SkillCatalog
from framework.skills.config import SkillsConfig
@@ -129,11 +127,13 @@ class TestEndToEndPipeline:
)
# Discovery
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
)
)
skills = discovery.discover()
assert len(skills) == 1
@@ -162,11 +162,13 @@ class TestEndToEndPipeline:
)
# Discover community skills
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
)
)
community_skills = discovery.discover()
catalog = SkillCatalog(community_skills)
catalog_prompt = catalog.to_prompt()
@@ -199,11 +201,13 @@ class TestEndToEndPipeline:
)
# Community skills
discovery = SkillDiscovery(DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
))
discovery = SkillDiscovery(
DiscoveryConfig(
project_root=tmp_path,
skip_user_scope=True,
skip_framework_scope=True,
)
)
catalog = SkillCatalog(discovery.discover())
# Disabled defaults
+5 -2
View File
@@ -1,20 +1,23 @@
"""Tests for SKILL.md parser."""
import pytest
from pathlib import Path
from framework.skills.parser import parse_skill_md, ParsedSkill
import pytest
from framework.skills.parser import parse_skill_md
@pytest.fixture
def tmp_skill(tmp_path):
"""Helper to create a SKILL.md file and return its path."""
def _create(content: str, dir_name: str = "my-skill") -> Path:
skill_dir = tmp_path / dir_name
skill_dir.mkdir(parents=True, exist_ok=True)
skill_md = skill_dir / "SKILL.md"
skill_md.write_text(content, encoding="utf-8")
return skill_md
return _create
+42 -45
View File
@@ -3,24 +3,18 @@
from __future__ import annotations
import json
from datetime import UTC, datetime
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from framework.skills.parser import ParsedSkill
from framework.skills.trust import (
ProjectTrustClassification,
ProjectTrustDetector,
TrustGate,
TrustedRepoEntry,
TrustedRepoStore,
TrustGate,
_is_localhost_remote,
_normalize_remote_url,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
@@ -53,10 +47,7 @@ class TestNormalizeRemoteUrl:
assert _normalize_remote_url("https://github.com/org/repo") == "github.com/org/repo"
def test_ssh_url_format(self):
assert (
_normalize_remote_url("ssh://git@github.com/org/repo.git")
== "github.com/org/repo"
)
assert _normalize_remote_url("ssh://git@github.com/org/repo.git") == "github.com/org/repo"
def test_lowercased(self):
assert _normalize_remote_url("git@GitHub.COM:Org/Repo.git") == "github.com/org/repo"
@@ -65,10 +56,7 @@ class TestNormalizeRemoteUrl:
assert _normalize_remote_url("https://github.com/org/repo/") == "github.com/org/repo"
def test_gitlab(self):
assert (
_normalize_remote_url("git@gitlab.com:team/project.git")
== "gitlab.com/team/project"
)
assert _normalize_remote_url("git@gitlab.com:team/project.git") == "gitlab.com/team/project"
# ---------------------------------------------------------------------------
@@ -238,6 +226,7 @@ class TestProjectTrustDetector:
def test_git_timeout_treated_as_trusted(self, tmp_path):
import subprocess
(tmp_path / ".git").mkdir()
store = TrustedRepoStore(tmp_path / "t.json")
det = ProjectTrustDetector(store)
@@ -287,9 +276,7 @@ class TestTrustGate:
skill = make_skill("proj-skill", "project")
gate = TrustGate(store=store, interactive=False)
with patch("subprocess.run") as m:
m.return_value = MagicMock(
returncode=0, stdout="git@github.com:trusted/repo.git\n"
)
m.return_value = MagicMock(returncode=0, stdout="git@github.com:trusted/repo.git\n")
result = gate.filter_and_gate([skill], project_dir=tmp_path)
assert any(s.name == "proj-skill" for s in result)
@@ -322,9 +309,11 @@ class TestTrustGate:
print_fn=outputs.append,
input_fn=lambda _: "1", # trust this session
)
with patch("sys.stdin.isatty", return_value=True), patch(
"sys.stdout.isatty", return_value=True
), patch("subprocess.run") as m:
with (
patch("sys.stdin.isatty", return_value=True),
patch("sys.stdout.isatty", return_value=True),
patch("subprocess.run") as m,
):
m.return_value = MagicMock(
returncode=0, stdout="https://github.com/stranger/repo.git\n"
)
@@ -344,9 +333,11 @@ class TestTrustGate:
print_fn=lambda _: None,
input_fn=lambda _: "2", # trust permanently
)
with patch("sys.stdin.isatty", return_value=True), patch(
"sys.stdout.isatty", return_value=True
), patch("subprocess.run") as m:
with (
patch("sys.stdin.isatty", return_value=True),
patch("sys.stdout.isatty", return_value=True),
patch("subprocess.run") as m,
):
m.return_value = MagicMock(
returncode=0, stdout="https://github.com/stranger/repo.git\n"
)
@@ -365,9 +356,11 @@ class TestTrustGate:
print_fn=lambda _: None,
input_fn=lambda _: "3", # deny
)
with patch("sys.stdin.isatty", return_value=True), patch(
"sys.stdout.isatty", return_value=True
), patch("subprocess.run") as m:
with (
patch("sys.stdin.isatty", return_value=True),
patch("sys.stdout.isatty", return_value=True),
patch("subprocess.run") as m,
):
m.return_value = MagicMock(
returncode=0, stdout="https://github.com/stranger/repo.git\n"
)
@@ -394,9 +387,11 @@ class TestTrustGate:
print_fn=lambda _: None,
input_fn=lambda _: (_ for _ in ()).throw(KeyboardInterrupt()),
)
with patch("sys.stdin.isatty", return_value=True), patch(
"sys.stdout.isatty", return_value=True
), patch("subprocess.run") as m:
with (
patch("sys.stdin.isatty", return_value=True),
patch("sys.stdout.isatty", return_value=True),
patch("subprocess.run") as m,
):
m.return_value = MagicMock(
returncode=0, stdout="https://github.com/stranger/repo.git\n"
)
@@ -407,9 +402,7 @@ class TestTrustGate:
"""Security notice (NFR-5) should be shown the first time only."""
# Use a temp sentinel path
sentinel = tmp_path / ".skill_trust_notice_shown"
monkeypatch.setattr(
"framework.skills.trust._NOTICE_SENTINEL_PATH", sentinel
)
monkeypatch.setattr("framework.skills.trust._NOTICE_SENTINEL_PATH", sentinel)
assert not sentinel.exists()
(tmp_path / ".git").mkdir()
@@ -422,9 +415,11 @@ class TestTrustGate:
print_fn=output_lines.append,
input_fn=lambda _: "3",
)
with patch("sys.stdin.isatty", return_value=True), patch(
"sys.stdout.isatty", return_value=True
), patch("subprocess.run") as m:
with (
patch("sys.stdin.isatty", return_value=True),
patch("sys.stdout.isatty", return_value=True),
patch("subprocess.run") as m,
):
m.return_value = MagicMock(
returncode=0, stdout="https://github.com/stranger/repo.git\n"
)
@@ -436,9 +431,11 @@ class TestTrustGate:
# Second run should NOT show the notice again
output_lines.clear()
skill2 = make_skill("notice-skill-2", "project")
with patch("sys.stdin.isatty", return_value=True), patch(
"sys.stdout.isatty", return_value=True
), patch("subprocess.run") as m:
with (
patch("sys.stdin.isatty", return_value=True),
patch("sys.stdout.isatty", return_value=True),
patch("subprocess.run") as m,
):
m.return_value = MagicMock(
returncode=0, stdout="https://github.com/stranger/repo.git\n"
)
@@ -459,15 +456,15 @@ class TestTrustGate:
print_fn=lambda _: None,
input_fn=lambda _: "3", # deny project skills
)
with patch("sys.stdin.isatty", return_value=True), patch(
"sys.stdout.isatty", return_value=True
), patch("subprocess.run") as m:
with (
patch("sys.stdin.isatty", return_value=True),
patch("sys.stdout.isatty", return_value=True),
patch("subprocess.run") as m,
):
m.return_value = MagicMock(
returncode=0, stdout="https://github.com/stranger/repo.git\n"
)
result = gate.filter_and_gate(
[fw_skill, user_skill, proj_skill], project_dir=tmp_path
)
result = gate.filter_and_gate([fw_skill, user_skill, proj_skill], project_dir=tmp_path)
names = {s.name for s in result}
assert "fw" in names
assert "usr" in names