mirror of
https://fastgit.cc/github.com/HKUDS/CLI-Anything
synced 2026-04-20 21:00:28 +08:00
fix: Address Copilot review comments on PR #47
This commit resolves all 8 Copilot review comments: Template fixes (SKILL.md.template): - Fix #8: Use YAML block scalars for frontmatter to safely handle quotes/newlines - Fix #2: Add conditional wrapper for command groups section to prevent empty headers Generator fixes (skill_generator.py): - Fix #6: Remove unused imports (os, json) and unused decorator_name variable - Fix #5: Add cli_anything_dir.exists() check before iterdir() with descriptive error - Fix #7: Add _format_display_name() helper for proper software name formatting - Fix #4: Change output_path default to use harness_path instead of CWD - Fix #3: Update regex patterns to handle multi-line decorators and docstrings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,14 +11,17 @@ The generated SKILL.md files contain:
|
||||
- Examples for AI agents
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
def _format_display_name(name: str) -> str:
|
||||
"""Format software name for display (replace underscores/hyphens with spaces, then title)."""
|
||||
return name.replace("_", " ").replace("-", " ").title()
|
||||
|
||||
|
||||
@dataclass
|
||||
class CommandInfo:
|
||||
"""Information about a CLI command."""
|
||||
@@ -69,7 +72,12 @@ def extract_cli_metadata(harness_path: str) -> SkillMetadata:
|
||||
|
||||
# Find the cli_anything/<software> directory
|
||||
cli_anything_dir = harness_path / "cli_anything"
|
||||
software_dirs = [d for d in cli_anything_dir.iterdir()
|
||||
if not cli_anything_dir.exists():
|
||||
raise ValueError(
|
||||
f"cli_anything directory not found in {harness_path}. "
|
||||
"Ensure the harness structure includes cli_anything/<software>/"
|
||||
)
|
||||
software_dirs = [d for d in cli_anything_dir.iterdir()]
|
||||
if d.is_dir() and (d / "__init__.py").exists()]
|
||||
|
||||
if not software_dirs:
|
||||
@@ -107,7 +115,7 @@ def extract_cli_metadata(harness_path: str) -> SkillMetadata:
|
||||
|
||||
# Build skill name and description
|
||||
skill_name = f"cli-anything-{software_name}"
|
||||
skill_description = f"Command-line interface for {software_name.title()} - {skill_intro[:100]}..."
|
||||
skill_description = f"Command-line interface for {_format_display_name(software_name)} - {skill_intro[:100]}..."
|
||||
|
||||
return SkillMetadata(
|
||||
skill_name=skill_name,
|
||||
@@ -181,13 +189,23 @@ def extract_commands_from_cli(cli_path: Path) -> list[CommandGroup]:
|
||||
groups = []
|
||||
|
||||
# Find Click group decorators
|
||||
# Pattern: @click.group() or @<group_name>.group()
|
||||
group_pattern = r'@(\w+)\.group\([^)]*\)(?:\s*@click\.[^\n]+)*\s*def\s+(\w+)\([^)]*\):\s*"""([^"]*)"""'
|
||||
# Pattern handles:
|
||||
# - Multi-line decorators (decorators on separate lines)
|
||||
# - Docstrings on the same line or following line after function definition
|
||||
# - Various Click decorator patterns like @click.option(), @click.argument()
|
||||
# Uses re.DOTALL to match across newlines between decorator and def
|
||||
group_pattern = (
|
||||
r'@(\w+)\.group\([^)]*\)' # @xxx.group(...)
|
||||
r'(?:\s*@[\w.]+\([^)]*\))*' # optional additional decorators
|
||||
r'\s*def\s+(\w+)\([^)]*\)' # def xxx(...):
|
||||
r':\s*' # colon with optional whitespace
|
||||
r'(?:"""([\s\S]*?)"""|\'\'\'([\s\S]*?)\'\'\')?' # optional docstring (""" or ''')
|
||||
)
|
||||
|
||||
for match in re.finditer(group_pattern, content, re.MULTILINE):
|
||||
decorator_name = match.group(1)
|
||||
for match in re.finditer(group_pattern, content):
|
||||
group_func = match.group(2)
|
||||
group_doc = match.group(3).strip()
|
||||
# Docstring can be in group 3 (triple-double) or group 4 (triple-single)
|
||||
group_doc = (match.group(3) or match.group(4) or "").strip()
|
||||
|
||||
group_name = group_func.replace("_", " ").title()
|
||||
if not group_name:
|
||||
@@ -200,13 +218,23 @@ def extract_commands_from_cli(cli_path: Path) -> list[CommandGroup]:
|
||||
))
|
||||
|
||||
# Find Click command decorators
|
||||
# Pattern: @<group>.command() followed by def <name>(): """doc"""
|
||||
command_pattern = r'@(\w+)\.command\([^)]*\)(?:\s*@click\.[^\n]+)*\s*def\s+(\w+)\([^)]*\):\s*"""([^"]*)"""'
|
||||
# Pattern handles:
|
||||
# - Multi-line decorators (decorators on separate lines)
|
||||
# - Docstrings on the same line or following line after function definition
|
||||
# - Various Click decorator patterns like @click.option(), @click.argument()
|
||||
command_pattern = (
|
||||
r'@(\w+)\.command\([^)]*\)' # @xxx.command(...)
|
||||
r'(?:\s*@[\w.]+\([^)]*\))*' # optional additional decorators
|
||||
r'\s*def\s+(\w+)\([^)]*\)' # def xxx(...):
|
||||
r':\s*' # colon with optional whitespace
|
||||
r'(?:"""([\s\S]*?)"""|\'\'\'([\s\S]*?)\'\'\')?' # optional docstring (""" or ''')
|
||||
)
|
||||
|
||||
for match in re.finditer(command_pattern, content, re.MULTILINE):
|
||||
for match in re.finditer(command_pattern, content):
|
||||
group_name = match.group(1)
|
||||
cmd_name = match.group(2)
|
||||
cmd_doc = match.group(3).strip()
|
||||
# Docstring can be in group 3 (triple-double) or group 4 (triple-single)
|
||||
cmd_doc = (match.group(3) or match.group(4) or "").strip()
|
||||
|
||||
# Find the matching group
|
||||
for group in groups:
|
||||
@@ -224,9 +252,10 @@ def extract_commands_from_cli(cli_path: Path) -> list[CommandGroup]:
|
||||
commands=[]
|
||||
)
|
||||
|
||||
for match in re.finditer(command_pattern, content, re.MULTILINE):
|
||||
for match in re.finditer(command_pattern, content):
|
||||
cmd_name = match.group(2)
|
||||
cmd_doc = match.group(3).strip()
|
||||
# Docstring can be in group 3 (triple-double) or group 4 (triple-single)
|
||||
cmd_doc = (match.group(3) or match.group(4) or "").strip()
|
||||
default_group.commands.append(CommandInfo(
|
||||
name=cmd_name.replace("_", "-"),
|
||||
description=cmd_doc or f"Execute {cmd_name} operation."
|
||||
@@ -346,7 +375,7 @@ def generate_skill_md_simple(metadata: SkillMetadata) -> str:
|
||||
"",
|
||||
"**Prerequisites:**",
|
||||
"- Python 3.10+",
|
||||
f"- {metadata.software_name.title()} must be installed on your system",
|
||||
f"- {_format_display_name(metadata.software_name)} must be installed on your system",
|
||||
]
|
||||
|
||||
if metadata.system_package:
|
||||
@@ -450,8 +479,9 @@ def generate_skill_file(harness_path: str, output_path: Optional[str] = None,
|
||||
|
||||
# Determine output path
|
||||
if output_path is None:
|
||||
# Default to skills/ directory
|
||||
output_path = Path("skills") / f"{metadata.software_name}_SKILL.md"
|
||||
# Default to skills/ directory under harness_path
|
||||
harness_path_obj = Path(harness_path)
|
||||
output_path = harness_path_obj / "skills" / f"{metadata.software_name}_SKILL.md"
|
||||
else:
|
||||
output_path = Path(output_path)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
name: "{{ skill_name }}"
|
||||
description: "{{ skill_description }}"
|
||||
name: >-
|
||||
{{ skill_name }}
|
||||
description: >-
|
||||
{{ skill_description }}
|
||||
---
|
||||
|
||||
# {{ skill_name }}
|
||||
@@ -49,6 +51,7 @@ cli-anything-{{ software_name }}
|
||||
# Enter commands interactively with tab-completion and history
|
||||
```
|
||||
|
||||
{% if command_groups %}
|
||||
## Command Groups
|
||||
|
||||
{% for group in command_groups %}
|
||||
@@ -63,6 +66,7 @@ cli-anything-{{ software_name }}
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
## Examples
|
||||
|
||||
{% for example in examples %}
|
||||
|
||||
Reference in New Issue
Block a user