refactor(skills): slim AST diagnostic to single entry point

Trim ~600 LOC off the original contribution while keeping the same
operator-facing surface and detection coverage.

- Collapse three entry points (file / dir / bundle) into one
  ast_scan_path(path) that handles both files and directories.
- Drop AstFinding dataclass + severity field — replaced with plain
  (file, line, pattern_id, description) tuples. Severity ordering was
  display-only for a diagnostic that explicitly disclaims security
  verdicts, so the field added bookkeeping without earning its place.
- Replace Rich-markup formatter with plain text grouped by file.
- Drop the 'inspect --ast-deep' surface — same scanner, same output as
  'audit --deep', single CLI entry is enough. Operators audit after
  install; pre-install inspection signal isn't worth the second surface.
- Trim test file to the cases that earn their place: bypass payload,
  syntax error survival, RecursionError survival, false-positive guard
  (importer lookalike), literal-arg false-positive guard, non-.py
  ignored, directory recursion + cache-dir skipping, missing-path,
  getattr/__dict__ detection, formatter empty + populated.

Net: tools/skills_ast_audit.py 353 -> 133 LOC,
tests/tools/test_skills_ast_audit.py 299 -> 103 LOC, full diff
+704/-12 -> +264/-6. No change to tools/skills_guard.py — Skills Guard
verdicts remain untouched per SECURITY.md §2.4.
This commit is contained in:
teknium1 2026-05-23 16:36:37 -07:00 committed by Teknium
parent 7255050c99
commit 4254f7dd17
4 changed files with 175 additions and 609 deletions

View file

@ -12267,11 +12267,6 @@ Examples:
"inspect", help="Preview a skill without installing"
)
skills_inspect.add_argument("identifier", help="Skill identifier")
skills_inspect.add_argument(
"--ast-deep",
action="store_true",
help="Run AST-level diagnostics on Python files before installing",
)
skills_list = skills_subparsers.add_parser("list", help="List installed skills")
skills_list.add_argument(

View file

@ -630,13 +630,8 @@ def do_install(identifier: str, category: str = "", force: bool = False,
c.print("[dim]Use /reset to start a new session now, or --now to activate immediately (invalidates prompt cache).[/]\n")
def do_inspect(identifier: str, console: Optional[Console] = None,
ast_deep: bool = False) -> None:
"""Preview a skill's SKILL.md content without installing.
When ``ast_deep=True``, also runs AST-level diagnostics against Python
files in the fetched bundle before installation.
"""
def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
"""Preview a skill's SKILL.md content without installing."""
from tools.skills_hub import GitHubAuth, create_source_router
c = console or _console
@ -682,11 +677,6 @@ def do_inspect(identifier: str, console: Optional[Console] = None,
preview += f"\n\n... ({len(lines) - 50} more lines)"
c.print(Panel(preview, title="SKILL.md Preview", subtitle="hermes skills install <id> to install"))
if bundle and ast_deep:
from tools.skills_ast_audit import ast_scan_bundle_files, format_ast_report
ast_findings = ast_scan_bundle_files(bundle.files)
c.print(format_ast_report(ast_findings, skill_name=meta.name))
c.print()
@ -920,8 +910,9 @@ def do_audit(name: Optional[str] = None, console: Optional[Console] = None,
deep: bool = False) -> None:
"""Re-run security scan on installed hub skills.
When ``deep=True``, also runs AST-level analysis on Python files
(opt-in diagnostic, not a security gate).
When ``deep=True``, also runs an opt-in AST-level diagnostic on Python
files (review aid only not a security gate; skills_guard.py verdicts
are unchanged).
"""
from tools.skills_hub import HubLockFile, SKILLS_DIR
from tools.skills_guard import scan_skill, format_scan_report
@ -943,9 +934,8 @@ def do_audit(name: Optional[str] = None, console: Optional[Console] = None,
c.print(f"\n[bold]Auditing {len(targets)} skill(s)...[/]\n")
ast_scan_skill = format_ast_report = None
if deep:
from tools.skills_ast_audit import ast_scan_skill, format_ast_report
from tools.skills_ast_audit import ast_scan_path, format_ast_report
for entry in targets:
skill_path = SKILLS_DIR / entry["install_path"]
@ -957,8 +947,7 @@ def do_audit(name: Optional[str] = None, console: Optional[Console] = None,
c.print(format_scan_report(result))
if deep:
ast_findings = ast_scan_skill(skill_path)
c.print(format_ast_report(ast_findings, skill_name=entry["name"]))
c.print(format_ast_report(ast_scan_path(skill_path), skill_name=entry["name"]))
c.print()
@ -1356,7 +1345,7 @@ def skills_command(args) -> None:
skip_confirm=getattr(args, "yes", False),
name_override=getattr(args, "name", "") or "")
elif action == "inspect":
do_inspect(args.identifier, ast_deep=getattr(args, "ast_deep", False))
do_inspect(args.identifier)
elif action == "list":
do_list(
source_filter=args.source,
@ -1414,7 +1403,6 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None:
/skills install openai/skills/skill-creator --force
/skills install https://example.com/path/SKILL.md
/skills inspect openai/skills/skill-creator
/skills inspect openai/skills/skill-creator --ast-deep
/skills list
/skills list --source hub
/skills check
@ -1514,11 +1502,10 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None:
name_override=name_override, console=c)
elif action == "inspect":
non_flag_args = [arg for arg in args if not arg.startswith("--")]
if not non_flag_args:
c.print("[bold red]Usage:[/] /skills inspect <identifier> [--ast-deep]\n")
if not args:
c.print("[bold red]Usage:[/] /skills inspect <identifier>\n")
return
do_inspect(non_flag_args[0], console=c, ast_deep="--ast-deep" in args)
do_inspect(args[0], console=c)
elif action == "list":
source_filter = "all"