mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
Honor standalone cleaner skill roots
The standalone helper is advertised from inside the k-skill-cleaner directory, so --skills-root . now resolves that self-skill directory to its parent skills root before scanning siblings. A subprocess regression locks the installed-skill layout that previously returned skill_count 0.\n\nConstraint: Preserve the documented standalone command without requiring users to switch to --skills-root ..\nRejected: Documentation-only fix | would make the advertised command more brittle and leave existing users with empty reports\nConfidence: high\nScope-risk: narrow\nDirective: Keep standalone helper invocation from inside k-skill-cleaner covered when changing root detection\nTested: PYTHONPATH=scripts python3 -m unittest scripts.test_k_skill_cleaner\nTested: Standalone temp-layout smoke from inside k-skill-cleaner with --skills-root .\nTested: npm run lint && npm run typecheck && npm test && npm run ci
This commit is contained in:
parent
10cb419212
commit
1935e641a6
2 changed files with 71 additions and 2 deletions
|
|
@ -71,10 +71,33 @@ AGENT_USAGE_SOURCES = [
|
|||
]
|
||||
|
||||
|
||||
def resolve_skills_root(root: Path | str) -> Path:
|
||||
"""Resolve the directory that contains installable skill directories.
|
||||
|
||||
Standalone installs tell users to run this helper from inside the
|
||||
``k-skill-cleaner`` directory with ``--skills-root .``. In that layout, the
|
||||
current directory is itself a skill, while sibling skill directories live in
|
||||
the parent directory. Treat that self-skill root as shorthand for its parent
|
||||
so the advertised standalone command scans the installed skill bundle.
|
||||
"""
|
||||
|
||||
root_path = Path(root).expanduser().resolve()
|
||||
if (root_path / "SKILL.md").is_file():
|
||||
parent = root_path.parent
|
||||
if any(
|
||||
child.is_dir()
|
||||
and child.name not in EXCLUDED_ROOT_DIRS
|
||||
and (child / "SKILL.md").is_file()
|
||||
for child in parent.iterdir()
|
||||
):
|
||||
return parent
|
||||
return root_path
|
||||
|
||||
|
||||
def find_skill_dirs(root: Path | str) -> list[str]:
|
||||
"""Return root-level directories that look like installable skills."""
|
||||
|
||||
root_path = Path(root)
|
||||
root_path = resolve_skills_root(root)
|
||||
skills: list[str] = []
|
||||
for child in root_path.iterdir():
|
||||
if not child.is_dir() or child.name in EXCLUDED_ROOT_DIRS:
|
||||
|
|
@ -316,7 +339,11 @@ def _resolve_since(days: int | None, since: str | None, now: datetime | None = N
|
|||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Suggest K-skill cleanup candidates from interviews and usage logs.")
|
||||
parser.add_argument("--skills-root", default=".", help="Repository root containing root-level skill directories")
|
||||
parser.add_argument(
|
||||
"--skills-root",
|
||||
default=".",
|
||||
help="Directory containing root-level skills; a skill directory with SKILL.md auto-scans its parent",
|
||||
)
|
||||
parser.add_argument("--usage-json", help="Optional JSON object mapping skill names to trigger counts")
|
||||
parser.add_argument("--log", action="append", default=[], help="Agent log file to scan; repeatable")
|
||||
parser.add_argument("--scan-default-logs", action="store_true", help="Best-effort scan known local agent log locations")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
|
@ -106,6 +109,45 @@ class KSkillCleanerTest(unittest.TestCase):
|
|||
self.assertTrue(source["paths"] or source["fallback"])
|
||||
self.assertIn("confidence", source)
|
||||
|
||||
def test_skill_local_helper_autodetects_parent_skills_root(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
skills_root = Path(tmp)
|
||||
cleaner_dir = skills_root / "k-skill-cleaner"
|
||||
cleaner_scripts = cleaner_dir / "scripts"
|
||||
cleaner_scripts.mkdir(parents=True)
|
||||
(cleaner_dir / "SKILL.md").write_text("---\nname: k-skill-cleaner\n", encoding="utf-8")
|
||||
shutil.copyfile(
|
||||
Path(__file__).resolve().parents[1] / "k-skill-cleaner" / "scripts" / "k_skill_cleaner.py",
|
||||
cleaner_scripts / "k_skill_cleaner.py",
|
||||
)
|
||||
|
||||
for skill in ["kbo-results", "k-skill-setup"]:
|
||||
skill_dir = skills_root / skill
|
||||
skill_dir.mkdir()
|
||||
(skill_dir / "SKILL.md").write_text(f"---\nname: {skill}\n", encoding="utf-8")
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"scripts/k_skill_cleaner.py",
|
||||
"--skills-root",
|
||||
".",
|
||||
"--never-use",
|
||||
"kbo-results",
|
||||
"--keep",
|
||||
"k-skill-setup",
|
||||
],
|
||||
cwd=cleaner_dir,
|
||||
check=True,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
report = json.loads(result.stdout)
|
||||
|
||||
self.assertEqual(report["skill_count"], 3)
|
||||
self.assertEqual(report["candidates"][0]["skill"], "kbo-results")
|
||||
self.assertEqual(report["candidates"][0]["action"], "remove")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue