fix: Resolve all linting errors from ruff

Fix 145 linting errors across CLI refactor code:

Type annotation modernization (Python 3.9+):
- Replace typing.Dict with dict
- Replace typing.List with list
- Replace typing.Set with set
- Replace Optional[X] with X | None

Code quality improvements:
- Remove trailing whitespace (W291)
- Remove whitespace from blank lines (W293)
- Remove unused imports (F401)
- Use dictionary lookup instead of if-elif chains (SIM116)
- Combine nested if statements (SIM102)

Files fixed (45 files):
- src/skill_seekers/cli/arguments/*.py (10 files)
- src/skill_seekers/cli/parsers/*.py (24 files)
- src/skill_seekers/cli/presets/*.py (4 files)
- src/skill_seekers/cli/create_command.py
- src/skill_seekers/cli/source_detector.py
- src/skill_seekers/cli/github_scraper.py
- tests/test_*.py (5 test files)

All files now pass ruff linting checks.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
yusyus
2026-02-15 20:20:55 +03:00
parent 4c6d885725
commit 83b03d9f9f
45 changed files with 134 additions and 270 deletions

View File

@@ -8,10 +8,9 @@ Includes preset system support for #268.
"""
import argparse
from typing import Dict, Any
from typing import Any
ANALYZE_ARGUMENTS: Dict[str, Dict[str, Any]] = {
ANALYZE_ARGUMENTS: dict[str, dict[str, Any]] = {
# Core options
"directory": {
"flags": ("--directory",),
@@ -172,7 +171,6 @@ ANALYZE_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
def add_analyze_arguments(parser: argparse.ArgumentParser) -> None:
"""Add all analyze command arguments to a parser."""
for arg_name, arg_def in ANALYZE_ARGUMENTS.items():
@@ -180,7 +178,6 @@ def add_analyze_arguments(parser: argparse.ArgumentParser) -> None:
kwargs = arg_def["kwargs"]
parser.add_argument(*flags, **kwargs)
def get_analyze_argument_names() -> set:
"""Get the set of analyze argument destination names."""
return set(ANALYZE_ARGUMENTS.keys())

View File

@@ -5,12 +5,11 @@ and provide consistent behavior for configuration, output control, and help.
"""
import argparse
from typing import Dict, Any
from typing import Any
# Common argument definitions as data structure
# These are arguments that appear in MULTIPLE commands
COMMON_ARGUMENTS: Dict[str, Dict[str, Any]] = {
COMMON_ARGUMENTS: dict[str, dict[str, Any]] = {
"config": {
"flags": ("--config", "-c"),
"kwargs": {
@@ -67,10 +66,9 @@ COMMON_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
# RAG (Retrieval-Augmented Generation) arguments
# These are shared across commands that support RAG chunking
RAG_ARGUMENTS: Dict[str, Dict[str, Any]] = {
RAG_ARGUMENTS: dict[str, dict[str, Any]] = {
"chunk_for_rag": {
"flags": ("--chunk-for-rag",),
"kwargs": {
@@ -98,15 +96,14 @@ RAG_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
def add_common_arguments(parser: argparse.ArgumentParser) -> None:
"""Add common arguments to a parser.
These arguments are shared across most commands for consistent UX.
Args:
parser: The ArgumentParser to add arguments to
Example:
>>> parser = argparse.ArgumentParser()
>>> add_common_arguments(parser)
@@ -117,7 +114,6 @@ def add_common_arguments(parser: argparse.ArgumentParser) -> None:
kwargs = arg_def["kwargs"]
parser.add_argument(*flags, **kwargs)
def get_common_argument_names() -> set:
"""Get the set of common argument destination names.
@@ -126,7 +122,6 @@ def get_common_argument_names() -> set:
"""
return set(COMMON_ARGUMENTS.keys())
def add_rag_arguments(parser: argparse.ArgumentParser) -> None:
"""Add RAG (Retrieval-Augmented Generation) arguments to a parser.
@@ -145,7 +140,6 @@ def add_rag_arguments(parser: argparse.ArgumentParser) -> None:
kwargs = arg_def["kwargs"]
parser.add_argument(*flags, **kwargs)
def get_rag_argument_names() -> set:
"""Get the set of RAG argument destination names.
@@ -154,16 +148,15 @@ def get_rag_argument_names() -> set:
"""
return set(RAG_ARGUMENTS.keys())
def get_argument_help(arg_name: str) -> str:
"""Get the help text for a common argument.
Args:
arg_name: Name of the argument (e.g., 'config')
Returns:
Help text string
Raises:
KeyError: If argument doesn't exist
"""

View File

@@ -10,18 +10,17 @@ This enables progressive disclosure in help text while maintaining
"""
import argparse
from typing import Dict, Any, Set, List
from typing import Any
from skill_seekers.cli.constants import DEFAULT_RATE_LIMIT
from .common import RAG_ARGUMENTS
# =============================================================================
# TIER 1: UNIVERSAL ARGUMENTS (15 flags)
# =============================================================================
# These arguments work for ALL source types
UNIVERSAL_ARGUMENTS: Dict[str, Dict[str, Any]] = {
UNIVERSAL_ARGUMENTS: dict[str, dict[str, Any]] = {
# Identity arguments
"name": {
"flags": ("--name",),
@@ -118,13 +117,12 @@ UNIVERSAL_ARGUMENTS: Dict[str, Dict[str, Any]] = {
# Merge RAG arguments from common.py into universal arguments
UNIVERSAL_ARGUMENTS.update(RAG_ARGUMENTS)
# =============================================================================
# TIER 2: SOURCE-SPECIFIC ARGUMENTS
# =============================================================================
# Web scraping specific (from scrape.py)
WEB_ARGUMENTS: Dict[str, Dict[str, Any]] = {
WEB_ARGUMENTS: dict[str, dict[str, Any]] = {
"url": {
"flags": ("--url",),
"kwargs": {
@@ -189,7 +187,7 @@ WEB_ARGUMENTS: Dict[str, Dict[str, Any]] = {
}
# GitHub repository specific (from github.py)
GITHUB_ARGUMENTS: Dict[str, Dict[str, Any]] = {
GITHUB_ARGUMENTS: dict[str, dict[str, Any]] = {
"repo": {
"flags": ("--repo",),
"kwargs": {
@@ -261,7 +259,7 @@ GITHUB_ARGUMENTS: Dict[str, Dict[str, Any]] = {
}
# Local codebase specific (from analyze.py)
LOCAL_ARGUMENTS: Dict[str, Dict[str, Any]] = {
LOCAL_ARGUMENTS: dict[str, dict[str, Any]] = {
"directory": {
"flags": ("--directory",),
"kwargs": {
@@ -324,7 +322,7 @@ LOCAL_ARGUMENTS: Dict[str, Dict[str, Any]] = {
}
# PDF specific (from pdf.py)
PDF_ARGUMENTS: Dict[str, Dict[str, Any]] = {
PDF_ARGUMENTS: dict[str, dict[str, Any]] = {
"pdf": {
"flags": ("--pdf",),
"kwargs": {
@@ -350,13 +348,12 @@ PDF_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
# =============================================================================
# TIER 3: ADVANCED/RARE ARGUMENTS
# =============================================================================
# Hidden from default help, shown only with --help-advanced
ADVANCED_ARGUMENTS: Dict[str, Dict[str, Any]] = {
ADVANCED_ARGUMENTS: dict[str, dict[str, Any]] = {
"no_rate_limit": {
"flags": ("--no-rate-limit",),
"kwargs": {
@@ -387,17 +384,15 @@ ADVANCED_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
def get_universal_argument_names() -> Set[str]:
def get_universal_argument_names() -> set[str]:
"""Get set of universal argument names."""
return set(UNIVERSAL_ARGUMENTS.keys())
def get_source_specific_arguments(source_type: str) -> Dict[str, Dict[str, Any]]:
def get_source_specific_arguments(source_type: str) -> dict[str, dict[str, Any]]:
"""Get source-specific arguments for a given source type.
Args:
@@ -406,21 +401,16 @@ def get_source_specific_arguments(source_type: str) -> Dict[str, Dict[str, Any]]
Returns:
Dict of argument definitions
"""
if source_type == 'web':
return WEB_ARGUMENTS
elif source_type == 'github':
return GITHUB_ARGUMENTS
elif source_type == 'local':
return LOCAL_ARGUMENTS
elif source_type == 'pdf':
return PDF_ARGUMENTS
elif source_type == 'config':
return {} # Config files don't have extra args
else:
return {}
source_args = {
'web': WEB_ARGUMENTS,
'github': GITHUB_ARGUMENTS,
'local': LOCAL_ARGUMENTS,
'pdf': PDF_ARGUMENTS,
'config': {}, # Config files don't have extra args
}
return source_args.get(source_type, {})
def get_compatible_arguments(source_type: str) -> List[str]:
def get_compatible_arguments(source_type: str) -> list[str]:
"""Get list of compatible argument names for a source type.
Args:
@@ -441,7 +431,6 @@ def get_compatible_arguments(source_type: str) -> List[str]:
return compatible
def add_create_arguments(parser: argparse.ArgumentParser, mode: str = 'default') -> None:
"""Add create command arguments to parser.

View File

@@ -6,10 +6,9 @@ import and use these definitions.
"""
import argparse
from typing import Dict, Any
from typing import Any
ENHANCE_ARGUMENTS: Dict[str, Dict[str, Any]] = {
ENHANCE_ARGUMENTS: dict[str, dict[str, Any]] = {
# Positional argument
"skill_directory": {
"flags": ("skill_directory",),
@@ -69,7 +68,6 @@ ENHANCE_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
def add_enhance_arguments(parser: argparse.ArgumentParser) -> None:
"""Add all enhance command arguments to a parser."""
for arg_name, arg_def in ENHANCE_ARGUMENTS.items():

View File

@@ -8,11 +8,10 @@ This ensures the parsers NEVER drift out of sync.
"""
import argparse
from typing import Dict, Any
from typing import Any
# GitHub-specific argument definitions as data structure
GITHUB_ARGUMENTS: Dict[str, Dict[str, Any]] = {
GITHUB_ARGUMENTS: dict[str, dict[str, Any]] = {
# Core GitHub options
"repo": {
"flags": ("--repo",),
@@ -134,18 +133,17 @@ GITHUB_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
def add_github_arguments(parser: argparse.ArgumentParser) -> None:
"""Add all github command arguments to a parser.
This is the SINGLE SOURCE OF TRUTH for github arguments.
Used by:
- github_scraper.py (standalone scraper)
- parsers/github_parser.py (unified CLI)
Args:
parser: The ArgumentParser to add arguments to
Example:
>>> parser = argparse.ArgumentParser()
>>> add_github_arguments(parser) # Adds all github args
@@ -155,19 +153,17 @@ def add_github_arguments(parser: argparse.ArgumentParser) -> None:
kwargs = arg_def["kwargs"]
parser.add_argument(*flags, **kwargs)
def get_github_argument_names() -> set:
"""Get the set of github argument destination names.
Returns:
Set of argument dest names
"""
return set(GITHUB_ARGUMENTS.keys())
def get_github_argument_count() -> int:
"""Get the total number of github arguments.
Returns:
Number of arguments
"""

View File

@@ -6,10 +6,9 @@ import and use these definitions.
"""
import argparse
from typing import Dict, Any
from typing import Any
PACKAGE_ARGUMENTS: Dict[str, Dict[str, Any]] = {
PACKAGE_ARGUMENTS: dict[str, dict[str, Any]] = {
# Positional argument
"skill_directory": {
"flags": ("skill_directory",),
@@ -124,7 +123,6 @@ PACKAGE_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
def add_package_arguments(parser: argparse.ArgumentParser) -> None:
"""Add all package command arguments to a parser."""
for arg_name, arg_def in PACKAGE_ARGUMENTS.items():

View File

@@ -6,10 +6,9 @@ import and use these definitions.
"""
import argparse
from typing import Dict, Any
from typing import Any
PDF_ARGUMENTS: Dict[str, Dict[str, Any]] = {
PDF_ARGUMENTS: dict[str, dict[str, Any]] = {
"config": {
"flags": ("--config",),
"kwargs": {
@@ -52,7 +51,6 @@ PDF_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
def add_pdf_arguments(parser: argparse.ArgumentParser) -> None:
"""Add all pdf command arguments to a parser."""
for arg_name, arg_def in PDF_ARGUMENTS.items():

View File

@@ -8,15 +8,14 @@ This ensures the parsers NEVER drift out of sync.
"""
import argparse
from typing import Dict, Any
from typing import Any
from skill_seekers.cli.constants import DEFAULT_RATE_LIMIT
from .common import RAG_ARGUMENTS
# Scrape-specific argument definitions as data structure
# This enables introspection for UI generation and testing
SCRAPE_ARGUMENTS: Dict[str, Dict[str, Any]] = {
SCRAPE_ARGUMENTS: dict[str, dict[str, Any]] = {
# Positional argument
"url_positional": {
"flags": ("url",),
@@ -199,18 +198,17 @@ SCRAPE_ARGUMENTS: Dict[str, Dict[str, Any]] = {
# Merge RAG arguments from common.py
SCRAPE_ARGUMENTS.update(RAG_ARGUMENTS)
def add_scrape_arguments(parser: argparse.ArgumentParser) -> None:
"""Add all scrape command arguments to a parser.
This is the SINGLE SOURCE OF TRUTH for scrape arguments.
Used by:
- doc_scraper.py (standalone scraper)
- parsers/scrape_parser.py (unified CLI)
Args:
parser: The ArgumentParser to add arguments to
Example:
>>> parser = argparse.ArgumentParser()
>>> add_scrape_arguments(parser) # Adds all 26 scrape args
@@ -220,19 +218,17 @@ def add_scrape_arguments(parser: argparse.ArgumentParser) -> None:
kwargs = arg_def["kwargs"]
parser.add_argument(*flags, **kwargs)
def get_scrape_argument_names() -> set:
"""Get the set of scrape argument destination names.
Returns:
Set of argument dest names
"""
return set(SCRAPE_ARGUMENTS.keys())
def get_scrape_argument_count() -> int:
"""Get the total number of scrape arguments.
Returns:
Number of arguments
"""

View File

@@ -6,10 +6,9 @@ import and use these definitions.
"""
import argparse
from typing import Dict, Any
from typing import Any
UNIFIED_ARGUMENTS: Dict[str, Dict[str, Any]] = {
UNIFIED_ARGUMENTS: dict[str, dict[str, Any]] = {
"config": {
"flags": ("--config", "-c"),
"kwargs": {
@@ -43,7 +42,6 @@ UNIFIED_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
def add_unified_arguments(parser: argparse.ArgumentParser) -> None:
"""Add all unified command arguments to a parser."""
for arg_name, arg_def in UNIFIED_ARGUMENTS.items():

View File

@@ -6,10 +6,9 @@ import and use these definitions.
"""
import argparse
from typing import Dict, Any
from typing import Any
UPLOAD_ARGUMENTS: Dict[str, Dict[str, Any]] = {
UPLOAD_ARGUMENTS: dict[str, dict[str, Any]] = {
# Positional argument
"package_file": {
"flags": ("package_file",),
@@ -99,7 +98,6 @@ UPLOAD_ARGUMENTS: Dict[str, Dict[str, Any]] = {
},
}
def add_upload_arguments(parser: argparse.ArgumentParser) -> None:
"""Add all upload command arguments to a parser."""
for arg_name, arg_def in UPLOAD_ARGUMENTS.items():

View File

@@ -7,7 +7,6 @@ to appropriate scraper while maintaining full backward compatibility.
import sys
import logging
import argparse
from typing import List, Optional
from skill_seekers.cli.source_detector import SourceDetector, SourceInfo
from skill_seekers.cli.arguments.create import (
@@ -17,7 +16,6 @@ from skill_seekers.cli.arguments.create import (
logger = logging.getLogger(__name__)
class CreateCommand:
"""Unified create command implementation."""
@@ -28,7 +26,7 @@ class CreateCommand:
args: Parsed command-line arguments
"""
self.args = args
self.source_info: Optional[SourceInfo] = None
self.source_info: SourceInfo | None = None
def execute(self) -> int:
"""Execute the create command.
@@ -311,7 +309,7 @@ class CreateCommand:
finally:
sys.argv = original_argv
def _add_common_args(self, argv: List[str]) -> None:
def _add_common_args(self, argv: list[str]) -> None:
"""Add common/universal arguments to argv list.
Args:
@@ -367,7 +365,6 @@ class CreateCommand:
if getattr(self.args, 'interactive_enhancement', False):
argv.append('--interactive-enhancement')
def main() -> int:
"""Entry point for create command.
@@ -510,6 +507,5 @@ Common Workflows:
command = CreateCommand(args)
return command.execute()
if __name__ == '__main__':
sys.exit(main())

View File

@@ -21,7 +21,7 @@ import os
import re
import sys
from pathlib import Path
from typing import Any, Optional
from typing import Any
try:
from github import Github, GithubException, Repository
@@ -100,7 +100,6 @@ EXCLUDED_DIRS = {
".tmp",
}
def extract_description_from_readme(readme_content: str, repo_name: str) -> str:
"""
Extract a meaningful description from README content for skill description.
@@ -181,7 +180,6 @@ def extract_description_from_readme(readme_content: str, repo_name: str) -> str:
project_name = repo_name.split("/")[-1]
return f"Use when working with {project_name}"
class GitHubScraper:
"""
GitHub Repository Scraper (C1.1-C1.9)
@@ -563,7 +561,7 @@ class GitHubScraper:
return False
def _load_gitignore(self) -> Optional["pathspec.PathSpec"]:
def _load_gitignore(self) -> "pathspec.PathSpec" | None:
"""
Load .gitignore file and create pathspec matcher (C2.1).
@@ -894,7 +892,6 @@ class GitHubScraper:
logger.info(f"Data saved to: {self.data_file}")
class GitHubToSkillConverter:
"""
Convert extracted GitHub data to Claude skill format (C1.10).
@@ -1350,14 +1347,13 @@ Use this skill when you need to:
f.write(content)
logger.info(f"Generated: {structure_path}")
def setup_argument_parser() -> argparse.ArgumentParser:
"""Setup and configure command-line argument parser.
Creates an ArgumentParser with all CLI options for the github scraper.
All arguments are defined in skill_seekers.cli.arguments.github to ensure
consistency between the standalone scraper and unified CLI.
Returns:
argparse.ArgumentParser: Configured argument parser
"""
@@ -1378,7 +1374,6 @@ Examples:
return parser
def main():
"""C1.10: CLI tool entry point."""
parser = setup_argument_parser()
@@ -1476,6 +1471,5 @@ def main():
logger.error(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -28,7 +28,6 @@ from .update_parser import UpdateParser
from .multilang_parser import MultilangParser
from .quality_parser import QualityParser
# Registry of all parsers (in order of usage frequency)
PARSERS = [
CreateParser(), # NEW: Unified create command (placed first for prominence)
@@ -53,7 +52,6 @@ PARSERS = [
QualityParser(),
]
def register_parsers(subparsers):
"""Register all subcommand parsers.
@@ -66,7 +64,6 @@ def register_parsers(subparsers):
for parser_instance in PARSERS:
parser_instance.create_parser(subparsers)
def get_parser_names():
"""Get list of all subcommand names.
@@ -75,7 +72,6 @@ def get_parser_names():
"""
return [p.name for p in PARSERS]
__all__ = [
"SubcommandParser",
"PARSERS",

View File

@@ -9,7 +9,6 @@ Includes preset system support (Issue #268).
from .base import SubcommandParser
from skill_seekers.cli.arguments.analyze import add_analyze_arguments
class AnalyzeParser(SubcommandParser):
"""Parser for analyze subcommand."""
@@ -27,10 +26,10 @@ class AnalyzeParser(SubcommandParser):
def add_arguments(self, parser):
"""Add analyze-specific arguments.
Uses shared argument definitions to ensure consistency
with codebase_scraper.py (standalone scraper).
Includes preset system for simplified UX.
"""
add_analyze_arguments(parser)

View File

@@ -3,7 +3,6 @@
from abc import ABC, abstractmethod
import argparse
class SubcommandParser(ABC):
"""Base class for subcommand parsers.

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class ConfigParser(SubcommandParser):
"""Parser for config subcommand."""

View File

@@ -13,7 +13,6 @@ import argparse
from .base import SubcommandParser
from skill_seekers.cli.arguments.create import add_create_arguments
class CreateParser(SubcommandParser):
"""Parser for create subcommand with multi-mode help."""

View File

@@ -7,7 +7,6 @@ consistency with the standalone enhance_skill_local module.
from .base import SubcommandParser
from skill_seekers.cli.arguments.enhance import add_enhance_arguments
class EnhanceParser(SubcommandParser):
"""Parser for enhance subcommand."""
@@ -25,7 +24,7 @@ class EnhanceParser(SubcommandParser):
def add_arguments(self, parser):
"""Add enhance-specific arguments.
Uses shared argument definitions to ensure consistency
with enhance_skill_local.py (standalone enhancer).
"""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class EnhanceStatusParser(SubcommandParser):
"""Parser for enhance-status subcommand."""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class EstimateParser(SubcommandParser):
"""Parser for estimate subcommand."""

View File

@@ -7,7 +7,6 @@ consistency with the standalone github_scraper module.
from .base import SubcommandParser
from skill_seekers.cli.arguments.github import add_github_arguments
class GitHubParser(SubcommandParser):
"""Parser for github subcommand."""
@@ -25,7 +24,7 @@ class GitHubParser(SubcommandParser):
def add_arguments(self, parser):
"""Add github-specific arguments.
Uses shared argument definitions to ensure consistency
with github_scraper.py (standalone scraper).
"""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class InstallAgentParser(SubcommandParser):
"""Parser for install-agent subcommand."""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class InstallParser(SubcommandParser):
"""Parser for install subcommand."""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class MultilangParser(SubcommandParser):
"""Parser for multilang subcommand."""

View File

@@ -7,7 +7,6 @@ consistency with the standalone package_skill module.
from .base import SubcommandParser
from skill_seekers.cli.arguments.package import add_package_arguments
class PackageParser(SubcommandParser):
"""Parser for package subcommand."""
@@ -25,7 +24,7 @@ class PackageParser(SubcommandParser):
def add_arguments(self, parser):
"""Add package-specific arguments.
Uses shared argument definitions to ensure consistency
with package_skill.py (standalone packager).
"""

View File

@@ -7,7 +7,6 @@ consistency with the standalone pdf_scraper module.
from .base import SubcommandParser
from skill_seekers.cli.arguments.pdf import add_pdf_arguments
class PDFParser(SubcommandParser):
"""Parser for pdf subcommand."""
@@ -25,7 +24,7 @@ class PDFParser(SubcommandParser):
def add_arguments(self, parser):
"""Add pdf-specific arguments.
Uses shared argument definitions to ensure consistency
with pdf_scraper.py (standalone scraper).
"""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class QualityParser(SubcommandParser):
"""Parser for quality subcommand."""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class ResumeParser(SubcommandParser):
"""Parser for resume subcommand."""

View File

@@ -7,7 +7,6 @@ consistency with the standalone doc_scraper module.
from .base import SubcommandParser
from skill_seekers.cli.arguments.scrape import add_scrape_arguments
class ScrapeParser(SubcommandParser):
"""Parser for scrape subcommand."""
@@ -25,7 +24,7 @@ class ScrapeParser(SubcommandParser):
def add_arguments(self, parser):
"""Add scrape-specific arguments.
Uses shared argument definitions to ensure consistency
with doc_scraper.py (standalone scraper).
"""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class StreamParser(SubcommandParser):
"""Parser for stream subcommand."""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class TestExamplesParser(SubcommandParser):
"""Parser for extract-test-examples subcommand."""

View File

@@ -7,7 +7,6 @@ consistency with the standalone unified_scraper module.
from .base import SubcommandParser
from skill_seekers.cli.arguments.unified import add_unified_arguments
class UnifiedParser(SubcommandParser):
"""Parser for unified subcommand."""
@@ -25,7 +24,7 @@ class UnifiedParser(SubcommandParser):
def add_arguments(self, parser):
"""Add unified-specific arguments.
Uses shared argument definitions to ensure consistency
with unified_scraper.py (standalone scraper).
"""

View File

@@ -2,7 +2,6 @@
from .base import SubcommandParser
class UpdateParser(SubcommandParser):
"""Parser for update subcommand."""

View File

@@ -7,7 +7,6 @@ consistency with the standalone upload_skill module.
from .base import SubcommandParser
from skill_seekers.cli.arguments.upload import add_upload_arguments
class UploadParser(SubcommandParser):
"""Parser for upload subcommand."""
@@ -25,7 +24,7 @@ class UploadParser(SubcommandParser):
def add_arguments(self, parser):
"""Add upload-specific arguments.
Uses shared argument definitions to ensure consistency
with upload_skill.py (standalone uploader).
"""

View File

@@ -20,7 +20,6 @@ from .manager import (
# Analyze presets
from .analyze_presets import (
AnalysisPreset as AnalyzeAnalysisPreset, # Alternative version (without enhance_level)
ANALYZE_PRESETS,
apply_analyze_preset,
get_preset_help_text,

View File

@@ -12,17 +12,16 @@ Examples:
"""
from dataclasses import dataclass, field
from typing import Dict, Optional
import argparse
import argparse
@dataclass(frozen=True)
class AnalysisPreset:
"""Definition of an analysis preset.
Presets control analysis depth and features ONLY.
AI Enhancement is controlled separately via --enhance or --enhance-level.
Attributes:
name: Human-readable preset name
description: Brief description of what this preset does
@@ -33,10 +32,9 @@ class AnalysisPreset:
name: str
description: str
depth: str
features: Dict[str, bool] = field(default_factory=dict)
features: dict[str, bool] = field(default_factory=dict)
estimated_time: str = ""
# Preset definitions
ANALYZE_PRESETS = {
"quick": AnalysisPreset(
@@ -53,7 +51,7 @@ ANALYZE_PRESETS = {
},
estimated_time="1-2 minutes"
),
"standard": AnalysisPreset(
name="Standard",
description="Balanced analysis with core features (recommended)",
@@ -68,7 +66,7 @@ ANALYZE_PRESETS = {
},
estimated_time="5-10 minutes"
),
"comprehensive": AnalysisPreset(
name="Comprehensive",
description="Full analysis with all features",
@@ -85,21 +83,20 @@ ANALYZE_PRESETS = {
),
}
def apply_analyze_preset(args: argparse.Namespace, preset_name: str) -> None:
"""Apply an analysis preset to the args namespace.
This modifies the args object to set the preset's depth and feature flags.
NOTE: This does NOT set enhance_level - that's controlled separately via
--enhance or --enhance-level flags.
Args:
args: The argparse.Namespace to modify
preset_name: Name of the preset to apply
Raises:
KeyError: If preset_name is not a valid preset
Example:
>>> args = parser.parse_args(['--directory', '.', '--preset', 'quick'])
>>> apply_analyze_preset(args, args.preset)
@@ -107,22 +104,21 @@ def apply_analyze_preset(args: argparse.Namespace, preset_name: str) -> None:
>>> # enhance_level is still 0 (default) unless --enhance was specified
"""
preset = ANALYZE_PRESETS[preset_name]
# Set depth
args.depth = preset.depth
# Set feature flags (skip_* attributes)
for feature, enabled in preset.features.items():
skip_attr = f"skip_{feature}"
setattr(args, skip_attr, not enabled)
def get_preset_help_text(preset_name: str) -> str:
"""Get formatted help text for a preset.
Args:
preset_name: Name of the preset
Returns:
Formatted help string
"""
@@ -133,29 +129,28 @@ def get_preset_help_text(preset_name: str) -> str:
f" Depth: {preset.depth}"
)
def show_preset_list() -> None:
"""Print the list of available presets to stdout.
This is used by the --preset-list flag.
"""
print("\nAvailable Analysis Presets")
print("=" * 60)
print()
for name, preset in ANALYZE_PRESETS.items():
marker = " (DEFAULT)" if name == "standard" else ""
print(f" {name}{marker}")
print(f" {preset.description}")
print(f" Estimated time: {preset.estimated_time}")
print(f" Depth: {preset.depth}")
# Show enabled features
enabled = [f for f, v in preset.features.items() if v]
if enabled:
print(f" Features: {', '.join(enabled)}")
print()
print("AI Enhancement (separate from presets):")
print(" --enhance Enable AI enhancement (default level 1)")
print(" --enhance-level N Set AI enhancement level (0-3)")
@@ -166,91 +161,88 @@ def show_preset_list() -> None:
print(" skill-seekers analyze --directory <dir> --preset comprehensive --enhance-level 2")
print()
def resolve_enhance_level(args: argparse.Namespace) -> int:
"""Determine the enhance level based on user arguments.
This is separate from preset application. Enhance level is controlled by:
- --enhance-level N (explicit)
- --enhance (use default level 1)
- Neither (default to 0)
Args:
args: Parsed command-line arguments
Returns:
The enhance level to use (0-3)
"""
# Explicit enhance level takes priority
if args.enhance_level is not None:
return args.enhance_level
# --enhance flag enables default level (1)
if args.enhance:
return 1
# Default is no enhancement
return 0
def apply_preset_with_warnings(args: argparse.Namespace) -> str:
"""Apply preset with deprecation warnings for legacy flags.
This is the main entry point for applying presets. It:
1. Determines which preset to use
2. Prints deprecation warnings if legacy flags were used
3. Applies the preset (depth and features only)
4. Sets enhance_level separately based on --enhance/--enhance-level
5. Returns the preset name
Args:
args: Parsed command-line arguments
Returns:
The preset name that was applied
"""
preset_name = None
# Check for explicit preset
if args.preset:
preset_name = args.preset
# Check for legacy flags and print warnings
elif args.quick:
print_deprecation_warning("--quick", "--preset quick")
preset_name = "quick"
elif args.comprehensive:
print_deprecation_warning("--comprehensive", "--preset comprehensive")
preset_name = "comprehensive"
elif args.depth:
depth_to_preset = {
"surface": "quick",
"deep": "standard",
"deep": "standard",
"full": "comprehensive",
}
if args.depth in depth_to_preset:
new_flag = f"--preset {depth_to_preset[args.depth]}"
print_deprecation_warning(f"--depth {args.depth}", new_flag)
preset_name = depth_to_preset[args.depth]
# Default to standard
if preset_name is None:
preset_name = "standard"
# Apply the preset (depth and features only)
apply_analyze_preset(args, preset_name)
# Set enhance_level separately (not part of preset)
args.enhance_level = resolve_enhance_level(args)
return preset_name
return preset_name
def print_deprecation_warning(old_flag: str, new_flag: str) -> None:
"""Print a deprecation warning for legacy flags.
Args:
old_flag: The old/deprecated flag name
new_flag: The new recommended flag/preset

View File

@@ -9,14 +9,13 @@ Presets:
"""
from dataclasses import dataclass, field
from typing import Dict
import argparse
import argparse
@dataclass(frozen=True)
class GitHubPreset:
"""Definition of a GitHub preset.
Attributes:
name: Human-readable preset name
description: Brief description of what this preset does
@@ -27,10 +26,9 @@ class GitHubPreset:
name: str
description: str
max_issues: int
features: Dict[str, bool] = field(default_factory=dict)
features: dict[str, bool] = field(default_factory=dict)
estimated_time: str = ""
# Preset definitions
GITHUB_PRESETS = {
"quick": GitHubPreset(
@@ -44,7 +42,7 @@ GITHUB_PRESETS = {
},
estimated_time="1-3 minutes"
),
"standard": GitHubPreset(
name="Standard",
description="Balanced scraping with issues and releases (recommended)",
@@ -56,7 +54,7 @@ GITHUB_PRESETS = {
},
estimated_time="5-15 minutes"
),
"comprehensive": GitHubPreset(
name="Comprehensive",
description="Comprehensive scraping with all available data",
@@ -70,48 +68,46 @@ GITHUB_PRESETS = {
),
}
def apply_github_preset(args: argparse.Namespace, preset_name: str) -> None:
"""Apply a GitHub preset to the args namespace.
Args:
args: The argparse.Namespace to modify
preset_name: Name of the preset to apply
Raises:
KeyError: If preset_name is not a valid preset
"""
preset = GITHUB_PRESETS[preset_name]
# Apply max_issues only if not set by user
if args.max_issues is None or args.max_issues == 100: # 100 is default
args.max_issues = preset.max_issues
# Apply feature flags (only if not explicitly disabled by user)
for feature, enabled in preset.features.items():
skip_attr = f"no_{feature}"
if not hasattr(args, skip_attr) or not getattr(args, skip_attr):
setattr(args, skip_attr, not enabled)
def show_github_preset_list() -> None:
"""Print the list of available GitHub presets to stdout."""
print("\nAvailable GitHub Presets")
print("=" * 60)
print()
for name, preset in GITHUB_PRESETS.items():
marker = " (DEFAULT)" if name == "standard" else ""
print(f" {name}{marker}")
print(f" {preset.description}")
print(f" Estimated time: {preset.estimated_time}")
print(f" Max issues: {preset.max_issues}")
# Show enabled features
enabled = [f.replace("include_", "") for f, v in preset.features.items() if v]
if enabled:
print(f" Features: {', '.join(enabled)}")
print()
print("Usage: skill-seekers github --repo <owner/repo> --preset <name>")
print()

View File

@@ -6,7 +6,6 @@ between speed and comprehensiveness.
from dataclasses import dataclass
@dataclass
class AnalysisPreset:
"""Analysis preset configuration.
@@ -23,7 +22,6 @@ class AnalysisPreset:
estimated_time: str
icon: str
# Preset definitions
PRESETS = {
"quick": AnalysisPreset(
@@ -79,7 +77,6 @@ PRESETS = {
),
}
class PresetManager:
"""Manages analysis presets and applies them to CLI arguments."""
@@ -167,7 +164,6 @@ class PresetManager:
"""
return "standard"
# Public API
__all__ = [
"AnalysisPreset",

View File

@@ -9,14 +9,13 @@ Presets:
"""
from dataclasses import dataclass, field
from typing import Dict, Optional
import argparse
import argparse
@dataclass(frozen=True)
class ScrapePreset:
"""Definition of a scrape preset.
Attributes:
name: Human-readable preset name
description: Brief description of what this preset does
@@ -29,12 +28,11 @@ class ScrapePreset:
name: str
description: str
rate_limit: float
features: Dict[str, bool] = field(default_factory=dict)
features: dict[str, bool] = field(default_factory=dict)
async_mode: bool = False
workers: int = 1
estimated_time: str = ""
# Preset definitions
SCRAPE_PRESETS = {
"quick": ScrapePreset(
@@ -49,7 +47,7 @@ SCRAPE_PRESETS = {
workers=5,
estimated_time="2-5 minutes"
),
"standard": ScrapePreset(
name="Standard",
description="Balanced scraping with good coverage (recommended)",
@@ -62,7 +60,7 @@ SCRAPE_PRESETS = {
workers=3,
estimated_time="10-30 minutes"
),
"comprehensive": ScrapePreset(
name="Comprehensive",
description="Comprehensive scraping with all features",
@@ -77,43 +75,40 @@ SCRAPE_PRESETS = {
),
}
def apply_scrape_preset(args: argparse.Namespace, preset_name: str) -> None:
"""Apply a scrape preset to the args namespace.
Args:
args: The argparse.Namespace to modify
preset_name: Name of the preset to apply
Raises:
KeyError: If preset_name is not a valid preset
"""
preset = SCRAPE_PRESETS[preset_name]
# Apply rate limit (only if not set by user)
if args.rate_limit is None:
args.rate_limit = preset.rate_limit
# Apply workers (only if not set by user)
if args.workers is None:
args.workers = preset.workers
# Apply async mode
args.async_mode = preset.async_mode
# Apply feature flags
for feature, enabled in preset.features.items():
if feature == "rag_chunking":
if not hasattr(args, 'chunk_for_rag') or not args.chunk_for_rag:
args.chunk_for_rag = enabled
if feature == "rag_chunking" and (not hasattr(args, 'chunk_for_rag') or not args.chunk_for_rag):
args.chunk_for_rag = enabled
def show_scrape_preset_list() -> None:
"""Print the list of available scrape presets to stdout."""
print("\nAvailable Scrape Presets")
print("=" * 60)
print()
for name, preset in SCRAPE_PRESETS.items():
marker = " (DEFAULT)" if name == "standard" else ""
print(f" {name}{marker}")
@@ -122,6 +117,6 @@ def show_scrape_preset_list() -> None:
print(f" Workers: {preset.workers}")
print(f" Async: {preset.async_mode}, Rate limit: {preset.rate_limit}s")
print()
print("Usage: skill-seekers scrape <url> --preset <name>")
print()

View File

@@ -7,13 +7,12 @@ local directory, PDF file, or config file based on patterns.
import os
import re
from dataclasses import dataclass
from typing import Dict, Any, Optional
from typing import Any
from urllib.parse import urlparse
import logging
logger = logging.getLogger(__name__)
@dataclass
class SourceInfo:
"""Information about a detected source.
@@ -25,11 +24,10 @@ class SourceInfo:
raw_input: Original user input
"""
type: str
parsed: Dict[str, Any]
parsed: dict[str, Any]
suggested_name: str
raw_input: str
class SourceDetector:
"""Detects source type from user input and extracts relevant information."""
@@ -124,7 +122,7 @@ class SourceDetector:
)
@classmethod
def _detect_github(cls, source: str) -> Optional[SourceInfo]:
def _detect_github(cls, source: str) -> SourceInfo | None:
"""Detect GitHub repository source.
Supports patterns:

View File

@@ -12,9 +12,6 @@ These tests verify that the unified CLI architecture works correctly:
import pytest
import subprocess
import argparse
import sys
from pathlib import Path
class TestParserSync:
"""E2E tests for parser synchronization (Issue #285)."""
@@ -77,7 +74,6 @@ class TestParserSync:
for flag in expected_flags:
assert flag in result.stdout, f"Help should show {flag} flag"
class TestPresetSystem:
"""E2E tests for preset system (Issue #268)."""
@@ -141,7 +137,6 @@ class TestPresetSystem:
assert "DEPRECATED" in output, "Should show deprecation warning"
assert "--preset comprehensive" in output, "Should suggest alternative"
class TestBackwardCompatibility:
"""E2E tests for backward compatibility."""
@@ -185,7 +180,6 @@ class TestBackwardCompatibility:
assert flag in unified_result.stdout, f"Unified should have {flag}"
assert flag in standalone_result.stdout, f"Standalone should have {flag}"
class TestProgrammaticAPI:
"""Test that the shared argument functions work programmatically."""
@@ -227,7 +221,6 @@ class TestProgrammaticAPI:
# Note: enhance_level is not part of AnalysisPreset anymore.
# It's controlled separately via --enhance-level flag (default 2)
class TestIntegration:
"""Integration tests for the complete flow."""
@@ -278,7 +271,6 @@ class TestIntegration:
assert "--preset" in result.stdout, "Should show --preset flag"
assert "DEFAULT" in result.stdout or "default" in result.stdout, "Should indicate default preset"
class TestE2EWorkflow:
"""End-to-end workflow tests."""
@@ -322,6 +314,5 @@ class TestE2EWorkflow:
assert "--preset" in result.stdout, "Should have --preset flag"
assert "unrecognized arguments" not in result.stderr.lower()
if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])

View File

@@ -6,7 +6,6 @@ Tests the three-tier argument system:
3. Advanced arguments
"""
import pytest
from skill_seekers.cli.arguments.create import (
UNIVERSAL_ARGUMENTS,
WEB_ARGUMENTS,
@@ -20,7 +19,6 @@ from skill_seekers.cli.arguments.create import (
add_create_arguments,
)
class TestUniversalArguments:
"""Test universal argument definitions."""
@@ -51,7 +49,6 @@ class TestUniversalArguments:
assert 'kwargs' in arg_def
assert 'help' in arg_def['kwargs']
class TestSourceSpecificArguments:
"""Test source-specific argument definitions."""
@@ -96,7 +93,6 @@ class TestSourceSpecificArguments:
assert flag not in all_flags, f"Duplicate flag: {flag}"
all_flags.add(flag)
class TestAdvancedArguments:
"""Test advanced/rare argument definitions."""
@@ -106,7 +102,6 @@ class TestAdvancedArguments:
assert 'no_rate_limit' in ADVANCED_ARGUMENTS
assert 'interactive_enhancement' in ADVANCED_ARGUMENTS
class TestArgumentHelpers:
"""Test helper functions."""
@@ -148,7 +143,6 @@ class TestArgumentHelpers:
args = get_source_specific_arguments('unknown')
assert args == {}
class TestCompatibleArguments:
"""Test compatible argument detection."""
@@ -217,7 +211,6 @@ class TestCompatibleArguments:
assert 'repo' not in compatible
assert 'directory' not in compatible
class TestAddCreateArguments:
"""Test add_create_arguments function."""
@@ -284,7 +277,6 @@ class TestAddCreateArguments:
args = parser.parse_args(['some_source'])
assert args.source == 'some_source'
class TestNoDuplicates:
"""Test that there are no duplicate arguments across tiers."""
@@ -320,7 +312,6 @@ class TestNoDuplicates:
assert len(github_flags & pdf_flags) == 0
assert len(local_flags & pdf_flags) == 0
class TestArgumentQuality:
"""Test argument definition quality."""

View File

@@ -5,10 +5,6 @@ and routes to the correct scrapers without actually scraping.
"""
import pytest
import tempfile
import os
from pathlib import Path
class TestCreateCommandBasic:
"""Basic integration tests for create command (dry-run mode)."""
@@ -124,7 +120,6 @@ class TestCreateCommandBasic:
assert '--preset' in result.stdout
assert '--dry-run' in result.stdout
class TestBackwardCompatibility:
"""Test that old commands still work."""

View File

@@ -6,8 +6,6 @@ the same arguments as the standalone scraper modules. This prevents the
"""
import argparse
import pytest
class TestScrapeParserSync:
"""Ensure scrape_parser has all arguments from doc_scraper."""
@@ -87,7 +85,6 @@ class TestScrapeParserSync:
for arg in required_args:
assert arg in arg_dests, f"Required argument '{arg}' missing from scrape parser"
class TestGitHubParserSync:
"""Ensure github_parser has all arguments from github_scraper."""
@@ -131,7 +128,6 @@ class TestGitHubParserSync:
assert not missing, f"github_parser missing arguments: {missing}"
assert not extra, f"github_parser has extra arguments not in github_scraper: {extra}"
class TestUnifiedCLI:
"""Test the unified CLI main parser."""

View File

@@ -9,13 +9,10 @@ Tests the SourceDetector class's ability to identify and parse:
"""
import os
import tempfile
import pytest
from pathlib import Path
from skill_seekers.cli.source_detector import SourceDetector, SourceInfo
class TestWebDetection:
"""Test web URL detection."""
@@ -58,7 +55,6 @@ class TestWebDetection:
assert info.type == 'web'
assert info.suggested_name == 'vue'
class TestGitHubDetection:
"""Test GitHub repository detection."""
@@ -97,7 +93,6 @@ class TestGitHubDetection:
assert info.parsed['repo'] == "microsoft/vscode-python"
assert info.suggested_name == 'vscode-python'
class TestLocalDetection:
"""Test local directory detection."""
@@ -136,7 +131,6 @@ class TestLocalDetection:
assert info.type == 'local'
assert info.parsed['directory'] == cwd
class TestPDFDetection:
"""Test PDF file detection."""
@@ -160,7 +154,6 @@ class TestPDFDetection:
assert info.type == 'pdf'
assert info.suggested_name == 'my-awesome-guide'
class TestConfigDetection:
"""Test config file detection."""
@@ -178,7 +171,6 @@ class TestConfigDetection:
assert info.parsed['config_path'] == "configs/django.json"
assert info.suggested_name == 'django'
class TestValidation:
"""Test source validation."""
@@ -246,7 +238,6 @@ class TestValidation:
)
SourceDetector.validate_source(info)
class TestAmbiguousCases:
"""Test handling of ambiguous inputs."""
@@ -277,7 +268,6 @@ class TestAmbiguousCases:
# Should detect as local directory, not web
assert info.type == 'local'
class TestRawInputPreservation:
"""Test that raw_input is preserved correctly."""
@@ -302,7 +292,6 @@ class TestRawInputPreservation:
info = SourceDetector.detect(original)
assert info.raw_input == original
class TestEdgeCases:
"""Test edge cases and corner cases."""