style: Auto-format 48 files with ruff format
- Fixed formatting to comply with ruff standards - No functional changes, only formatting/style - Completes CI/CD pipeline formatting requirements
This commit is contained in:
@@ -171,6 +171,7 @@ ANALYZE_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_analyze_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_analyze_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add all analyze command arguments to a parser."""
|
"""Add all analyze command arguments to a parser."""
|
||||||
for arg_name, arg_def in ANALYZE_ARGUMENTS.items():
|
for arg_name, arg_def in ANALYZE_ARGUMENTS.items():
|
||||||
@@ -178,6 +179,7 @@ def add_analyze_arguments(parser: argparse.ArgumentParser) -> None:
|
|||||||
kwargs = arg_def["kwargs"]
|
kwargs = arg_def["kwargs"]
|
||||||
parser.add_argument(*flags, **kwargs)
|
parser.add_argument(*flags, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_analyze_argument_names() -> set:
|
def get_analyze_argument_names() -> set:
|
||||||
"""Get the set of analyze argument destination names."""
|
"""Get the set of analyze argument destination names."""
|
||||||
return set(ANALYZE_ARGUMENTS.keys())
|
return set(ANALYZE_ARGUMENTS.keys())
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ RAG_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_common_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_common_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add common arguments to a parser.
|
"""Add common arguments to a parser.
|
||||||
|
|
||||||
@@ -114,6 +115,7 @@ def add_common_arguments(parser: argparse.ArgumentParser) -> None:
|
|||||||
kwargs = arg_def["kwargs"]
|
kwargs = arg_def["kwargs"]
|
||||||
parser.add_argument(*flags, **kwargs)
|
parser.add_argument(*flags, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_common_argument_names() -> set:
|
def get_common_argument_names() -> set:
|
||||||
"""Get the set of common argument destination names.
|
"""Get the set of common argument destination names.
|
||||||
|
|
||||||
@@ -122,6 +124,7 @@ def get_common_argument_names() -> set:
|
|||||||
"""
|
"""
|
||||||
return set(COMMON_ARGUMENTS.keys())
|
return set(COMMON_ARGUMENTS.keys())
|
||||||
|
|
||||||
|
|
||||||
def add_rag_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_rag_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add RAG (Retrieval-Augmented Generation) arguments to a parser.
|
"""Add RAG (Retrieval-Augmented Generation) arguments to a parser.
|
||||||
|
|
||||||
@@ -140,6 +143,7 @@ def add_rag_arguments(parser: argparse.ArgumentParser) -> None:
|
|||||||
kwargs = arg_def["kwargs"]
|
kwargs = arg_def["kwargs"]
|
||||||
parser.add_argument(*flags, **kwargs)
|
parser.add_argument(*flags, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_rag_argument_names() -> set:
|
def get_rag_argument_names() -> set:
|
||||||
"""Get the set of RAG argument destination names.
|
"""Get the set of RAG argument destination names.
|
||||||
|
|
||||||
@@ -148,6 +152,7 @@ def get_rag_argument_names() -> set:
|
|||||||
"""
|
"""
|
||||||
return set(RAG_ARGUMENTS.keys())
|
return set(RAG_ARGUMENTS.keys())
|
||||||
|
|
||||||
|
|
||||||
def get_argument_help(arg_name: str) -> str:
|
def get_argument_help(arg_name: str) -> str:
|
||||||
"""Get the help text for a common argument.
|
"""Get the help text for a common argument.
|
||||||
|
|
||||||
|
|||||||
@@ -388,10 +388,12 @@ ADVANCED_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
# HELPER FUNCTIONS
|
# HELPER FUNCTIONS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
def get_universal_argument_names() -> set[str]:
|
def get_universal_argument_names() -> set[str]:
|
||||||
"""Get set of universal argument names."""
|
"""Get set of universal argument names."""
|
||||||
return set(UNIVERSAL_ARGUMENTS.keys())
|
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.
|
"""Get source-specific arguments for a given source type.
|
||||||
|
|
||||||
@@ -402,14 +404,15 @@ def get_source_specific_arguments(source_type: str) -> dict[str, dict[str, Any]]
|
|||||||
Dict of argument definitions
|
Dict of argument definitions
|
||||||
"""
|
"""
|
||||||
source_args = {
|
source_args = {
|
||||||
'web': WEB_ARGUMENTS,
|
"web": WEB_ARGUMENTS,
|
||||||
'github': GITHUB_ARGUMENTS,
|
"github": GITHUB_ARGUMENTS,
|
||||||
'local': LOCAL_ARGUMENTS,
|
"local": LOCAL_ARGUMENTS,
|
||||||
'pdf': PDF_ARGUMENTS,
|
"pdf": PDF_ARGUMENTS,
|
||||||
'config': {}, # Config files don't have extra args
|
"config": {}, # Config files don't have extra args
|
||||||
}
|
}
|
||||||
return source_args.get(source_type, {})
|
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.
|
"""Get list of compatible argument names for a source type.
|
||||||
|
|
||||||
@@ -431,7 +434,8 @@ def get_compatible_arguments(source_type: str) -> list[str]:
|
|||||||
|
|
||||||
return compatible
|
return compatible
|
||||||
|
|
||||||
def add_create_arguments(parser: argparse.ArgumentParser, mode: str = 'default') -> None:
|
|
||||||
|
def add_create_arguments(parser: argparse.ArgumentParser, mode: str = "default") -> None:
|
||||||
"""Add create command arguments to parser.
|
"""Add create command arguments to parser.
|
||||||
|
|
||||||
Supports multiple help modes for progressive disclosure:
|
Supports multiple help modes for progressive disclosure:
|
||||||
@@ -449,10 +453,10 @@ def add_create_arguments(parser: argparse.ArgumentParser, mode: str = 'default')
|
|||||||
"""
|
"""
|
||||||
# Positional argument for source
|
# Positional argument for source
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'source',
|
"source",
|
||||||
nargs='?',
|
nargs="?",
|
||||||
type=str,
|
type=str,
|
||||||
help='Source to create skill from (URL, GitHub repo, directory, PDF, or config file)'
|
help="Source to create skill from (URL, GitHub repo, directory, PDF, or config file)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Always add universal arguments
|
# Always add universal arguments
|
||||||
@@ -460,23 +464,23 @@ def add_create_arguments(parser: argparse.ArgumentParser, mode: str = 'default')
|
|||||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||||
|
|
||||||
# Add source-specific arguments based on mode
|
# Add source-specific arguments based on mode
|
||||||
if mode in ['web', 'all']:
|
if mode in ["web", "all"]:
|
||||||
for arg_name, arg_def in WEB_ARGUMENTS.items():
|
for arg_name, arg_def in WEB_ARGUMENTS.items():
|
||||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||||
|
|
||||||
if mode in ['github', 'all']:
|
if mode in ["github", "all"]:
|
||||||
for arg_name, arg_def in GITHUB_ARGUMENTS.items():
|
for arg_name, arg_def in GITHUB_ARGUMENTS.items():
|
||||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||||
|
|
||||||
if mode in ['local', 'all']:
|
if mode in ["local", "all"]:
|
||||||
for arg_name, arg_def in LOCAL_ARGUMENTS.items():
|
for arg_name, arg_def in LOCAL_ARGUMENTS.items():
|
||||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||||
|
|
||||||
if mode in ['pdf', 'all']:
|
if mode in ["pdf", "all"]:
|
||||||
for arg_name, arg_def in PDF_ARGUMENTS.items():
|
for arg_name, arg_def in PDF_ARGUMENTS.items():
|
||||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||||
|
|
||||||
# Add advanced arguments if requested
|
# Add advanced arguments if requested
|
||||||
if mode in ['advanced', 'all']:
|
if mode in ["advanced", "all"]:
|
||||||
for arg_name, arg_def in ADVANCED_ARGUMENTS.items():
|
for arg_name, arg_def in ADVANCED_ARGUMENTS.items():
|
||||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ ENHANCE_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_enhance_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_enhance_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add all enhance command arguments to a parser."""
|
"""Add all enhance command arguments to a parser."""
|
||||||
for arg_name, arg_def in ENHANCE_ARGUMENTS.items():
|
for arg_name, arg_def in ENHANCE_ARGUMENTS.items():
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ GITHUB_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_github_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_github_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add all github command arguments to a parser.
|
"""Add all github command arguments to a parser.
|
||||||
|
|
||||||
@@ -153,6 +154,7 @@ def add_github_arguments(parser: argparse.ArgumentParser) -> None:
|
|||||||
kwargs = arg_def["kwargs"]
|
kwargs = arg_def["kwargs"]
|
||||||
parser.add_argument(*flags, **kwargs)
|
parser.add_argument(*flags, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_github_argument_names() -> set:
|
def get_github_argument_names() -> set:
|
||||||
"""Get the set of github argument destination names.
|
"""Get the set of github argument destination names.
|
||||||
|
|
||||||
@@ -161,6 +163,7 @@ def get_github_argument_names() -> set:
|
|||||||
"""
|
"""
|
||||||
return set(GITHUB_ARGUMENTS.keys())
|
return set(GITHUB_ARGUMENTS.keys())
|
||||||
|
|
||||||
|
|
||||||
def get_github_argument_count() -> int:
|
def get_github_argument_count() -> int:
|
||||||
"""Get the total number of github arguments.
|
"""Get the total number of github arguments.
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ PACKAGE_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_package_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_package_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add all package command arguments to a parser."""
|
"""Add all package command arguments to a parser."""
|
||||||
for arg_name, arg_def in PACKAGE_ARGUMENTS.items():
|
for arg_name, arg_def in PACKAGE_ARGUMENTS.items():
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ PDF_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_pdf_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_pdf_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add all pdf command arguments to a parser."""
|
"""Add all pdf command arguments to a parser."""
|
||||||
for arg_name, arg_def in PDF_ARGUMENTS.items():
|
for arg_name, arg_def in PDF_ARGUMENTS.items():
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ SCRAPE_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
# Merge RAG arguments from common.py
|
# Merge RAG arguments from common.py
|
||||||
SCRAPE_ARGUMENTS.update(RAG_ARGUMENTS)
|
SCRAPE_ARGUMENTS.update(RAG_ARGUMENTS)
|
||||||
|
|
||||||
|
|
||||||
def add_scrape_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_scrape_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add all scrape command arguments to a parser.
|
"""Add all scrape command arguments to a parser.
|
||||||
|
|
||||||
@@ -218,6 +219,7 @@ def add_scrape_arguments(parser: argparse.ArgumentParser) -> None:
|
|||||||
kwargs = arg_def["kwargs"]
|
kwargs = arg_def["kwargs"]
|
||||||
parser.add_argument(*flags, **kwargs)
|
parser.add_argument(*flags, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_scrape_argument_names() -> set:
|
def get_scrape_argument_names() -> set:
|
||||||
"""Get the set of scrape argument destination names.
|
"""Get the set of scrape argument destination names.
|
||||||
|
|
||||||
@@ -226,6 +228,7 @@ def get_scrape_argument_names() -> set:
|
|||||||
"""
|
"""
|
||||||
return set(SCRAPE_ARGUMENTS.keys())
|
return set(SCRAPE_ARGUMENTS.keys())
|
||||||
|
|
||||||
|
|
||||||
def get_scrape_argument_count() -> int:
|
def get_scrape_argument_count() -> int:
|
||||||
"""Get the total number of scrape arguments.
|
"""Get the total number of scrape arguments.
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ UNIFIED_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_unified_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_unified_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add all unified command arguments to a parser."""
|
"""Add all unified command arguments to a parser."""
|
||||||
for arg_name, arg_def in UNIFIED_ARGUMENTS.items():
|
for arg_name, arg_def in UNIFIED_ARGUMENTS.items():
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ UPLOAD_ARGUMENTS: dict[str, dict[str, Any]] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def add_upload_arguments(parser: argparse.ArgumentParser) -> None:
|
def add_upload_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add all upload command arguments to a parser."""
|
"""Add all upload command arguments to a parser."""
|
||||||
for arg_name, arg_def in UPLOAD_ARGUMENTS.items():
|
for arg_name, arg_def in UPLOAD_ARGUMENTS.items():
|
||||||
|
|||||||
@@ -870,7 +870,7 @@ def main():
|
|||||||
|
|
||||||
# AI Enhancement (if requested)
|
# AI Enhancement (if requested)
|
||||||
enhance_mode = args.ai_mode
|
enhance_mode = args.ai_mode
|
||||||
if getattr(args, 'enhance_level', 0) > 0:
|
if getattr(args, "enhance_level", 0) > 0:
|
||||||
# Auto-detect mode if enhance_level is set
|
# Auto-detect mode if enhance_level is set
|
||||||
enhance_mode = "auto" # ConfigEnhancer will auto-detect API vs LOCAL
|
enhance_mode = "auto" # ConfigEnhancer will auto-detect API vs LOCAL
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from skill_seekers.cli.arguments.create import (
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CreateCommand:
|
class CreateCommand:
|
||||||
"""Unified create command implementation."""
|
"""Unified create command implementation."""
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ class CreateCommand:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Skip internal arguments
|
# Skip internal arguments
|
||||||
if arg_name in ['source', 'func', 'subcommand']:
|
if arg_name in ["source", "func", "subcommand"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Warn about incompatible argument
|
# Warn about incompatible argument
|
||||||
@@ -104,10 +105,10 @@ class CreateCommand:
|
|||||||
|
|
||||||
# Check against common defaults
|
# Check against common defaults
|
||||||
defaults = {
|
defaults = {
|
||||||
'max_issues': 100,
|
"max_issues": 100,
|
||||||
'chunk_size': 512,
|
"chunk_size": 512,
|
||||||
'chunk_overlap': 50,
|
"chunk_overlap": 50,
|
||||||
'output': None,
|
"output": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
if arg_name in defaults:
|
if arg_name in defaults:
|
||||||
@@ -122,15 +123,15 @@ class CreateCommand:
|
|||||||
Returns:
|
Returns:
|
||||||
Exit code from scraper
|
Exit code from scraper
|
||||||
"""
|
"""
|
||||||
if self.source_info.type == 'web':
|
if self.source_info.type == "web":
|
||||||
return self._route_web()
|
return self._route_web()
|
||||||
elif self.source_info.type == 'github':
|
elif self.source_info.type == "github":
|
||||||
return self._route_github()
|
return self._route_github()
|
||||||
elif self.source_info.type == 'local':
|
elif self.source_info.type == "local":
|
||||||
return self._route_local()
|
return self._route_local()
|
||||||
elif self.source_info.type == 'pdf':
|
elif self.source_info.type == "pdf":
|
||||||
return self._route_pdf()
|
return self._route_pdf()
|
||||||
elif self.source_info.type == 'config':
|
elif self.source_info.type == "config":
|
||||||
return self._route_config()
|
return self._route_config()
|
||||||
else:
|
else:
|
||||||
logger.error(f"Unknown source type: {self.source_info.type}")
|
logger.error(f"Unknown source type: {self.source_info.type}")
|
||||||
@@ -141,10 +142,10 @@ class CreateCommand:
|
|||||||
from skill_seekers.cli import doc_scraper
|
from skill_seekers.cli import doc_scraper
|
||||||
|
|
||||||
# Reconstruct argv for doc_scraper
|
# Reconstruct argv for doc_scraper
|
||||||
argv = ['doc_scraper']
|
argv = ["doc_scraper"]
|
||||||
|
|
||||||
# Add URL
|
# Add URL
|
||||||
url = self.source_info.parsed['url']
|
url = self.source_info.parsed["url"]
|
||||||
argv.append(url)
|
argv.append(url)
|
||||||
|
|
||||||
# Add universal arguments
|
# Add universal arguments
|
||||||
@@ -152,21 +153,21 @@ class CreateCommand:
|
|||||||
|
|
||||||
# Add web-specific arguments
|
# Add web-specific arguments
|
||||||
if self.args.max_pages:
|
if self.args.max_pages:
|
||||||
argv.extend(['--max-pages', str(self.args.max_pages)])
|
argv.extend(["--max-pages", str(self.args.max_pages)])
|
||||||
if getattr(self.args, 'skip_scrape', False):
|
if getattr(self.args, "skip_scrape", False):
|
||||||
argv.append('--skip-scrape')
|
argv.append("--skip-scrape")
|
||||||
if getattr(self.args, 'resume', False):
|
if getattr(self.args, "resume", False):
|
||||||
argv.append('--resume')
|
argv.append("--resume")
|
||||||
if getattr(self.args, 'fresh', False):
|
if getattr(self.args, "fresh", False):
|
||||||
argv.append('--fresh')
|
argv.append("--fresh")
|
||||||
if getattr(self.args, 'rate_limit', None):
|
if getattr(self.args, "rate_limit", None):
|
||||||
argv.extend(['--rate-limit', str(self.args.rate_limit)])
|
argv.extend(["--rate-limit", str(self.args.rate_limit)])
|
||||||
if getattr(self.args, 'workers', None):
|
if getattr(self.args, "workers", None):
|
||||||
argv.extend(['--workers', str(self.args.workers)])
|
argv.extend(["--workers", str(self.args.workers)])
|
||||||
if getattr(self.args, 'async_mode', False):
|
if getattr(self.args, "async_mode", False):
|
||||||
argv.append('--async')
|
argv.append("--async")
|
||||||
if getattr(self.args, 'no_rate_limit', False):
|
if getattr(self.args, "no_rate_limit", False):
|
||||||
argv.append('--no-rate-limit')
|
argv.append("--no-rate-limit")
|
||||||
|
|
||||||
# Call doc_scraper with modified argv
|
# Call doc_scraper with modified argv
|
||||||
logger.debug(f"Calling doc_scraper with argv: {argv}")
|
logger.debug(f"Calling doc_scraper with argv: {argv}")
|
||||||
@@ -182,32 +183,32 @@ class CreateCommand:
|
|||||||
from skill_seekers.cli import github_scraper
|
from skill_seekers.cli import github_scraper
|
||||||
|
|
||||||
# Reconstruct argv for github_scraper
|
# Reconstruct argv for github_scraper
|
||||||
argv = ['github_scraper']
|
argv = ["github_scraper"]
|
||||||
|
|
||||||
# Add repo
|
# Add repo
|
||||||
repo = self.source_info.parsed['repo']
|
repo = self.source_info.parsed["repo"]
|
||||||
argv.extend(['--repo', repo])
|
argv.extend(["--repo", repo])
|
||||||
|
|
||||||
# Add universal arguments
|
# Add universal arguments
|
||||||
self._add_common_args(argv)
|
self._add_common_args(argv)
|
||||||
|
|
||||||
# Add GitHub-specific arguments
|
# Add GitHub-specific arguments
|
||||||
if getattr(self.args, 'token', None):
|
if getattr(self.args, "token", None):
|
||||||
argv.extend(['--token', self.args.token])
|
argv.extend(["--token", self.args.token])
|
||||||
if getattr(self.args, 'profile', None):
|
if getattr(self.args, "profile", None):
|
||||||
argv.extend(['--profile', self.args.profile])
|
argv.extend(["--profile", self.args.profile])
|
||||||
if getattr(self.args, 'non_interactive', False):
|
if getattr(self.args, "non_interactive", False):
|
||||||
argv.append('--non-interactive')
|
argv.append("--non-interactive")
|
||||||
if getattr(self.args, 'no_issues', False):
|
if getattr(self.args, "no_issues", False):
|
||||||
argv.append('--no-issues')
|
argv.append("--no-issues")
|
||||||
if getattr(self.args, 'no_changelog', False):
|
if getattr(self.args, "no_changelog", False):
|
||||||
argv.append('--no-changelog')
|
argv.append("--no-changelog")
|
||||||
if getattr(self.args, 'no_releases', False):
|
if getattr(self.args, "no_releases", False):
|
||||||
argv.append('--no-releases')
|
argv.append("--no-releases")
|
||||||
if getattr(self.args, 'max_issues', None) and self.args.max_issues != 100:
|
if getattr(self.args, "max_issues", None) and self.args.max_issues != 100:
|
||||||
argv.extend(['--max-issues', str(self.args.max_issues)])
|
argv.extend(["--max-issues", str(self.args.max_issues)])
|
||||||
if getattr(self.args, 'scrape_only', False):
|
if getattr(self.args, "scrape_only", False):
|
||||||
argv.append('--scrape-only')
|
argv.append("--scrape-only")
|
||||||
|
|
||||||
# Call github_scraper with modified argv
|
# Call github_scraper with modified argv
|
||||||
logger.debug(f"Calling github_scraper with argv: {argv}")
|
logger.debug(f"Calling github_scraper with argv: {argv}")
|
||||||
@@ -223,30 +224,30 @@ class CreateCommand:
|
|||||||
from skill_seekers.cli import codebase_scraper
|
from skill_seekers.cli import codebase_scraper
|
||||||
|
|
||||||
# Reconstruct argv for codebase_scraper
|
# Reconstruct argv for codebase_scraper
|
||||||
argv = ['codebase_scraper']
|
argv = ["codebase_scraper"]
|
||||||
|
|
||||||
# Add directory
|
# Add directory
|
||||||
directory = self.source_info.parsed['directory']
|
directory = self.source_info.parsed["directory"]
|
||||||
argv.extend(['--directory', directory])
|
argv.extend(["--directory", directory])
|
||||||
|
|
||||||
# Add universal arguments
|
# Add universal arguments
|
||||||
self._add_common_args(argv)
|
self._add_common_args(argv)
|
||||||
|
|
||||||
# Add local-specific arguments
|
# Add local-specific arguments
|
||||||
if getattr(self.args, 'languages', None):
|
if getattr(self.args, "languages", None):
|
||||||
argv.extend(['--languages', self.args.languages])
|
argv.extend(["--languages", self.args.languages])
|
||||||
if getattr(self.args, 'file_patterns', None):
|
if getattr(self.args, "file_patterns", None):
|
||||||
argv.extend(['--file-patterns', self.args.file_patterns])
|
argv.extend(["--file-patterns", self.args.file_patterns])
|
||||||
if getattr(self.args, 'skip_patterns', False):
|
if getattr(self.args, "skip_patterns", False):
|
||||||
argv.append('--skip-patterns')
|
argv.append("--skip-patterns")
|
||||||
if getattr(self.args, 'skip_test_examples', False):
|
if getattr(self.args, "skip_test_examples", False):
|
||||||
argv.append('--skip-test-examples')
|
argv.append("--skip-test-examples")
|
||||||
if getattr(self.args, 'skip_how_to_guides', False):
|
if getattr(self.args, "skip_how_to_guides", False):
|
||||||
argv.append('--skip-how-to-guides')
|
argv.append("--skip-how-to-guides")
|
||||||
if getattr(self.args, 'skip_config', False):
|
if getattr(self.args, "skip_config", False):
|
||||||
argv.append('--skip-config')
|
argv.append("--skip-config")
|
||||||
if getattr(self.args, 'skip_docs', False):
|
if getattr(self.args, "skip_docs", False):
|
||||||
argv.append('--skip-docs')
|
argv.append("--skip-docs")
|
||||||
|
|
||||||
# Call codebase_scraper with modified argv
|
# Call codebase_scraper with modified argv
|
||||||
logger.debug(f"Calling codebase_scraper with argv: {argv}")
|
logger.debug(f"Calling codebase_scraper with argv: {argv}")
|
||||||
@@ -262,20 +263,20 @@ class CreateCommand:
|
|||||||
from skill_seekers.cli import pdf_scraper
|
from skill_seekers.cli import pdf_scraper
|
||||||
|
|
||||||
# Reconstruct argv for pdf_scraper
|
# Reconstruct argv for pdf_scraper
|
||||||
argv = ['pdf_scraper']
|
argv = ["pdf_scraper"]
|
||||||
|
|
||||||
# Add PDF file
|
# Add PDF file
|
||||||
file_path = self.source_info.parsed['file_path']
|
file_path = self.source_info.parsed["file_path"]
|
||||||
argv.extend(['--pdf', file_path])
|
argv.extend(["--pdf", file_path])
|
||||||
|
|
||||||
# Add universal arguments
|
# Add universal arguments
|
||||||
self._add_common_args(argv)
|
self._add_common_args(argv)
|
||||||
|
|
||||||
# Add PDF-specific arguments
|
# Add PDF-specific arguments
|
||||||
if getattr(self.args, 'ocr', False):
|
if getattr(self.args, "ocr", False):
|
||||||
argv.append('--ocr')
|
argv.append("--ocr")
|
||||||
if getattr(self.args, 'pages', None):
|
if getattr(self.args, "pages", None):
|
||||||
argv.extend(['--pages', self.args.pages])
|
argv.extend(["--pages", self.args.pages])
|
||||||
|
|
||||||
# Call pdf_scraper with modified argv
|
# Call pdf_scraper with modified argv
|
||||||
logger.debug(f"Calling pdf_scraper with argv: {argv}")
|
logger.debug(f"Calling pdf_scraper with argv: {argv}")
|
||||||
@@ -291,11 +292,11 @@ class CreateCommand:
|
|||||||
from skill_seekers.cli import unified_scraper
|
from skill_seekers.cli import unified_scraper
|
||||||
|
|
||||||
# Reconstruct argv for unified_scraper
|
# Reconstruct argv for unified_scraper
|
||||||
argv = ['unified_scraper']
|
argv = ["unified_scraper"]
|
||||||
|
|
||||||
# Add config file
|
# Add config file
|
||||||
config_path = self.source_info.parsed['config_path']
|
config_path = self.source_info.parsed["config_path"]
|
||||||
argv.extend(['--config', config_path])
|
argv.extend(["--config", config_path])
|
||||||
|
|
||||||
# Add universal arguments (unified scraper supports most)
|
# Add universal arguments (unified scraper supports most)
|
||||||
self._add_common_args(argv)
|
self._add_common_args(argv)
|
||||||
@@ -317,53 +318,54 @@ class CreateCommand:
|
|||||||
"""
|
"""
|
||||||
# Identity arguments
|
# Identity arguments
|
||||||
if self.args.name:
|
if self.args.name:
|
||||||
argv.extend(['--name', self.args.name])
|
argv.extend(["--name", self.args.name])
|
||||||
elif hasattr(self, 'source_info') and self.source_info:
|
elif hasattr(self, "source_info") and self.source_info:
|
||||||
# Use suggested name from source detection
|
# Use suggested name from source detection
|
||||||
argv.extend(['--name', self.source_info.suggested_name])
|
argv.extend(["--name", self.source_info.suggested_name])
|
||||||
|
|
||||||
if self.args.description:
|
if self.args.description:
|
||||||
argv.extend(['--description', self.args.description])
|
argv.extend(["--description", self.args.description])
|
||||||
if self.args.output:
|
if self.args.output:
|
||||||
argv.extend(['--output', self.args.output])
|
argv.extend(["--output", self.args.output])
|
||||||
|
|
||||||
# Enhancement arguments (consolidated to --enhance-level only)
|
# Enhancement arguments (consolidated to --enhance-level only)
|
||||||
if self.args.enhance_level > 0:
|
if self.args.enhance_level > 0:
|
||||||
argv.extend(['--enhance-level', str(self.args.enhance_level)])
|
argv.extend(["--enhance-level", str(self.args.enhance_level)])
|
||||||
if self.args.api_key:
|
if self.args.api_key:
|
||||||
argv.extend(['--api-key', self.args.api_key])
|
argv.extend(["--api-key", self.args.api_key])
|
||||||
|
|
||||||
# Behavior arguments
|
# Behavior arguments
|
||||||
if self.args.dry_run:
|
if self.args.dry_run:
|
||||||
argv.append('--dry-run')
|
argv.append("--dry-run")
|
||||||
if self.args.verbose:
|
if self.args.verbose:
|
||||||
argv.append('--verbose')
|
argv.append("--verbose")
|
||||||
if self.args.quiet:
|
if self.args.quiet:
|
||||||
argv.append('--quiet')
|
argv.append("--quiet")
|
||||||
|
|
||||||
# RAG arguments (NEW - universal!)
|
# RAG arguments (NEW - universal!)
|
||||||
if getattr(self.args, 'chunk_for_rag', False):
|
if getattr(self.args, "chunk_for_rag", False):
|
||||||
argv.append('--chunk-for-rag')
|
argv.append("--chunk-for-rag")
|
||||||
if getattr(self.args, 'chunk_size', None) and self.args.chunk_size != 512:
|
if getattr(self.args, "chunk_size", None) and self.args.chunk_size != 512:
|
||||||
argv.extend(['--chunk-size', str(self.args.chunk_size)])
|
argv.extend(["--chunk-size", str(self.args.chunk_size)])
|
||||||
if getattr(self.args, 'chunk_overlap', None) and self.args.chunk_overlap != 50:
|
if getattr(self.args, "chunk_overlap", None) and self.args.chunk_overlap != 50:
|
||||||
argv.extend(['--chunk-overlap', str(self.args.chunk_overlap)])
|
argv.extend(["--chunk-overlap", str(self.args.chunk_overlap)])
|
||||||
|
|
||||||
# Preset argument
|
# Preset argument
|
||||||
if getattr(self.args, 'preset', None):
|
if getattr(self.args, "preset", None):
|
||||||
argv.extend(['--preset', self.args.preset])
|
argv.extend(["--preset", self.args.preset])
|
||||||
|
|
||||||
# Config file
|
# Config file
|
||||||
if self.args.config:
|
if self.args.config:
|
||||||
argv.extend(['--config', self.args.config])
|
argv.extend(["--config", self.args.config])
|
||||||
|
|
||||||
# Advanced arguments
|
# Advanced arguments
|
||||||
if getattr(self.args, 'no_preserve_code_blocks', False):
|
if getattr(self.args, "no_preserve_code_blocks", False):
|
||||||
argv.append('--no-preserve-code-blocks')
|
argv.append("--no-preserve-code-blocks")
|
||||||
if getattr(self.args, 'no_preserve_paragraphs', False):
|
if getattr(self.args, "no_preserve_paragraphs", False):
|
||||||
argv.append('--no-preserve-paragraphs')
|
argv.append("--no-preserve-paragraphs")
|
||||||
if getattr(self.args, 'interactive_enhancement', False):
|
if getattr(self.args, "interactive_enhancement", False):
|
||||||
argv.append('--interactive-enhancement')
|
argv.append("--interactive-enhancement")
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
"""Entry point for create command.
|
"""Entry point for create command.
|
||||||
@@ -381,8 +383,8 @@ def main() -> int:
|
|||||||
return text.splitlines()
|
return text.splitlines()
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog='skill-seekers create',
|
prog="skill-seekers create",
|
||||||
description='Create skill from any source (auto-detects type)',
|
description="Create skill from any source (auto-detects type)",
|
||||||
formatter_class=NoWrapFormatter,
|
formatter_class=NoWrapFormatter,
|
||||||
epilog=textwrap.dedent("""\
|
epilog=textwrap.dedent("""\
|
||||||
Examples:
|
Examples:
|
||||||
@@ -416,19 +418,25 @@ Common Workflows:
|
|||||||
skill-seekers create <source> -p quick
|
skill-seekers create <source> -p quick
|
||||||
skill-seekers create <source> -p standard --enhance-level 2
|
skill-seekers create <source> -p standard --enhance-level 2
|
||||||
skill-seekers create <source> --chunk-for-rag
|
skill-seekers create <source> --chunk-for-rag
|
||||||
""")
|
"""),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add arguments in default mode (universal only)
|
# Add arguments in default mode (universal only)
|
||||||
add_create_arguments(parser, mode='default')
|
add_create_arguments(parser, mode="default")
|
||||||
|
|
||||||
# Add hidden help mode flags (use underscore prefix to match CreateParser)
|
# Add hidden help mode flags (use underscore prefix to match CreateParser)
|
||||||
parser.add_argument('--help-web', action='store_true', help=argparse.SUPPRESS, dest='_help_web')
|
parser.add_argument("--help-web", action="store_true", help=argparse.SUPPRESS, dest="_help_web")
|
||||||
parser.add_argument('--help-github', action='store_true', help=argparse.SUPPRESS, dest='_help_github')
|
parser.add_argument(
|
||||||
parser.add_argument('--help-local', action='store_true', help=argparse.SUPPRESS, dest='_help_local')
|
"--help-github", action="store_true", help=argparse.SUPPRESS, dest="_help_github"
|
||||||
parser.add_argument('--help-pdf', action='store_true', help=argparse.SUPPRESS, dest='_help_pdf')
|
)
|
||||||
parser.add_argument('--help-advanced', action='store_true', help=argparse.SUPPRESS, dest='_help_advanced')
|
parser.add_argument(
|
||||||
parser.add_argument('--help-all', action='store_true', help=argparse.SUPPRESS, dest='_help_all')
|
"--help-local", action="store_true", help=argparse.SUPPRESS, dest="_help_local"
|
||||||
|
)
|
||||||
|
parser.add_argument("--help-pdf", action="store_true", help=argparse.SUPPRESS, dest="_help_pdf")
|
||||||
|
parser.add_argument(
|
||||||
|
"--help-advanced", action="store_true", help=argparse.SUPPRESS, dest="_help_advanced"
|
||||||
|
)
|
||||||
|
parser.add_argument("--help-all", action="store_true", help=argparse.SUPPRESS, dest="_help_all")
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -437,67 +445,62 @@ Common Workflows:
|
|||||||
if args._help_web:
|
if args._help_web:
|
||||||
# Recreate parser with web-specific arguments
|
# Recreate parser with web-specific arguments
|
||||||
parser_web = argparse.ArgumentParser(
|
parser_web = argparse.ArgumentParser(
|
||||||
prog='skill-seekers create',
|
prog="skill-seekers create",
|
||||||
description='Create skill from web documentation',
|
description="Create skill from web documentation",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
add_create_arguments(parser_web, mode='web')
|
add_create_arguments(parser_web, mode="web")
|
||||||
parser_web.print_help()
|
parser_web.print_help()
|
||||||
return 0
|
return 0
|
||||||
elif args._help_github:
|
elif args._help_github:
|
||||||
parser_github = argparse.ArgumentParser(
|
parser_github = argparse.ArgumentParser(
|
||||||
prog='skill-seekers create',
|
prog="skill-seekers create",
|
||||||
description='Create skill from GitHub repository',
|
description="Create skill from GitHub repository",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
add_create_arguments(parser_github, mode='github')
|
add_create_arguments(parser_github, mode="github")
|
||||||
parser_github.print_help()
|
parser_github.print_help()
|
||||||
return 0
|
return 0
|
||||||
elif args._help_local:
|
elif args._help_local:
|
||||||
parser_local = argparse.ArgumentParser(
|
parser_local = argparse.ArgumentParser(
|
||||||
prog='skill-seekers create',
|
prog="skill-seekers create",
|
||||||
description='Create skill from local codebase',
|
description="Create skill from local codebase",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
add_create_arguments(parser_local, mode='local')
|
add_create_arguments(parser_local, mode="local")
|
||||||
parser_local.print_help()
|
parser_local.print_help()
|
||||||
return 0
|
return 0
|
||||||
elif args._help_pdf:
|
elif args._help_pdf:
|
||||||
parser_pdf = argparse.ArgumentParser(
|
parser_pdf = argparse.ArgumentParser(
|
||||||
prog='skill-seekers create',
|
prog="skill-seekers create",
|
||||||
description='Create skill from PDF file',
|
description="Create skill from PDF file",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
add_create_arguments(parser_pdf, mode='pdf')
|
add_create_arguments(parser_pdf, mode="pdf")
|
||||||
parser_pdf.print_help()
|
parser_pdf.print_help()
|
||||||
return 0
|
return 0
|
||||||
elif args._help_advanced:
|
elif args._help_advanced:
|
||||||
parser_advanced = argparse.ArgumentParser(
|
parser_advanced = argparse.ArgumentParser(
|
||||||
prog='skill-seekers create',
|
prog="skill-seekers create",
|
||||||
description='Create skill - advanced options',
|
description="Create skill - advanced options",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
add_create_arguments(parser_advanced, mode='advanced')
|
add_create_arguments(parser_advanced, mode="advanced")
|
||||||
parser_advanced.print_help()
|
parser_advanced.print_help()
|
||||||
return 0
|
return 0
|
||||||
elif args._help_all:
|
elif args._help_all:
|
||||||
parser_all = argparse.ArgumentParser(
|
parser_all = argparse.ArgumentParser(
|
||||||
prog='skill-seekers create',
|
prog="skill-seekers create",
|
||||||
description='Create skill - all options',
|
description="Create skill - all options",
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
add_create_arguments(parser_all, mode='all')
|
add_create_arguments(parser_all, mode="all")
|
||||||
parser_all.print_help()
|
parser_all.print_help()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Setup logging
|
# Setup logging
|
||||||
log_level = logging.DEBUG if args.verbose else (
|
log_level = logging.DEBUG if args.verbose else (logging.WARNING if args.quiet else logging.INFO)
|
||||||
logging.WARNING if args.quiet else logging.INFO
|
logging.basicConfig(level=log_level, format="%(levelname)s: %(message)s")
|
||||||
)
|
|
||||||
logging.basicConfig(
|
|
||||||
level=log_level,
|
|
||||||
format='%(levelname)s: %(message)s'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate source provided
|
# Validate source provided
|
||||||
if not args.source:
|
if not args.source:
|
||||||
@@ -507,5 +510,6 @@ Common Workflows:
|
|||||||
command = CreateCommand(args)
|
command = CreateCommand(args)
|
||||||
return command.execute()
|
return command.execute()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
@@ -2231,8 +2231,9 @@ def execute_enhancement(config: dict[str, Any], args: argparse.Namespace) -> Non
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
# Optional enhancement with auto-detected mode (API or LOCAL)
|
# Optional enhancement with auto-detected mode (API or LOCAL)
|
||||||
if getattr(args, 'enhance_level', 0) > 0:
|
if getattr(args, "enhance_level", 0) > 0:
|
||||||
import os
|
import os
|
||||||
|
|
||||||
has_api_key = bool(os.environ.get("ANTHROPIC_API_KEY") or args.api_key)
|
has_api_key = bool(os.environ.get("ANTHROPIC_API_KEY") or args.api_key)
|
||||||
mode = "API" if has_api_key else "LOCAL"
|
mode = "API" if has_api_key else "LOCAL"
|
||||||
|
|
||||||
@@ -2246,7 +2247,7 @@ def execute_enhancement(config: dict[str, Any], args: argparse.Namespace) -> Non
|
|||||||
|
|
||||||
if args.api_key:
|
if args.api_key:
|
||||||
enhance_cmd.extend(["--api-key", args.api_key])
|
enhance_cmd.extend(["--api-key", args.api_key])
|
||||||
if getattr(args, 'interactive_enhancement', False):
|
if getattr(args, "interactive_enhancement", False):
|
||||||
enhance_cmd.append("--interactive-enhancement")
|
enhance_cmd.append("--interactive-enhancement")
|
||||||
|
|
||||||
result = subprocess.run(enhance_cmd, check=True)
|
result = subprocess.run(enhance_cmd, check=True)
|
||||||
@@ -2256,14 +2257,18 @@ def execute_enhancement(config: dict[str, Any], args: argparse.Namespace) -> Non
|
|||||||
logger.warning("\n⚠ Enhancement failed, but skill was still built")
|
logger.warning("\n⚠ Enhancement failed, but skill was still built")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.warning("\n⚠ skill-seekers-enhance command not found. Run manually:")
|
logger.warning("\n⚠ skill-seekers-enhance command not found. Run manually:")
|
||||||
logger.info(" skill-seekers-enhance output/%s/ --enhance-level %d", config["name"], args.enhance_level)
|
logger.info(
|
||||||
|
" skill-seekers-enhance output/%s/ --enhance-level %d",
|
||||||
|
config["name"],
|
||||||
|
args.enhance_level,
|
||||||
|
)
|
||||||
|
|
||||||
# Print packaging instructions
|
# Print packaging instructions
|
||||||
logger.info("\n📦 Package your skill:")
|
logger.info("\n📦 Package your skill:")
|
||||||
logger.info(" skill-seekers-package output/%s/", config["name"])
|
logger.info(" skill-seekers-package output/%s/", config["name"])
|
||||||
|
|
||||||
# Suggest enhancement if not done
|
# Suggest enhancement if not done
|
||||||
if getattr(args, 'enhance_level', 0) == 0:
|
if getattr(args, "enhance_level", 0) == 0:
|
||||||
logger.info("\n💡 Optional: Enhance SKILL.md with Claude:")
|
logger.info("\n💡 Optional: Enhance SKILL.md with Claude:")
|
||||||
logger.info(" skill-seekers-enhance output/%s/ --enhance-level 2", config["name"])
|
logger.info(" skill-seekers-enhance output/%s/ --enhance-level 2", config["name"])
|
||||||
logger.info(" or re-run with: --enhance-level 2 (auto-detects API vs LOCAL mode)")
|
logger.info(" or re-run with: --enhance-level 2 (auto-detects API vs LOCAL mode)")
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ EXCLUDED_DIRS = {
|
|||||||
".tmp",
|
".tmp",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def extract_description_from_readme(readme_content: str, repo_name: str) -> str:
|
def extract_description_from_readme(readme_content: str, repo_name: str) -> str:
|
||||||
"""
|
"""
|
||||||
Extract a meaningful description from README content for skill description.
|
Extract a meaningful description from README content for skill description.
|
||||||
@@ -180,6 +181,7 @@ def extract_description_from_readme(readme_content: str, repo_name: str) -> str:
|
|||||||
project_name = repo_name.split("/")[-1]
|
project_name = repo_name.split("/")[-1]
|
||||||
return f"Use when working with {project_name}"
|
return f"Use when working with {project_name}"
|
||||||
|
|
||||||
|
|
||||||
class GitHubScraper:
|
class GitHubScraper:
|
||||||
"""
|
"""
|
||||||
GitHub Repository Scraper (C1.1-C1.9)
|
GitHub Repository Scraper (C1.1-C1.9)
|
||||||
@@ -892,6 +894,7 @@ class GitHubScraper:
|
|||||||
|
|
||||||
logger.info(f"Data saved to: {self.data_file}")
|
logger.info(f"Data saved to: {self.data_file}")
|
||||||
|
|
||||||
|
|
||||||
class GitHubToSkillConverter:
|
class GitHubToSkillConverter:
|
||||||
"""
|
"""
|
||||||
Convert extracted GitHub data to Claude skill format (C1.10).
|
Convert extracted GitHub data to Claude skill format (C1.10).
|
||||||
@@ -1347,6 +1350,7 @@ Use this skill when you need to:
|
|||||||
f.write(content)
|
f.write(content)
|
||||||
logger.info(f"Generated: {structure_path}")
|
logger.info(f"Generated: {structure_path}")
|
||||||
|
|
||||||
|
|
||||||
def setup_argument_parser() -> argparse.ArgumentParser:
|
def setup_argument_parser() -> argparse.ArgumentParser:
|
||||||
"""Setup and configure command-line argument parser.
|
"""Setup and configure command-line argument parser.
|
||||||
|
|
||||||
@@ -1374,6 +1378,7 @@ Examples:
|
|||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""C1.10: CLI tool entry point."""
|
"""C1.10: CLI tool entry point."""
|
||||||
parser = setup_argument_parser()
|
parser = setup_argument_parser()
|
||||||
@@ -1421,14 +1426,16 @@ def main():
|
|||||||
skill_dir = f"output/{skill_name}"
|
skill_dir = f"output/{skill_name}"
|
||||||
|
|
||||||
# Phase 3: Optional enhancement with auto-detected mode
|
# Phase 3: Optional enhancement with auto-detected mode
|
||||||
if getattr(args, 'enhance_level', 0) > 0:
|
if getattr(args, "enhance_level", 0) > 0:
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Auto-detect mode based on API key availability
|
# Auto-detect mode based on API key availability
|
||||||
api_key = args.api_key or os.environ.get("ANTHROPIC_API_KEY")
|
api_key = args.api_key or os.environ.get("ANTHROPIC_API_KEY")
|
||||||
mode = "API" if api_key else "LOCAL"
|
mode = "API" if api_key else "LOCAL"
|
||||||
|
|
||||||
logger.info(f"\n📝 Enhancing SKILL.md with Claude ({mode} mode, level {args.enhance_level})...")
|
logger.info(
|
||||||
|
f"\n📝 Enhancing SKILL.md with Claude ({mode} mode, level {args.enhance_level})..."
|
||||||
|
)
|
||||||
|
|
||||||
if api_key:
|
if api_key:
|
||||||
# API-based enhancement
|
# API-based enhancement
|
||||||
@@ -1438,9 +1445,7 @@ def main():
|
|||||||
enhance_skill_md(skill_dir, api_key)
|
enhance_skill_md(skill_dir, api_key)
|
||||||
logger.info("✅ API enhancement complete!")
|
logger.info("✅ API enhancement complete!")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.error(
|
logger.error("❌ API enhancement not available. Install: pip install anthropic")
|
||||||
"❌ API enhancement not available. Install: pip install anthropic"
|
|
||||||
)
|
|
||||||
logger.info("💡 Falling back to LOCAL mode...")
|
logger.info("💡 Falling back to LOCAL mode...")
|
||||||
# Fall back to LOCAL mode
|
# Fall back to LOCAL mode
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -1460,7 +1465,7 @@ def main():
|
|||||||
|
|
||||||
logger.info(f"\n✅ Success! Skill created at: {skill_dir}/")
|
logger.info(f"\n✅ Success! Skill created at: {skill_dir}/")
|
||||||
|
|
||||||
if getattr(args, 'enhance_level', 0) == 0:
|
if getattr(args, "enhance_level", 0) == 0:
|
||||||
logger.info("\n💡 Optional: Enhance SKILL.md with Claude:")
|
logger.info("\n💡 Optional: Enhance SKILL.md with Claude:")
|
||||||
logger.info(f" skill-seekers enhance {skill_dir}/ --enhance-level 2")
|
logger.info(f" skill-seekers enhance {skill_dir}/ --enhance-level 2")
|
||||||
logger.info(" (auto-detects API vs LOCAL mode based on ANTHROPIC_API_KEY)")
|
logger.info(" (auto-detects API vs LOCAL mode based on ANTHROPIC_API_KEY)")
|
||||||
@@ -1471,5 +1476,6 @@ def main():
|
|||||||
logger.error(f"Error: {e}")
|
logger.error(f"Error: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ def _reconstruct_argv(command: str, args: argparse.Namespace) -> list[str]:
|
|||||||
if key.startswith("_help_"):
|
if key.startswith("_help_"):
|
||||||
if value:
|
if value:
|
||||||
# Convert _help_web -> --help-web
|
# Convert _help_web -> --help-web
|
||||||
help_flag = key.replace('_help_', 'help-')
|
help_flag = key.replace("_help_", "help-")
|
||||||
argv.append(f"--{help_flag}")
|
argv.append(f"--{help_flag}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -181,6 +181,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
if len(argv) >= 2 and argv[0] == "analyze" and "--preset-list" in argv:
|
if len(argv) >= 2 and argv[0] == "analyze" and "--preset-list" in argv:
|
||||||
from skill_seekers.cli.codebase_scraper import main as analyze_main
|
from skill_seekers.cli.codebase_scraper import main as analyze_main
|
||||||
|
|
||||||
original_argv = sys.argv.copy()
|
original_argv = sys.argv.copy()
|
||||||
sys.argv = ["codebase_scraper.py", "--preset-list"]
|
sys.argv = ["codebase_scraper.py", "--preset-list"]
|
||||||
try:
|
try:
|
||||||
@@ -274,8 +275,8 @@ def _handle_analyze_command(args: argparse.Namespace) -> int:
|
|||||||
sys.argv.extend(["--depth", args.depth])
|
sys.argv.extend(["--depth", args.depth])
|
||||||
|
|
||||||
# Determine enhance_level (simplified - use default or override)
|
# Determine enhance_level (simplified - use default or override)
|
||||||
enhance_level = getattr(args, 'enhance_level', 2) # Default is 2
|
enhance_level = getattr(args, "enhance_level", 2) # Default is 2
|
||||||
if getattr(args, 'quick', False):
|
if getattr(args, "quick", False):
|
||||||
enhance_level = 0 # Quick mode disables enhancement
|
enhance_level = 0 # Quick mode disables enhancement
|
||||||
|
|
||||||
sys.argv.extend(["--enhance-level", str(enhance_level)])
|
sys.argv.extend(["--enhance-level", str(enhance_level)])
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ PARSERS = [
|
|||||||
QualityParser(),
|
QualityParser(),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def register_parsers(subparsers):
|
def register_parsers(subparsers):
|
||||||
"""Register all subcommand parsers.
|
"""Register all subcommand parsers.
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ def register_parsers(subparsers):
|
|||||||
for parser_instance in PARSERS:
|
for parser_instance in PARSERS:
|
||||||
parser_instance.create_parser(subparsers)
|
parser_instance.create_parser(subparsers)
|
||||||
|
|
||||||
|
|
||||||
def get_parser_names():
|
def get_parser_names():
|
||||||
"""Get list of all subcommand names.
|
"""Get list of all subcommand names.
|
||||||
|
|
||||||
@@ -72,6 +74,7 @@ def get_parser_names():
|
|||||||
"""
|
"""
|
||||||
return [p.name for p in PARSERS]
|
return [p.name for p in PARSERS]
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"SubcommandParser",
|
"SubcommandParser",
|
||||||
"PARSERS",
|
"PARSERS",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Includes preset system support (Issue #268).
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.analyze import add_analyze_arguments
|
from skill_seekers.cli.arguments.analyze import add_analyze_arguments
|
||||||
|
|
||||||
|
|
||||||
class AnalyzeParser(SubcommandParser):
|
class AnalyzeParser(SubcommandParser):
|
||||||
"""Parser for analyze subcommand."""
|
"""Parser for analyze subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
class SubcommandParser(ABC):
|
class SubcommandParser(ABC):
|
||||||
"""Base class for subcommand parsers.
|
"""Base class for subcommand parsers.
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class ConfigParser(SubcommandParser):
|
class ConfigParser(SubcommandParser):
|
||||||
"""Parser for config subcommand."""
|
"""Parser for config subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import argparse
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.create import add_create_arguments
|
from skill_seekers.cli.arguments.create import add_create_arguments
|
||||||
|
|
||||||
|
|
||||||
class CreateParser(SubcommandParser):
|
class CreateParser(SubcommandParser):
|
||||||
"""Parser for create subcommand with multi-mode help."""
|
"""Parser for create subcommand with multi-mode help."""
|
||||||
|
|
||||||
@@ -54,45 +55,45 @@ Presets: -p quick (1-2min) | -p standard (5-10min) | -p comprehensive (20-60min)
|
|||||||
"""
|
"""
|
||||||
# Add all arguments in 'default' mode (universal only)
|
# Add all arguments in 'default' mode (universal only)
|
||||||
# This keeps help text clean and focused
|
# This keeps help text clean and focused
|
||||||
add_create_arguments(parser, mode='default')
|
add_create_arguments(parser, mode="default")
|
||||||
|
|
||||||
# Add hidden help mode flags
|
# Add hidden help mode flags
|
||||||
# These won't show in default help but can be used to get source-specific help
|
# These won't show in default help but can be used to get source-specific help
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--help-web',
|
"--help-web",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Show web scraping specific options',
|
help="Show web scraping specific options",
|
||||||
dest='_help_web'
|
dest="_help_web",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--help-github',
|
"--help-github",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Show GitHub repository specific options',
|
help="Show GitHub repository specific options",
|
||||||
dest='_help_github'
|
dest="_help_github",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--help-local',
|
"--help-local",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Show local codebase specific options',
|
help="Show local codebase specific options",
|
||||||
dest='_help_local'
|
dest="_help_local",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--help-pdf',
|
"--help-pdf",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Show PDF extraction specific options',
|
help="Show PDF extraction specific options",
|
||||||
dest='_help_pdf'
|
dest="_help_pdf",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--help-advanced',
|
"--help-advanced",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Show advanced/rare options',
|
help="Show advanced/rare options",
|
||||||
dest='_help_advanced'
|
dest="_help_advanced",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--help-all',
|
"--help-all",
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Show all available options (120+ flags)',
|
help="Show all available options (120+ flags)",
|
||||||
dest='_help_all'
|
dest="_help_all",
|
||||||
)
|
)
|
||||||
|
|
||||||
def register(self, subparsers):
|
def register(self, subparsers):
|
||||||
@@ -104,16 +105,14 @@ Presets: -p quick (1-2min) | -p standard (5-10min) | -p comprehensive (20-60min)
|
|||||||
Returns:
|
Returns:
|
||||||
Configured ArgumentParser for this subcommand
|
Configured ArgumentParser for this subcommand
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Custom formatter that preserves line breaks
|
# Custom formatter that preserves line breaks
|
||||||
class NoWrapFormatter(argparse.RawDescriptionHelpFormatter):
|
class NoWrapFormatter(argparse.RawDescriptionHelpFormatter):
|
||||||
def _split_lines(self, text, width):
|
def _split_lines(self, text, width):
|
||||||
return text.splitlines()
|
return text.splitlines()
|
||||||
|
|
||||||
parser = subparsers.add_parser(
|
parser = subparsers.add_parser(
|
||||||
self.name,
|
self.name, help=self.help, description=self.description, formatter_class=NoWrapFormatter
|
||||||
help=self.help,
|
|
||||||
description=self.description,
|
|
||||||
formatter_class=NoWrapFormatter
|
|
||||||
)
|
)
|
||||||
self.add_arguments(parser)
|
self.add_arguments(parser)
|
||||||
return parser
|
return parser
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ consistency with the standalone enhance_skill_local module.
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.enhance import add_enhance_arguments
|
from skill_seekers.cli.arguments.enhance import add_enhance_arguments
|
||||||
|
|
||||||
|
|
||||||
class EnhanceParser(SubcommandParser):
|
class EnhanceParser(SubcommandParser):
|
||||||
"""Parser for enhance subcommand."""
|
"""Parser for enhance subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class EnhanceStatusParser(SubcommandParser):
|
class EnhanceStatusParser(SubcommandParser):
|
||||||
"""Parser for enhance-status subcommand."""
|
"""Parser for enhance-status subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class EstimateParser(SubcommandParser):
|
class EstimateParser(SubcommandParser):
|
||||||
"""Parser for estimate subcommand."""
|
"""Parser for estimate subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ consistency with the standalone github_scraper module.
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.github import add_github_arguments
|
from skill_seekers.cli.arguments.github import add_github_arguments
|
||||||
|
|
||||||
|
|
||||||
class GitHubParser(SubcommandParser):
|
class GitHubParser(SubcommandParser):
|
||||||
"""Parser for github subcommand."""
|
"""Parser for github subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class InstallAgentParser(SubcommandParser):
|
class InstallAgentParser(SubcommandParser):
|
||||||
"""Parser for install-agent subcommand."""
|
"""Parser for install-agent subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class InstallParser(SubcommandParser):
|
class InstallParser(SubcommandParser):
|
||||||
"""Parser for install subcommand."""
|
"""Parser for install subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class MultilangParser(SubcommandParser):
|
class MultilangParser(SubcommandParser):
|
||||||
"""Parser for multilang subcommand."""
|
"""Parser for multilang subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ consistency with the standalone package_skill module.
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.package import add_package_arguments
|
from skill_seekers.cli.arguments.package import add_package_arguments
|
||||||
|
|
||||||
|
|
||||||
class PackageParser(SubcommandParser):
|
class PackageParser(SubcommandParser):
|
||||||
"""Parser for package subcommand."""
|
"""Parser for package subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ consistency with the standalone pdf_scraper module.
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.pdf import add_pdf_arguments
|
from skill_seekers.cli.arguments.pdf import add_pdf_arguments
|
||||||
|
|
||||||
|
|
||||||
class PDFParser(SubcommandParser):
|
class PDFParser(SubcommandParser):
|
||||||
"""Parser for pdf subcommand."""
|
"""Parser for pdf subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class QualityParser(SubcommandParser):
|
class QualityParser(SubcommandParser):
|
||||||
"""Parser for quality subcommand."""
|
"""Parser for quality subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class ResumeParser(SubcommandParser):
|
class ResumeParser(SubcommandParser):
|
||||||
"""Parser for resume subcommand."""
|
"""Parser for resume subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ consistency with the standalone doc_scraper module.
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.scrape import add_scrape_arguments
|
from skill_seekers.cli.arguments.scrape import add_scrape_arguments
|
||||||
|
|
||||||
|
|
||||||
class ScrapeParser(SubcommandParser):
|
class ScrapeParser(SubcommandParser):
|
||||||
"""Parser for scrape subcommand."""
|
"""Parser for scrape subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class StreamParser(SubcommandParser):
|
class StreamParser(SubcommandParser):
|
||||||
"""Parser for stream subcommand."""
|
"""Parser for stream subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class TestExamplesParser(SubcommandParser):
|
class TestExamplesParser(SubcommandParser):
|
||||||
"""Parser for extract-test-examples subcommand."""
|
"""Parser for extract-test-examples subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ consistency with the standalone unified_scraper module.
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.unified import add_unified_arguments
|
from skill_seekers.cli.arguments.unified import add_unified_arguments
|
||||||
|
|
||||||
|
|
||||||
class UnifiedParser(SubcommandParser):
|
class UnifiedParser(SubcommandParser):
|
||||||
"""Parser for unified subcommand."""
|
"""Parser for unified subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
|
|
||||||
|
|
||||||
class UpdateParser(SubcommandParser):
|
class UpdateParser(SubcommandParser):
|
||||||
"""Parser for update subcommand."""
|
"""Parser for update subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ consistency with the standalone upload_skill module.
|
|||||||
from .base import SubcommandParser
|
from .base import SubcommandParser
|
||||||
from skill_seekers.cli.arguments.upload import add_upload_arguments
|
from skill_seekers.cli.arguments.upload import add_upload_arguments
|
||||||
|
|
||||||
|
|
||||||
class UploadParser(SubcommandParser):
|
class UploadParser(SubcommandParser):
|
||||||
"""Parser for upload subcommand."""
|
"""Parser for upload subcommand."""
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from dataclasses import dataclass, field
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class AnalysisPreset:
|
class AnalysisPreset:
|
||||||
"""Definition of an analysis preset.
|
"""Definition of an analysis preset.
|
||||||
@@ -29,12 +30,14 @@ class AnalysisPreset:
|
|||||||
features: Dict of feature flags (feature_name -> enabled)
|
features: Dict of feature flags (feature_name -> enabled)
|
||||||
estimated_time: Human-readable time estimate
|
estimated_time: Human-readable time estimate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
depth: str
|
depth: str
|
||||||
features: dict[str, bool] = field(default_factory=dict)
|
features: dict[str, bool] = field(default_factory=dict)
|
||||||
estimated_time: str = ""
|
estimated_time: str = ""
|
||||||
|
|
||||||
|
|
||||||
# Preset definitions
|
# Preset definitions
|
||||||
ANALYZE_PRESETS = {
|
ANALYZE_PRESETS = {
|
||||||
"quick": AnalysisPreset(
|
"quick": AnalysisPreset(
|
||||||
@@ -49,9 +52,8 @@ ANALYZE_PRESETS = {
|
|||||||
"how_to_guides": False,
|
"how_to_guides": False,
|
||||||
"config_patterns": False,
|
"config_patterns": False,
|
||||||
},
|
},
|
||||||
estimated_time="1-2 minutes"
|
estimated_time="1-2 minutes",
|
||||||
),
|
),
|
||||||
|
|
||||||
"standard": AnalysisPreset(
|
"standard": AnalysisPreset(
|
||||||
name="Standard",
|
name="Standard",
|
||||||
description="Balanced analysis with core features (recommended)",
|
description="Balanced analysis with core features (recommended)",
|
||||||
@@ -64,9 +66,8 @@ ANALYZE_PRESETS = {
|
|||||||
"how_to_guides": False,
|
"how_to_guides": False,
|
||||||
"config_patterns": True,
|
"config_patterns": True,
|
||||||
},
|
},
|
||||||
estimated_time="5-10 minutes"
|
estimated_time="5-10 minutes",
|
||||||
),
|
),
|
||||||
|
|
||||||
"comprehensive": AnalysisPreset(
|
"comprehensive": AnalysisPreset(
|
||||||
name="Comprehensive",
|
name="Comprehensive",
|
||||||
description="Full analysis with all features",
|
description="Full analysis with all features",
|
||||||
@@ -79,10 +80,11 @@ ANALYZE_PRESETS = {
|
|||||||
"how_to_guides": True,
|
"how_to_guides": True,
|
||||||
"config_patterns": True,
|
"config_patterns": True,
|
||||||
},
|
},
|
||||||
estimated_time="20-60 minutes"
|
estimated_time="20-60 minutes",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def apply_analyze_preset(args: argparse.Namespace, preset_name: str) -> None:
|
def apply_analyze_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||||
"""Apply an analysis preset to the args namespace.
|
"""Apply an analysis preset to the args namespace.
|
||||||
|
|
||||||
@@ -113,6 +115,7 @@ def apply_analyze_preset(args: argparse.Namespace, preset_name: str) -> None:
|
|||||||
skip_attr = f"skip_{feature}"
|
skip_attr = f"skip_{feature}"
|
||||||
setattr(args, skip_attr, not enabled)
|
setattr(args, skip_attr, not enabled)
|
||||||
|
|
||||||
|
|
||||||
def get_preset_help_text(preset_name: str) -> str:
|
def get_preset_help_text(preset_name: str) -> str:
|
||||||
"""Get formatted help text for a preset.
|
"""Get formatted help text for a preset.
|
||||||
|
|
||||||
@@ -129,6 +132,7 @@ def get_preset_help_text(preset_name: str) -> str:
|
|||||||
f" Depth: {preset.depth}"
|
f" Depth: {preset.depth}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def show_preset_list() -> None:
|
def show_preset_list() -> None:
|
||||||
"""Print the list of available presets to stdout.
|
"""Print the list of available presets to stdout.
|
||||||
|
|
||||||
@@ -161,6 +165,7 @@ def show_preset_list() -> None:
|
|||||||
print(" skill-seekers analyze --directory <dir> --preset comprehensive --enhance-level 2")
|
print(" skill-seekers analyze --directory <dir> --preset comprehensive --enhance-level 2")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
def resolve_enhance_level(args: argparse.Namespace) -> int:
|
def resolve_enhance_level(args: argparse.Namespace) -> int:
|
||||||
"""Determine the enhance level based on user arguments.
|
"""Determine the enhance level based on user arguments.
|
||||||
|
|
||||||
@@ -186,6 +191,7 @@ def resolve_enhance_level(args: argparse.Namespace) -> int:
|
|||||||
# Default is no enhancement
|
# Default is no enhancement
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def apply_preset_with_warnings(args: argparse.Namespace) -> str:
|
def apply_preset_with_warnings(args: argparse.Namespace) -> str:
|
||||||
"""Apply preset with deprecation warnings for legacy flags.
|
"""Apply preset with deprecation warnings for legacy flags.
|
||||||
|
|
||||||
@@ -240,6 +246,7 @@ def apply_preset_with_warnings(args: argparse.Namespace) -> str:
|
|||||||
|
|
||||||
return preset_name
|
return preset_name
|
||||||
|
|
||||||
|
|
||||||
def print_deprecation_warning(old_flag: str, new_flag: str) -> None:
|
def print_deprecation_warning(old_flag: str, new_flag: str) -> None:
|
||||||
"""Print a deprecation warning for legacy flags.
|
"""Print a deprecation warning for legacy flags.
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from dataclasses import dataclass, field
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class GitHubPreset:
|
class GitHubPreset:
|
||||||
"""Definition of a GitHub preset.
|
"""Definition of a GitHub preset.
|
||||||
@@ -23,12 +24,14 @@ class GitHubPreset:
|
|||||||
features: Dict of feature flags (feature_name -> enabled)
|
features: Dict of feature flags (feature_name -> enabled)
|
||||||
estimated_time: Human-readable time estimate
|
estimated_time: Human-readable time estimate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
max_issues: int
|
max_issues: int
|
||||||
features: dict[str, bool] = field(default_factory=dict)
|
features: dict[str, bool] = field(default_factory=dict)
|
||||||
estimated_time: str = ""
|
estimated_time: str = ""
|
||||||
|
|
||||||
|
|
||||||
# Preset definitions
|
# Preset definitions
|
||||||
GITHUB_PRESETS = {
|
GITHUB_PRESETS = {
|
||||||
"quick": GitHubPreset(
|
"quick": GitHubPreset(
|
||||||
@@ -40,9 +43,8 @@ GITHUB_PRESETS = {
|
|||||||
"include_changelog": True,
|
"include_changelog": True,
|
||||||
"include_releases": False,
|
"include_releases": False,
|
||||||
},
|
},
|
||||||
estimated_time="1-3 minutes"
|
estimated_time="1-3 minutes",
|
||||||
),
|
),
|
||||||
|
|
||||||
"standard": GitHubPreset(
|
"standard": GitHubPreset(
|
||||||
name="Standard",
|
name="Standard",
|
||||||
description="Balanced scraping with issues and releases (recommended)",
|
description="Balanced scraping with issues and releases (recommended)",
|
||||||
@@ -52,9 +54,8 @@ GITHUB_PRESETS = {
|
|||||||
"include_changelog": True,
|
"include_changelog": True,
|
||||||
"include_releases": True,
|
"include_releases": True,
|
||||||
},
|
},
|
||||||
estimated_time="5-15 minutes"
|
estimated_time="5-15 minutes",
|
||||||
),
|
),
|
||||||
|
|
||||||
"comprehensive": GitHubPreset(
|
"comprehensive": GitHubPreset(
|
||||||
name="Comprehensive",
|
name="Comprehensive",
|
||||||
description="Comprehensive scraping with all available data",
|
description="Comprehensive scraping with all available data",
|
||||||
@@ -64,10 +65,11 @@ GITHUB_PRESETS = {
|
|||||||
"include_changelog": True,
|
"include_changelog": True,
|
||||||
"include_releases": True,
|
"include_releases": True,
|
||||||
},
|
},
|
||||||
estimated_time="20-60 minutes"
|
estimated_time="20-60 minutes",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def apply_github_preset(args: argparse.Namespace, preset_name: str) -> None:
|
def apply_github_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||||
"""Apply a GitHub preset to the args namespace.
|
"""Apply a GitHub preset to the args namespace.
|
||||||
|
|
||||||
@@ -90,6 +92,7 @@ def apply_github_preset(args: argparse.Namespace, preset_name: str) -> None:
|
|||||||
if not hasattr(args, skip_attr) or not getattr(args, skip_attr):
|
if not hasattr(args, skip_attr) or not getattr(args, skip_attr):
|
||||||
setattr(args, skip_attr, not enabled)
|
setattr(args, skip_attr, not enabled)
|
||||||
|
|
||||||
|
|
||||||
def show_github_preset_list() -> None:
|
def show_github_preset_list() -> None:
|
||||||
"""Print the list of available GitHub presets to stdout."""
|
"""Print the list of available GitHub presets to stdout."""
|
||||||
print("\nAvailable GitHub Presets")
|
print("\nAvailable GitHub Presets")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ between speed and comprehensiveness.
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AnalysisPreset:
|
class AnalysisPreset:
|
||||||
"""Analysis preset configuration.
|
"""Analysis preset configuration.
|
||||||
@@ -22,6 +23,7 @@ class AnalysisPreset:
|
|||||||
estimated_time: str
|
estimated_time: str
|
||||||
icon: str
|
icon: str
|
||||||
|
|
||||||
|
|
||||||
# Preset definitions
|
# Preset definitions
|
||||||
PRESETS = {
|
PRESETS = {
|
||||||
"quick": AnalysisPreset(
|
"quick": AnalysisPreset(
|
||||||
@@ -77,6 +79,7 @@ PRESETS = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PresetManager:
|
class PresetManager:
|
||||||
"""Manages analysis presets and applies them to CLI arguments."""
|
"""Manages analysis presets and applies them to CLI arguments."""
|
||||||
|
|
||||||
@@ -164,6 +167,7 @@ class PresetManager:
|
|||||||
"""
|
"""
|
||||||
return "standard"
|
return "standard"
|
||||||
|
|
||||||
|
|
||||||
# Public API
|
# Public API
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AnalysisPreset",
|
"AnalysisPreset",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from dataclasses import dataclass, field
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ScrapePreset:
|
class ScrapePreset:
|
||||||
"""Definition of a scrape preset.
|
"""Definition of a scrape preset.
|
||||||
@@ -25,6 +26,7 @@ class ScrapePreset:
|
|||||||
workers: Number of parallel workers
|
workers: Number of parallel workers
|
||||||
estimated_time: Human-readable time estimate
|
estimated_time: Human-readable time estimate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
rate_limit: float
|
rate_limit: float
|
||||||
@@ -33,6 +35,7 @@ class ScrapePreset:
|
|||||||
workers: int = 1
|
workers: int = 1
|
||||||
estimated_time: str = ""
|
estimated_time: str = ""
|
||||||
|
|
||||||
|
|
||||||
# Preset definitions
|
# Preset definitions
|
||||||
SCRAPE_PRESETS = {
|
SCRAPE_PRESETS = {
|
||||||
"quick": ScrapePreset(
|
"quick": ScrapePreset(
|
||||||
@@ -45,9 +48,8 @@ SCRAPE_PRESETS = {
|
|||||||
},
|
},
|
||||||
async_mode=True,
|
async_mode=True,
|
||||||
workers=5,
|
workers=5,
|
||||||
estimated_time="2-5 minutes"
|
estimated_time="2-5 minutes",
|
||||||
),
|
),
|
||||||
|
|
||||||
"standard": ScrapePreset(
|
"standard": ScrapePreset(
|
||||||
name="Standard",
|
name="Standard",
|
||||||
description="Balanced scraping with good coverage (recommended)",
|
description="Balanced scraping with good coverage (recommended)",
|
||||||
@@ -58,9 +60,8 @@ SCRAPE_PRESETS = {
|
|||||||
},
|
},
|
||||||
async_mode=True,
|
async_mode=True,
|
||||||
workers=3,
|
workers=3,
|
||||||
estimated_time="10-30 minutes"
|
estimated_time="10-30 minutes",
|
||||||
),
|
),
|
||||||
|
|
||||||
"comprehensive": ScrapePreset(
|
"comprehensive": ScrapePreset(
|
||||||
name="Comprehensive",
|
name="Comprehensive",
|
||||||
description="Comprehensive scraping with all features",
|
description="Comprehensive scraping with all features",
|
||||||
@@ -71,10 +72,11 @@ SCRAPE_PRESETS = {
|
|||||||
},
|
},
|
||||||
async_mode=True,
|
async_mode=True,
|
||||||
workers=2,
|
workers=2,
|
||||||
estimated_time="1-3 hours"
|
estimated_time="1-3 hours",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def apply_scrape_preset(args: argparse.Namespace, preset_name: str) -> None:
|
def apply_scrape_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||||
"""Apply a scrape preset to the args namespace.
|
"""Apply a scrape preset to the args namespace.
|
||||||
|
|
||||||
@@ -100,9 +102,12 @@ def apply_scrape_preset(args: argparse.Namespace, preset_name: str) -> None:
|
|||||||
|
|
||||||
# Apply feature flags
|
# Apply feature flags
|
||||||
for feature, enabled in preset.features.items():
|
for feature, enabled in preset.features.items():
|
||||||
if feature == "rag_chunking" and (not hasattr(args, 'chunk_for_rag') or not args.chunk_for_rag):
|
if feature == "rag_chunking" and (
|
||||||
|
not hasattr(args, "chunk_for_rag") or not args.chunk_for_rag
|
||||||
|
):
|
||||||
args.chunk_for_rag = enabled
|
args.chunk_for_rag = enabled
|
||||||
|
|
||||||
|
|
||||||
def show_scrape_preset_list() -> None:
|
def show_scrape_preset_list() -> None:
|
||||||
"""Print the list of available scrape presets to stdout."""
|
"""Print the list of available scrape presets to stdout."""
|
||||||
print("\nAvailable Scrape Presets")
|
print("\nAvailable Scrape Presets")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import logging
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SourceInfo:
|
class SourceInfo:
|
||||||
"""Information about a detected source.
|
"""Information about a detected source.
|
||||||
@@ -23,18 +24,20 @@ class SourceInfo:
|
|||||||
suggested_name: Auto-suggested name for the skill
|
suggested_name: Auto-suggested name for the skill
|
||||||
raw_input: Original user input
|
raw_input: Original user input
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: str
|
type: str
|
||||||
parsed: dict[str, Any]
|
parsed: dict[str, Any]
|
||||||
suggested_name: str
|
suggested_name: str
|
||||||
raw_input: str
|
raw_input: str
|
||||||
|
|
||||||
|
|
||||||
class SourceDetector:
|
class SourceDetector:
|
||||||
"""Detects source type from user input and extracts relevant information."""
|
"""Detects source type from user input and extracts relevant information."""
|
||||||
|
|
||||||
# GitHub repo patterns
|
# GitHub repo patterns
|
||||||
GITHUB_REPO_PATTERN = re.compile(r'^([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)$')
|
GITHUB_REPO_PATTERN = re.compile(r"^([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)$")
|
||||||
GITHUB_URL_PATTERN = re.compile(
|
GITHUB_URL_PATTERN = re.compile(
|
||||||
r'(?:https?://)?(?:www\.)?github\.com/([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)(?:\.git)?'
|
r"(?:https?://)?(?:www\.)?github\.com/([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)(?:\.git)?"
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -51,10 +54,10 @@ class SourceDetector:
|
|||||||
ValueError: If source type cannot be determined
|
ValueError: If source type cannot be determined
|
||||||
"""
|
"""
|
||||||
# 1. File extension detection
|
# 1. File extension detection
|
||||||
if source.endswith('.json'):
|
if source.endswith(".json"):
|
||||||
return cls._detect_config(source)
|
return cls._detect_config(source)
|
||||||
|
|
||||||
if source.endswith('.pdf'):
|
if source.endswith(".pdf"):
|
||||||
return cls._detect_pdf(source)
|
return cls._detect_pdf(source)
|
||||||
|
|
||||||
# 2. Directory detection
|
# 2. Directory detection
|
||||||
@@ -67,12 +70,12 @@ class SourceDetector:
|
|||||||
return github_info
|
return github_info
|
||||||
|
|
||||||
# 4. URL detection
|
# 4. URL detection
|
||||||
if source.startswith('http://') or source.startswith('https://'):
|
if source.startswith("http://") or source.startswith("https://"):
|
||||||
return cls._detect_web(source)
|
return cls._detect_web(source)
|
||||||
|
|
||||||
# 5. Domain inference (add https://)
|
# 5. Domain inference (add https://)
|
||||||
if '.' in source and not source.startswith('/'):
|
if "." in source and not source.startswith("/"):
|
||||||
return cls._detect_web(f'https://{source}')
|
return cls._detect_web(f"https://{source}")
|
||||||
|
|
||||||
# 6. Error - cannot determine
|
# 6. Error - cannot determine
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -90,10 +93,7 @@ class SourceDetector:
|
|||||||
"""Detect config file source."""
|
"""Detect config file source."""
|
||||||
name = os.path.splitext(os.path.basename(source))[0]
|
name = os.path.splitext(os.path.basename(source))[0]
|
||||||
return SourceInfo(
|
return SourceInfo(
|
||||||
type='config',
|
type="config", parsed={"config_path": source}, suggested_name=name, raw_input=source
|
||||||
parsed={'config_path': source},
|
|
||||||
suggested_name=name,
|
|
||||||
raw_input=source
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -101,10 +101,7 @@ class SourceDetector:
|
|||||||
"""Detect PDF file source."""
|
"""Detect PDF file source."""
|
||||||
name = os.path.splitext(os.path.basename(source))[0]
|
name = os.path.splitext(os.path.basename(source))[0]
|
||||||
return SourceInfo(
|
return SourceInfo(
|
||||||
type='pdf',
|
type="pdf", parsed={"file_path": source}, suggested_name=name, raw_input=source
|
||||||
parsed={'file_path': source},
|
|
||||||
suggested_name=name,
|
|
||||||
raw_input=source
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -115,10 +112,7 @@ class SourceDetector:
|
|||||||
name = os.path.basename(directory)
|
name = os.path.basename(directory)
|
||||||
|
|
||||||
return SourceInfo(
|
return SourceInfo(
|
||||||
type='local',
|
type="local", parsed={"directory": directory}, suggested_name=name, raw_input=source
|
||||||
parsed={'directory': directory},
|
|
||||||
suggested_name=name,
|
|
||||||
raw_input=source
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -135,10 +129,10 @@ class SourceDetector:
|
|||||||
if match:
|
if match:
|
||||||
owner, repo = match.groups()
|
owner, repo = match.groups()
|
||||||
return SourceInfo(
|
return SourceInfo(
|
||||||
type='github',
|
type="github",
|
||||||
parsed={'repo': f'{owner}/{repo}'},
|
parsed={"repo": f"{owner}/{repo}"},
|
||||||
suggested_name=repo,
|
suggested_name=repo,
|
||||||
raw_input=source
|
raw_input=source,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Try GitHub URL pattern
|
# Try GitHub URL pattern
|
||||||
@@ -146,13 +140,13 @@ class SourceDetector:
|
|||||||
if match:
|
if match:
|
||||||
owner, repo = match.groups()
|
owner, repo = match.groups()
|
||||||
# Clean up repo name (remove .git suffix if present)
|
# Clean up repo name (remove .git suffix if present)
|
||||||
if repo.endswith('.git'):
|
if repo.endswith(".git"):
|
||||||
repo = repo[:-4]
|
repo = repo[:-4]
|
||||||
return SourceInfo(
|
return SourceInfo(
|
||||||
type='github',
|
type="github",
|
||||||
parsed={'repo': f'{owner}/{repo}'},
|
parsed={"repo": f"{owner}/{repo}"},
|
||||||
suggested_name=repo,
|
suggested_name=repo,
|
||||||
raw_input=source
|
raw_input=source,
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@@ -167,15 +161,10 @@ class SourceDetector:
|
|||||||
# Clean up domain for name suggestion
|
# Clean up domain for name suggestion
|
||||||
# docs.react.dev -> react
|
# docs.react.dev -> react
|
||||||
# reactjs.org -> react
|
# reactjs.org -> react
|
||||||
name = domain.replace('www.', '').replace('docs.', '')
|
name = domain.replace("www.", "").replace("docs.", "")
|
||||||
name = name.split('.')[0] # Take first part before TLD
|
name = name.split(".")[0] # Take first part before TLD
|
||||||
|
|
||||||
return SourceInfo(
|
return SourceInfo(type="web", parsed={"url": source}, suggested_name=name, raw_input=source)
|
||||||
type='web',
|
|
||||||
parsed={'url': source},
|
|
||||||
suggested_name=name,
|
|
||||||
raw_input=source
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_source(cls, source_info: SourceInfo) -> None:
|
def validate_source(cls, source_info: SourceInfo) -> None:
|
||||||
@@ -187,22 +176,22 @@ class SourceDetector:
|
|||||||
Raises:
|
Raises:
|
||||||
ValueError: If source is not accessible
|
ValueError: If source is not accessible
|
||||||
"""
|
"""
|
||||||
if source_info.type == 'local':
|
if source_info.type == "local":
|
||||||
directory = source_info.parsed['directory']
|
directory = source_info.parsed["directory"]
|
||||||
if not os.path.exists(directory):
|
if not os.path.exists(directory):
|
||||||
raise ValueError(f"Directory does not exist: {directory}")
|
raise ValueError(f"Directory does not exist: {directory}")
|
||||||
if not os.path.isdir(directory):
|
if not os.path.isdir(directory):
|
||||||
raise ValueError(f"Path is not a directory: {directory}")
|
raise ValueError(f"Path is not a directory: {directory}")
|
||||||
|
|
||||||
elif source_info.type == 'pdf':
|
elif source_info.type == "pdf":
|
||||||
file_path = source_info.parsed['file_path']
|
file_path = source_info.parsed["file_path"]
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
raise ValueError(f"PDF file does not exist: {file_path}")
|
raise ValueError(f"PDF file does not exist: {file_path}")
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
raise ValueError(f"Path is not a file: {file_path}")
|
raise ValueError(f"Path is not a file: {file_path}")
|
||||||
|
|
||||||
elif source_info.type == 'config':
|
elif source_info.type == "config":
|
||||||
config_path = source_info.parsed['config_path']
|
config_path = source_info.parsed["config_path"]
|
||||||
if not os.path.exists(config_path):
|
if not os.path.exists(config_path):
|
||||||
raise ValueError(f"Config file does not exist: {config_path}")
|
raise ValueError(f"Config file does not exist: {config_path}")
|
||||||
if not os.path.isfile(config_path):
|
if not os.path.isfile(config_path):
|
||||||
|
|||||||
@@ -13,15 +13,14 @@ import pytest
|
|||||||
import subprocess
|
import subprocess
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
class TestParserSync:
|
class TestParserSync:
|
||||||
"""E2E tests for parser synchronization (Issue #285)."""
|
"""E2E tests for parser synchronization (Issue #285)."""
|
||||||
|
|
||||||
def test_scrape_interactive_flag_works(self):
|
def test_scrape_interactive_flag_works(self):
|
||||||
"""Test that --interactive flag (previously missing) now works."""
|
"""Test that --interactive flag (previously missing) now works."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "scrape", "--interactive", "--help"],
|
["skill-seekers", "scrape", "--interactive", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
assert result.returncode == 0, "Command should execute successfully"
|
assert result.returncode == 0, "Command should execute successfully"
|
||||||
assert "--interactive" in result.stdout, "Help should show --interactive flag"
|
assert "--interactive" in result.stdout, "Help should show --interactive flag"
|
||||||
@@ -30,9 +29,7 @@ class TestParserSync:
|
|||||||
def test_scrape_chunk_for_rag_flag_works(self):
|
def test_scrape_chunk_for_rag_flag_works(self):
|
||||||
"""Test that --chunk-for-rag flag (previously missing) now works."""
|
"""Test that --chunk-for-rag flag (previously missing) now works."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "scrape", "--help"],
|
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
assert "--chunk-for-rag" in result.stdout, "Help should show --chunk-for-rag flag"
|
assert "--chunk-for-rag" in result.stdout, "Help should show --chunk-for-rag flag"
|
||||||
assert "--chunk-size" in result.stdout, "Help should show --chunk-size flag"
|
assert "--chunk-size" in result.stdout, "Help should show --chunk-size flag"
|
||||||
@@ -41,9 +38,7 @@ class TestParserSync:
|
|||||||
def test_scrape_verbose_flag_works(self):
|
def test_scrape_verbose_flag_works(self):
|
||||||
"""Test that --verbose flag (previously missing) now works."""
|
"""Test that --verbose flag (previously missing) now works."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "scrape", "--help"],
|
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
assert "--verbose" in result.stdout, "Help should show --verbose flag"
|
assert "--verbose" in result.stdout, "Help should show --verbose flag"
|
||||||
assert "-v" in result.stdout, "Help should show short form -v"
|
assert "-v" in result.stdout, "Help should show short form -v"
|
||||||
@@ -51,18 +46,14 @@ class TestParserSync:
|
|||||||
def test_scrape_url_flag_works(self):
|
def test_scrape_url_flag_works(self):
|
||||||
"""Test that --url flag (previously missing) now works."""
|
"""Test that --url flag (previously missing) now works."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "scrape", "--help"],
|
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
assert "--url URL" in result.stdout, "Help should show --url flag"
|
assert "--url URL" in result.stdout, "Help should show --url flag"
|
||||||
|
|
||||||
def test_github_all_flags_present(self):
|
def test_github_all_flags_present(self):
|
||||||
"""Test that github command has all expected flags."""
|
"""Test that github command has all expected flags."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "github", "--help"],
|
["skill-seekers", "github", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
# Key github flags that should be present
|
# Key github flags that should be present
|
||||||
expected_flags = [
|
expected_flags = [
|
||||||
@@ -74,15 +65,14 @@ class TestParserSync:
|
|||||||
for flag in expected_flags:
|
for flag in expected_flags:
|
||||||
assert flag in result.stdout, f"Help should show {flag} flag"
|
assert flag in result.stdout, f"Help should show {flag} flag"
|
||||||
|
|
||||||
|
|
||||||
class TestPresetSystem:
|
class TestPresetSystem:
|
||||||
"""E2E tests for preset system (Issue #268)."""
|
"""E2E tests for preset system (Issue #268)."""
|
||||||
|
|
||||||
def test_analyze_preset_flag_exists(self):
|
def test_analyze_preset_flag_exists(self):
|
||||||
"""Test that analyze command has --preset flag."""
|
"""Test that analyze command has --preset flag."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "analyze", "--help"],
|
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
assert "--preset" in result.stdout, "Help should show --preset flag"
|
assert "--preset" in result.stdout, "Help should show --preset flag"
|
||||||
assert "quick" in result.stdout, "Help should mention 'quick' preset"
|
assert "quick" in result.stdout, "Help should mention 'quick' preset"
|
||||||
@@ -92,18 +82,14 @@ class TestPresetSystem:
|
|||||||
def test_analyze_preset_list_flag_exists(self):
|
def test_analyze_preset_list_flag_exists(self):
|
||||||
"""Test that analyze command has --preset-list flag."""
|
"""Test that analyze command has --preset-list flag."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "analyze", "--help"],
|
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
assert "--preset-list" in result.stdout, "Help should show --preset-list flag"
|
assert "--preset-list" in result.stdout, "Help should show --preset-list flag"
|
||||||
|
|
||||||
def test_preset_list_shows_presets(self):
|
def test_preset_list_shows_presets(self):
|
||||||
"""Test that --preset-list shows all available presets."""
|
"""Test that --preset-list shows all available presets."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "analyze", "--preset-list"],
|
["skill-seekers", "analyze", "--preset-list"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
assert result.returncode == 0, "Command should execute successfully"
|
assert result.returncode == 0, "Command should execute successfully"
|
||||||
assert "Available presets" in result.stdout, "Should show preset list header"
|
assert "Available presets" in result.stdout, "Should show preset list header"
|
||||||
@@ -118,7 +104,7 @@ class TestPresetSystem:
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "analyze", "--directory", ".", "--quick"],
|
["skill-seekers", "analyze", "--directory", ".", "--quick"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True,
|
||||||
)
|
)
|
||||||
# Note: Deprecation warnings go to stderr
|
# Note: Deprecation warnings go to stderr
|
||||||
output = result.stdout + result.stderr
|
output = result.stdout + result.stderr
|
||||||
@@ -131,22 +117,19 @@ class TestPresetSystem:
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "analyze", "--directory", ".", "--comprehensive"],
|
["skill-seekers", "analyze", "--directory", ".", "--comprehensive"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True,
|
||||||
)
|
)
|
||||||
output = result.stdout + result.stderr
|
output = result.stdout + result.stderr
|
||||||
assert "DEPRECATED" in output, "Should show deprecation warning"
|
assert "DEPRECATED" in output, "Should show deprecation warning"
|
||||||
assert "--preset comprehensive" in output, "Should suggest alternative"
|
assert "--preset comprehensive" in output, "Should suggest alternative"
|
||||||
|
|
||||||
|
|
||||||
class TestBackwardCompatibility:
|
class TestBackwardCompatibility:
|
||||||
"""E2E tests for backward compatibility."""
|
"""E2E tests for backward compatibility."""
|
||||||
|
|
||||||
def test_old_scrape_command_still_works(self):
|
def test_old_scrape_command_still_works(self):
|
||||||
"""Test that old scrape command invocations still work."""
|
"""Test that old scrape command invocations still work."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(["skill-seekers-scrape", "--help"], capture_output=True, text=True)
|
||||||
["skill-seekers-scrape", "--help"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
assert result.returncode == 0, "Old command should still work"
|
assert result.returncode == 0, "Old command should still work"
|
||||||
assert "documentation" in result.stdout.lower(), "Help should mention documentation"
|
assert "documentation" in result.stdout.lower(), "Help should mention documentation"
|
||||||
|
|
||||||
@@ -154,16 +137,12 @@ class TestBackwardCompatibility:
|
|||||||
"""Test that unified CLI and standalone have identical arguments."""
|
"""Test that unified CLI and standalone have identical arguments."""
|
||||||
# Get help from unified CLI
|
# Get help from unified CLI
|
||||||
unified_result = subprocess.run(
|
unified_result = subprocess.run(
|
||||||
["skill-seekers", "scrape", "--help"],
|
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get help from standalone
|
# Get help from standalone
|
||||||
standalone_result = subprocess.run(
|
standalone_result = subprocess.run(
|
||||||
["skill-seekers-scrape", "--help"],
|
["skill-seekers-scrape", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Both should have the same key flags
|
# Both should have the same key flags
|
||||||
@@ -180,6 +159,7 @@ class TestBackwardCompatibility:
|
|||||||
assert flag in unified_result.stdout, f"Unified should have {flag}"
|
assert flag in unified_result.stdout, f"Unified should have {flag}"
|
||||||
assert flag in standalone_result.stdout, f"Standalone should have {flag}"
|
assert flag in standalone_result.stdout, f"Standalone should have {flag}"
|
||||||
|
|
||||||
|
|
||||||
class TestProgrammaticAPI:
|
class TestProgrammaticAPI:
|
||||||
"""Test that the shared argument functions work programmatically."""
|
"""Test that the shared argument functions work programmatically."""
|
||||||
|
|
||||||
@@ -221,16 +201,13 @@ class TestProgrammaticAPI:
|
|||||||
# Note: enhance_level is not part of AnalysisPreset anymore.
|
# Note: enhance_level is not part of AnalysisPreset anymore.
|
||||||
# It's controlled separately via --enhance-level flag (default 2)
|
# It's controlled separately via --enhance-level flag (default 2)
|
||||||
|
|
||||||
|
|
||||||
class TestIntegration:
|
class TestIntegration:
|
||||||
"""Integration tests for the complete flow."""
|
"""Integration tests for the complete flow."""
|
||||||
|
|
||||||
def test_unified_cli_subcommands_registered(self):
|
def test_unified_cli_subcommands_registered(self):
|
||||||
"""Test that all subcommands are properly registered."""
|
"""Test that all subcommands are properly registered."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(["skill-seekers", "--help"], capture_output=True, text=True)
|
||||||
["skill-seekers", "--help"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# All major commands should be listed
|
# All major commands should be listed
|
||||||
expected_commands = [
|
expected_commands = [
|
||||||
@@ -250,9 +227,7 @@ class TestIntegration:
|
|||||||
def test_scrape_help_detailed(self):
|
def test_scrape_help_detailed(self):
|
||||||
"""Test that scrape help shows all argument details."""
|
"""Test that scrape help shows all argument details."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "scrape", "--help"],
|
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check for argument categories
|
# Check for argument categories
|
||||||
@@ -263,13 +238,14 @@ class TestIntegration:
|
|||||||
def test_analyze_help_shows_presets(self):
|
def test_analyze_help_shows_presets(self):
|
||||||
"""Test that analyze help prominently shows preset information."""
|
"""Test that analyze help prominently shows preset information."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["skill-seekers", "analyze", "--help"],
|
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "--preset" in result.stdout, "Should show --preset flag"
|
assert "--preset" in result.stdout, "Should show --preset flag"
|
||||||
assert "DEFAULT" in result.stdout or "default" in result.stdout, "Should indicate default preset"
|
assert "DEFAULT" in result.stdout or "default" in result.stdout, (
|
||||||
|
"Should indicate default preset"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestE2EWorkflow:
|
class TestE2EWorkflow:
|
||||||
"""End-to-end workflow tests."""
|
"""End-to-end workflow tests."""
|
||||||
@@ -279,15 +255,18 @@ class TestE2EWorkflow:
|
|||||||
"""Test scraping with previously missing arguments (dry run)."""
|
"""Test scraping with previously missing arguments (dry run)."""
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[
|
[
|
||||||
"skill-seekers", "scrape",
|
"skill-seekers",
|
||||||
"--url", "https://example.com",
|
"scrape",
|
||||||
"--interactive", "false", # Would fail if arg didn't exist
|
"--url",
|
||||||
|
"https://example.com",
|
||||||
|
"--interactive",
|
||||||
|
"false", # Would fail if arg didn't exist
|
||||||
"--verbose", # Would fail if arg didn't exist
|
"--verbose", # Would fail if arg didn't exist
|
||||||
"--dry-run",
|
"--dry-run",
|
||||||
],
|
],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=10
|
timeout=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Dry run should complete without errors
|
# Dry run should complete without errors
|
||||||
@@ -314,5 +293,6 @@ class TestE2EWorkflow:
|
|||||||
assert "--preset" in result.stdout, "Should have --preset flag"
|
assert "--preset" in result.stdout, "Should have --preset flag"
|
||||||
assert "unrecognized arguments" not in result.stderr.lower()
|
assert "unrecognized arguments" not in result.stderr.lower()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
pytest.main([__file__, "-v", "-s"])
|
pytest.main([__file__, "-v", "-s"])
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from skill_seekers.cli.arguments.create import (
|
|||||||
add_create_arguments,
|
add_create_arguments,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestUniversalArguments:
|
class TestUniversalArguments:
|
||||||
"""Test universal argument definitions."""
|
"""Test universal argument definitions."""
|
||||||
|
|
||||||
@@ -29,25 +30,34 @@ class TestUniversalArguments:
|
|||||||
def test_universal_argument_names(self):
|
def test_universal_argument_names(self):
|
||||||
"""Universal arguments should have expected names."""
|
"""Universal arguments should have expected names."""
|
||||||
expected_names = {
|
expected_names = {
|
||||||
'name', 'description', 'output',
|
"name",
|
||||||
'enhance_level', 'api_key', # Phase 1: consolidated from enhance + enhance_local
|
"description",
|
||||||
'dry_run', 'verbose', 'quiet',
|
"output",
|
||||||
'chunk_for_rag', 'chunk_size', 'chunk_overlap', # Phase 2: RAG args from common.py
|
"enhance_level",
|
||||||
'preset', 'config'
|
"api_key", # Phase 1: consolidated from enhance + enhance_local
|
||||||
|
"dry_run",
|
||||||
|
"verbose",
|
||||||
|
"quiet",
|
||||||
|
"chunk_for_rag",
|
||||||
|
"chunk_size",
|
||||||
|
"chunk_overlap", # Phase 2: RAG args from common.py
|
||||||
|
"preset",
|
||||||
|
"config",
|
||||||
}
|
}
|
||||||
assert set(UNIVERSAL_ARGUMENTS.keys()) == expected_names
|
assert set(UNIVERSAL_ARGUMENTS.keys()) == expected_names
|
||||||
|
|
||||||
def test_all_universal_have_flags(self):
|
def test_all_universal_have_flags(self):
|
||||||
"""All universal arguments should have flags."""
|
"""All universal arguments should have flags."""
|
||||||
for arg_name, arg_def in UNIVERSAL_ARGUMENTS.items():
|
for arg_name, arg_def in UNIVERSAL_ARGUMENTS.items():
|
||||||
assert 'flags' in arg_def
|
assert "flags" in arg_def
|
||||||
assert len(arg_def['flags']) > 0
|
assert len(arg_def["flags"]) > 0
|
||||||
|
|
||||||
def test_all_universal_have_kwargs(self):
|
def test_all_universal_have_kwargs(self):
|
||||||
"""All universal arguments should have kwargs."""
|
"""All universal arguments should have kwargs."""
|
||||||
for arg_name, arg_def in UNIVERSAL_ARGUMENTS.items():
|
for arg_name, arg_def in UNIVERSAL_ARGUMENTS.items():
|
||||||
assert 'kwargs' in arg_def
|
assert "kwargs" in arg_def
|
||||||
assert 'help' in arg_def['kwargs']
|
assert "help" in arg_def["kwargs"]
|
||||||
|
|
||||||
|
|
||||||
class TestSourceSpecificArguments:
|
class TestSourceSpecificArguments:
|
||||||
"""Test source-specific argument definitions."""
|
"""Test source-specific argument definitions."""
|
||||||
@@ -55,29 +65,29 @@ class TestSourceSpecificArguments:
|
|||||||
def test_web_arguments_exist(self):
|
def test_web_arguments_exist(self):
|
||||||
"""Web-specific arguments should be defined."""
|
"""Web-specific arguments should be defined."""
|
||||||
assert len(WEB_ARGUMENTS) > 0
|
assert len(WEB_ARGUMENTS) > 0
|
||||||
assert 'max_pages' in WEB_ARGUMENTS
|
assert "max_pages" in WEB_ARGUMENTS
|
||||||
assert 'rate_limit' in WEB_ARGUMENTS
|
assert "rate_limit" in WEB_ARGUMENTS
|
||||||
assert 'workers' in WEB_ARGUMENTS
|
assert "workers" in WEB_ARGUMENTS
|
||||||
|
|
||||||
def test_github_arguments_exist(self):
|
def test_github_arguments_exist(self):
|
||||||
"""GitHub-specific arguments should be defined."""
|
"""GitHub-specific arguments should be defined."""
|
||||||
assert len(GITHUB_ARGUMENTS) > 0
|
assert len(GITHUB_ARGUMENTS) > 0
|
||||||
assert 'repo' in GITHUB_ARGUMENTS
|
assert "repo" in GITHUB_ARGUMENTS
|
||||||
assert 'token' in GITHUB_ARGUMENTS
|
assert "token" in GITHUB_ARGUMENTS
|
||||||
assert 'max_issues' in GITHUB_ARGUMENTS
|
assert "max_issues" in GITHUB_ARGUMENTS
|
||||||
|
|
||||||
def test_local_arguments_exist(self):
|
def test_local_arguments_exist(self):
|
||||||
"""Local-specific arguments should be defined."""
|
"""Local-specific arguments should be defined."""
|
||||||
assert len(LOCAL_ARGUMENTS) > 0
|
assert len(LOCAL_ARGUMENTS) > 0
|
||||||
assert 'directory' in LOCAL_ARGUMENTS
|
assert "directory" in LOCAL_ARGUMENTS
|
||||||
assert 'languages' in LOCAL_ARGUMENTS
|
assert "languages" in LOCAL_ARGUMENTS
|
||||||
assert 'skip_patterns' in LOCAL_ARGUMENTS
|
assert "skip_patterns" in LOCAL_ARGUMENTS
|
||||||
|
|
||||||
def test_pdf_arguments_exist(self):
|
def test_pdf_arguments_exist(self):
|
||||||
"""PDF-specific arguments should be defined."""
|
"""PDF-specific arguments should be defined."""
|
||||||
assert len(PDF_ARGUMENTS) > 0
|
assert len(PDF_ARGUMENTS) > 0
|
||||||
assert 'pdf' in PDF_ARGUMENTS
|
assert "pdf" in PDF_ARGUMENTS
|
||||||
assert 'ocr' in PDF_ARGUMENTS
|
assert "ocr" in PDF_ARGUMENTS
|
||||||
|
|
||||||
def test_no_duplicate_flags_across_sources(self):
|
def test_no_duplicate_flags_across_sources(self):
|
||||||
"""Source-specific arguments should not have duplicate flags."""
|
"""Source-specific arguments should not have duplicate flags."""
|
||||||
@@ -86,21 +96,25 @@ class TestSourceSpecificArguments:
|
|||||||
|
|
||||||
for source_args in [WEB_ARGUMENTS, GITHUB_ARGUMENTS, LOCAL_ARGUMENTS, PDF_ARGUMENTS]:
|
for source_args in [WEB_ARGUMENTS, GITHUB_ARGUMENTS, LOCAL_ARGUMENTS, PDF_ARGUMENTS]:
|
||||||
for arg_name, arg_def in source_args.items():
|
for arg_name, arg_def in source_args.items():
|
||||||
flags = arg_def['flags']
|
flags = arg_def["flags"]
|
||||||
for flag in flags:
|
for flag in flags:
|
||||||
# Check if this flag already exists in source-specific args
|
# Check if this flag already exists in source-specific args
|
||||||
if flag not in [f for arg in UNIVERSAL_ARGUMENTS.values() for f in arg['flags']]:
|
if flag not in [
|
||||||
|
f for arg in UNIVERSAL_ARGUMENTS.values() for f in arg["flags"]
|
||||||
|
]:
|
||||||
assert flag not in all_flags, f"Duplicate flag: {flag}"
|
assert flag not in all_flags, f"Duplicate flag: {flag}"
|
||||||
all_flags.add(flag)
|
all_flags.add(flag)
|
||||||
|
|
||||||
|
|
||||||
class TestAdvancedArguments:
|
class TestAdvancedArguments:
|
||||||
"""Test advanced/rare argument definitions."""
|
"""Test advanced/rare argument definitions."""
|
||||||
|
|
||||||
def test_advanced_arguments_exist(self):
|
def test_advanced_arguments_exist(self):
|
||||||
"""Advanced arguments should be defined."""
|
"""Advanced arguments should be defined."""
|
||||||
assert len(ADVANCED_ARGUMENTS) > 0
|
assert len(ADVANCED_ARGUMENTS) > 0
|
||||||
assert 'no_rate_limit' in ADVANCED_ARGUMENTS
|
assert "no_rate_limit" in ADVANCED_ARGUMENTS
|
||||||
assert 'interactive_enhancement' in ADVANCED_ARGUMENTS
|
assert "interactive_enhancement" in ADVANCED_ARGUMENTS
|
||||||
|
|
||||||
|
|
||||||
class TestArgumentHelpers:
|
class TestArgumentHelpers:
|
||||||
"""Test helper functions."""
|
"""Test helper functions."""
|
||||||
@@ -110,106 +124,108 @@ class TestArgumentHelpers:
|
|||||||
names = get_universal_argument_names()
|
names = get_universal_argument_names()
|
||||||
assert isinstance(names, set)
|
assert isinstance(names, set)
|
||||||
assert len(names) == 13
|
assert len(names) == 13
|
||||||
assert 'name' in names
|
assert "name" in names
|
||||||
assert 'enhance_level' in names # Phase 1: consolidated flag
|
assert "enhance_level" in names # Phase 1: consolidated flag
|
||||||
|
|
||||||
def test_get_source_specific_web(self):
|
def test_get_source_specific_web(self):
|
||||||
"""Should return web-specific arguments."""
|
"""Should return web-specific arguments."""
|
||||||
args = get_source_specific_arguments('web')
|
args = get_source_specific_arguments("web")
|
||||||
assert args == WEB_ARGUMENTS
|
assert args == WEB_ARGUMENTS
|
||||||
|
|
||||||
def test_get_source_specific_github(self):
|
def test_get_source_specific_github(self):
|
||||||
"""Should return github-specific arguments."""
|
"""Should return github-specific arguments."""
|
||||||
args = get_source_specific_arguments('github')
|
args = get_source_specific_arguments("github")
|
||||||
assert args == GITHUB_ARGUMENTS
|
assert args == GITHUB_ARGUMENTS
|
||||||
|
|
||||||
def test_get_source_specific_local(self):
|
def test_get_source_specific_local(self):
|
||||||
"""Should return local-specific arguments."""
|
"""Should return local-specific arguments."""
|
||||||
args = get_source_specific_arguments('local')
|
args = get_source_specific_arguments("local")
|
||||||
assert args == LOCAL_ARGUMENTS
|
assert args == LOCAL_ARGUMENTS
|
||||||
|
|
||||||
def test_get_source_specific_pdf(self):
|
def test_get_source_specific_pdf(self):
|
||||||
"""Should return pdf-specific arguments."""
|
"""Should return pdf-specific arguments."""
|
||||||
args = get_source_specific_arguments('pdf')
|
args = get_source_specific_arguments("pdf")
|
||||||
assert args == PDF_ARGUMENTS
|
assert args == PDF_ARGUMENTS
|
||||||
|
|
||||||
def test_get_source_specific_config(self):
|
def test_get_source_specific_config(self):
|
||||||
"""Config should return empty dict (no extra args)."""
|
"""Config should return empty dict (no extra args)."""
|
||||||
args = get_source_specific_arguments('config')
|
args = get_source_specific_arguments("config")
|
||||||
assert args == {}
|
assert args == {}
|
||||||
|
|
||||||
def test_get_source_specific_unknown(self):
|
def test_get_source_specific_unknown(self):
|
||||||
"""Unknown source should return empty dict."""
|
"""Unknown source should return empty dict."""
|
||||||
args = get_source_specific_arguments('unknown')
|
args = get_source_specific_arguments("unknown")
|
||||||
assert args == {}
|
assert args == {}
|
||||||
|
|
||||||
|
|
||||||
class TestCompatibleArguments:
|
class TestCompatibleArguments:
|
||||||
"""Test compatible argument detection."""
|
"""Test compatible argument detection."""
|
||||||
|
|
||||||
def test_web_compatible_arguments(self):
|
def test_web_compatible_arguments(self):
|
||||||
"""Web source should include universal + web + advanced."""
|
"""Web source should include universal + web + advanced."""
|
||||||
compatible = get_compatible_arguments('web')
|
compatible = get_compatible_arguments("web")
|
||||||
|
|
||||||
# Should include universal arguments
|
# Should include universal arguments
|
||||||
assert 'name' in compatible
|
assert "name" in compatible
|
||||||
assert 'enhance_level' in compatible # Phase 1: consolidated flag
|
assert "enhance_level" in compatible # Phase 1: consolidated flag
|
||||||
|
|
||||||
# Should include web-specific arguments
|
# Should include web-specific arguments
|
||||||
assert 'max_pages' in compatible
|
assert "max_pages" in compatible
|
||||||
assert 'rate_limit' in compatible
|
assert "rate_limit" in compatible
|
||||||
|
|
||||||
# Should include advanced arguments
|
# Should include advanced arguments
|
||||||
assert 'no_rate_limit' in compatible
|
assert "no_rate_limit" in compatible
|
||||||
|
|
||||||
def test_github_compatible_arguments(self):
|
def test_github_compatible_arguments(self):
|
||||||
"""GitHub source should include universal + github + advanced."""
|
"""GitHub source should include universal + github + advanced."""
|
||||||
compatible = get_compatible_arguments('github')
|
compatible = get_compatible_arguments("github")
|
||||||
|
|
||||||
# Should include universal arguments
|
# Should include universal arguments
|
||||||
assert 'name' in compatible
|
assert "name" in compatible
|
||||||
|
|
||||||
# Should include github-specific arguments
|
# Should include github-specific arguments
|
||||||
assert 'repo' in compatible
|
assert "repo" in compatible
|
||||||
assert 'token' in compatible
|
assert "token" in compatible
|
||||||
|
|
||||||
# Should include advanced arguments
|
# Should include advanced arguments
|
||||||
assert 'interactive_enhancement' in compatible
|
assert "interactive_enhancement" in compatible
|
||||||
|
|
||||||
def test_local_compatible_arguments(self):
|
def test_local_compatible_arguments(self):
|
||||||
"""Local source should include universal + local + advanced."""
|
"""Local source should include universal + local + advanced."""
|
||||||
compatible = get_compatible_arguments('local')
|
compatible = get_compatible_arguments("local")
|
||||||
|
|
||||||
# Should include universal arguments
|
# Should include universal arguments
|
||||||
assert 'description' in compatible
|
assert "description" in compatible
|
||||||
|
|
||||||
# Should include local-specific arguments
|
# Should include local-specific arguments
|
||||||
assert 'directory' in compatible
|
assert "directory" in compatible
|
||||||
assert 'languages' in compatible
|
assert "languages" in compatible
|
||||||
|
|
||||||
def test_pdf_compatible_arguments(self):
|
def test_pdf_compatible_arguments(self):
|
||||||
"""PDF source should include universal + pdf + advanced."""
|
"""PDF source should include universal + pdf + advanced."""
|
||||||
compatible = get_compatible_arguments('pdf')
|
compatible = get_compatible_arguments("pdf")
|
||||||
|
|
||||||
# Should include universal arguments
|
# Should include universal arguments
|
||||||
assert 'output' in compatible
|
assert "output" in compatible
|
||||||
|
|
||||||
# Should include pdf-specific arguments
|
# Should include pdf-specific arguments
|
||||||
assert 'pdf' in compatible
|
assert "pdf" in compatible
|
||||||
assert 'ocr' in compatible
|
assert "ocr" in compatible
|
||||||
|
|
||||||
def test_config_compatible_arguments(self):
|
def test_config_compatible_arguments(self):
|
||||||
"""Config source should include universal + advanced only."""
|
"""Config source should include universal + advanced only."""
|
||||||
compatible = get_compatible_arguments('config')
|
compatible = get_compatible_arguments("config")
|
||||||
|
|
||||||
# Should include universal arguments
|
# Should include universal arguments
|
||||||
assert 'config' in compatible
|
assert "config" in compatible
|
||||||
|
|
||||||
# Should include advanced arguments
|
# Should include advanced arguments
|
||||||
assert 'no_preserve_code_blocks' in compatible
|
assert "no_preserve_code_blocks" in compatible
|
||||||
|
|
||||||
# Should not include source-specific arguments
|
# Should not include source-specific arguments
|
||||||
assert 'repo' not in compatible
|
assert "repo" not in compatible
|
||||||
assert 'directory' not in compatible
|
assert "directory" not in compatible
|
||||||
|
|
||||||
|
|
||||||
class TestAddCreateArguments:
|
class TestAddCreateArguments:
|
||||||
"""Test add_create_arguments function."""
|
"""Test add_create_arguments function."""
|
||||||
@@ -217,16 +233,17 @@ class TestAddCreateArguments:
|
|||||||
def test_default_mode_adds_universal_only(self):
|
def test_default_mode_adds_universal_only(self):
|
||||||
"""Default mode should add only universal arguments + source positional."""
|
"""Default mode should add only universal arguments + source positional."""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
add_create_arguments(parser, mode='default')
|
add_create_arguments(parser, mode="default")
|
||||||
|
|
||||||
# Parse to get all arguments
|
# Parse to get all arguments
|
||||||
args = vars(parser.parse_args([]))
|
args = vars(parser.parse_args([]))
|
||||||
|
|
||||||
# Should have universal arguments
|
# Should have universal arguments
|
||||||
assert 'name' in args
|
assert "name" in args
|
||||||
assert 'enhance_level' in args
|
assert "enhance_level" in args
|
||||||
assert 'chunk_for_rag' in args
|
assert "chunk_for_rag" in args
|
||||||
|
|
||||||
# Should not have source-specific arguments (they're not added in default mode)
|
# Should not have source-specific arguments (they're not added in default mode)
|
||||||
# Note: argparse won't error on unknown args, but they won't be in namespace
|
# Note: argparse won't error on unknown args, but they won't be in namespace
|
||||||
@@ -234,62 +251,60 @@ class TestAddCreateArguments:
|
|||||||
def test_web_mode_adds_web_arguments(self):
|
def test_web_mode_adds_web_arguments(self):
|
||||||
"""Web mode should add universal + web arguments."""
|
"""Web mode should add universal + web arguments."""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
add_create_arguments(parser, mode='web')
|
add_create_arguments(parser, mode="web")
|
||||||
|
|
||||||
args = vars(parser.parse_args([]))
|
args = vars(parser.parse_args([]))
|
||||||
|
|
||||||
# Should have universal arguments
|
# Should have universal arguments
|
||||||
assert 'name' in args
|
assert "name" in args
|
||||||
|
|
||||||
# Should have web-specific arguments
|
# Should have web-specific arguments
|
||||||
assert 'max_pages' in args
|
assert "max_pages" in args
|
||||||
assert 'rate_limit' in args
|
assert "rate_limit" in args
|
||||||
|
|
||||||
def test_all_mode_adds_all_arguments(self):
|
def test_all_mode_adds_all_arguments(self):
|
||||||
"""All mode should add every argument."""
|
"""All mode should add every argument."""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
add_create_arguments(parser, mode='all')
|
add_create_arguments(parser, mode="all")
|
||||||
|
|
||||||
args = vars(parser.parse_args([]))
|
args = vars(parser.parse_args([]))
|
||||||
|
|
||||||
# Should have universal arguments
|
# Should have universal arguments
|
||||||
assert 'name' in args
|
assert "name" in args
|
||||||
|
|
||||||
# Should have all source-specific arguments
|
# Should have all source-specific arguments
|
||||||
assert 'max_pages' in args # web
|
assert "max_pages" in args # web
|
||||||
assert 'repo' in args # github
|
assert "repo" in args # github
|
||||||
assert 'directory' in args # local
|
assert "directory" in args # local
|
||||||
assert 'pdf' in args # pdf
|
assert "pdf" in args # pdf
|
||||||
|
|
||||||
# Should have advanced arguments
|
# Should have advanced arguments
|
||||||
assert 'no_rate_limit' in args
|
assert "no_rate_limit" in args
|
||||||
|
|
||||||
def test_positional_source_argument_always_added(self):
|
def test_positional_source_argument_always_added(self):
|
||||||
"""Source positional argument should always be added."""
|
"""Source positional argument should always be added."""
|
||||||
import argparse
|
import argparse
|
||||||
for mode in ['default', 'web', 'github', 'local', 'pdf', 'all']:
|
|
||||||
|
for mode in ["default", "web", "github", "local", "pdf", "all"]:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
add_create_arguments(parser, mode=mode)
|
add_create_arguments(parser, mode=mode)
|
||||||
|
|
||||||
# Should accept source as positional
|
# Should accept source as positional
|
||||||
args = parser.parse_args(['some_source'])
|
args = parser.parse_args(["some_source"])
|
||||||
assert args.source == 'some_source'
|
assert args.source == "some_source"
|
||||||
|
|
||||||
|
|
||||||
class TestNoDuplicates:
|
class TestNoDuplicates:
|
||||||
"""Test that there are no duplicate arguments across tiers."""
|
"""Test that there are no duplicate arguments across tiers."""
|
||||||
|
|
||||||
def test_no_duplicates_between_universal_and_web(self):
|
def test_no_duplicates_between_universal_and_web(self):
|
||||||
"""Universal and web args should not overlap."""
|
"""Universal and web args should not overlap."""
|
||||||
universal_flags = {
|
universal_flags = {flag for arg in UNIVERSAL_ARGUMENTS.values() for flag in arg["flags"]}
|
||||||
flag for arg in UNIVERSAL_ARGUMENTS.values()
|
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg["flags"]}
|
||||||
for flag in arg['flags']
|
|
||||||
}
|
|
||||||
web_flags = {
|
|
||||||
flag for arg in WEB_ARGUMENTS.values()
|
|
||||||
for flag in arg['flags']
|
|
||||||
}
|
|
||||||
|
|
||||||
# Allow some overlap since we intentionally include common args
|
# Allow some overlap since we intentionally include common args
|
||||||
# in multiple places, but check that they're properly defined
|
# in multiple places, but check that they're properly defined
|
||||||
@@ -299,10 +314,10 @@ class TestNoDuplicates:
|
|||||||
|
|
||||||
def test_no_duplicates_between_source_specific_args(self):
|
def test_no_duplicates_between_source_specific_args(self):
|
||||||
"""Different source-specific arg groups should not overlap."""
|
"""Different source-specific arg groups should not overlap."""
|
||||||
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg['flags']}
|
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg["flags"]}
|
||||||
github_flags = {flag for arg in GITHUB_ARGUMENTS.values() for flag in arg['flags']}
|
github_flags = {flag for arg in GITHUB_ARGUMENTS.values() for flag in arg["flags"]}
|
||||||
local_flags = {flag for arg in LOCAL_ARGUMENTS.values() for flag in arg['flags']}
|
local_flags = {flag for arg in LOCAL_ARGUMENTS.values() for flag in arg["flags"]}
|
||||||
pdf_flags = {flag for arg in PDF_ARGUMENTS.values() for flag in arg['flags']}
|
pdf_flags = {flag for arg in PDF_ARGUMENTS.values() for flag in arg["flags"]}
|
||||||
|
|
||||||
# No overlap between different source types
|
# No overlap between different source types
|
||||||
assert len(web_flags & github_flags) == 0
|
assert len(web_flags & github_flags) == 0
|
||||||
@@ -312,6 +327,7 @@ class TestNoDuplicates:
|
|||||||
assert len(github_flags & pdf_flags) == 0
|
assert len(github_flags & pdf_flags) == 0
|
||||||
assert len(local_flags & pdf_flags) == 0
|
assert len(local_flags & pdf_flags) == 0
|
||||||
|
|
||||||
|
|
||||||
class TestArgumentQuality:
|
class TestArgumentQuality:
|
||||||
"""Test argument definition quality."""
|
"""Test argument definition quality."""
|
||||||
|
|
||||||
@@ -327,8 +343,8 @@ class TestArgumentQuality:
|
|||||||
}
|
}
|
||||||
|
|
||||||
for arg_name, arg_def in all_args.items():
|
for arg_name, arg_def in all_args.items():
|
||||||
assert 'help' in arg_def['kwargs'], f"{arg_name} missing help text"
|
assert "help" in arg_def["kwargs"], f"{arg_name} missing help text"
|
||||||
assert len(arg_def['kwargs']['help']) > 0, f"{arg_name} has empty help text"
|
assert len(arg_def["kwargs"]["help"]) > 0, f"{arg_name} has empty help text"
|
||||||
|
|
||||||
def test_boolean_arguments_use_store_true(self):
|
def test_boolean_arguments_use_store_true(self):
|
||||||
"""Boolean flags should use store_true action."""
|
"""Boolean flags should use store_true action."""
|
||||||
@@ -342,13 +358,25 @@ class TestArgumentQuality:
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean_args = [
|
boolean_args = [
|
||||||
'dry_run', 'verbose', 'quiet',
|
"dry_run",
|
||||||
'chunk_for_rag', 'skip_scrape', 'resume', 'fresh', 'async_mode',
|
"verbose",
|
||||||
'no_issues', 'no_changelog', 'no_releases', 'scrape_only',
|
"quiet",
|
||||||
'skip_patterns', 'skip_test_examples', 'ocr', 'no_rate_limit'
|
"chunk_for_rag",
|
||||||
|
"skip_scrape",
|
||||||
|
"resume",
|
||||||
|
"fresh",
|
||||||
|
"async_mode",
|
||||||
|
"no_issues",
|
||||||
|
"no_changelog",
|
||||||
|
"no_releases",
|
||||||
|
"scrape_only",
|
||||||
|
"skip_patterns",
|
||||||
|
"skip_test_examples",
|
||||||
|
"ocr",
|
||||||
|
"no_rate_limit",
|
||||||
]
|
]
|
||||||
|
|
||||||
for arg_name in boolean_args:
|
for arg_name in boolean_args:
|
||||||
if arg_name in all_args:
|
if arg_name in all_args:
|
||||||
action = all_args[arg_name]['kwargs'].get('action')
|
action = all_args[arg_name]["kwargs"].get("action")
|
||||||
assert action == 'store_true', f"{arg_name} should use store_true"
|
assert action == "store_true", f"{arg_name} should use store_true"
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ and routes to the correct scrapers without actually scraping.
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
class TestCreateCommandBasic:
|
class TestCreateCommandBasic:
|
||||||
"""Basic integration tests for create command (dry-run mode)."""
|
"""Basic integration tests for create command (dry-run mode)."""
|
||||||
|
|
||||||
def test_create_command_help(self):
|
def test_create_command_help(self):
|
||||||
"""Test that create command help works."""
|
"""Test that create command help works."""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'create', '--help'],
|
["skill-seekers", "create", "--help"], capture_output=True, text=True
|
||||||
capture_output=True,
|
|
||||||
text=True
|
|
||||||
)
|
)
|
||||||
assert result.returncode == 0
|
assert result.returncode == 0
|
||||||
assert 'Auto-detects source type' in result.stdout
|
assert "Auto-detects source type" in result.stdout
|
||||||
assert 'auto-detected' in result.stdout
|
assert "auto-detected" in result.stdout
|
||||||
assert '--help-web' in result.stdout
|
assert "--help-web" in result.stdout
|
||||||
|
|
||||||
def test_create_detects_web_url(self):
|
def test_create_detects_web_url(self):
|
||||||
"""Test that web URLs are detected and routed correctly."""
|
"""Test that web URLs are detected and routed correctly."""
|
||||||
@@ -31,11 +31,12 @@ class TestCreateCommandBasic:
|
|||||||
def test_create_detects_github_repo(self):
|
def test_create_detects_github_repo(self):
|
||||||
"""Test that GitHub repos are detected."""
|
"""Test that GitHub repos are detected."""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'create', 'facebook/react', '--help'],
|
["skill-seekers", "create", "facebook/react", "--help"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=10
|
timeout=10,
|
||||||
)
|
)
|
||||||
# Just verify help works - actual scraping would need API token
|
# Just verify help works - actual scraping would need API token
|
||||||
assert result.returncode in [0, 2] # 0 for success, 2 for argparse help
|
assert result.returncode in [0, 2] # 0 for success, 2 for argparse help
|
||||||
@@ -49,10 +50,10 @@ class TestCreateCommandBasic:
|
|||||||
test_dir.mkdir()
|
test_dir.mkdir()
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'create', str(test_dir), '--help'],
|
["skill-seekers", "create", str(test_dir), "--help"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=10
|
timeout=10,
|
||||||
)
|
)
|
||||||
# Verify help works
|
# Verify help works
|
||||||
assert result.returncode in [0, 2]
|
assert result.returncode in [0, 2]
|
||||||
@@ -66,10 +67,10 @@ class TestCreateCommandBasic:
|
|||||||
pdf_file.touch()
|
pdf_file.touch()
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'create', str(pdf_file), '--help'],
|
["skill-seekers", "create", str(pdf_file), "--help"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=10
|
timeout=10,
|
||||||
)
|
)
|
||||||
# Verify help works
|
# Verify help works
|
||||||
assert result.returncode in [0, 2]
|
assert result.returncode in [0, 2]
|
||||||
@@ -81,17 +82,14 @@ class TestCreateCommandBasic:
|
|||||||
|
|
||||||
# Create a minimal config file
|
# Create a minimal config file
|
||||||
config_file = tmp_path / "test.json"
|
config_file = tmp_path / "test.json"
|
||||||
config_data = {
|
config_data = {"name": "test", "base_url": "https://example.com/"}
|
||||||
"name": "test",
|
|
||||||
"base_url": "https://example.com/"
|
|
||||||
}
|
|
||||||
config_file.write_text(json.dumps(config_data))
|
config_file.write_text(json.dumps(config_data))
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'create', str(config_file), '--help'],
|
["skill-seekers", "create", str(config_file), "--help"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=10
|
timeout=10,
|
||||||
)
|
)
|
||||||
# Verify help works
|
# Verify help works
|
||||||
assert result.returncode in [0, 2]
|
assert result.returncode in [0, 2]
|
||||||
@@ -105,20 +103,19 @@ class TestCreateCommandBasic:
|
|||||||
def test_create_supports_universal_flags(self):
|
def test_create_supports_universal_flags(self):
|
||||||
"""Test that universal flags are accepted."""
|
"""Test that universal flags are accepted."""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'create', '--help'],
|
["skill-seekers", "create", "--help"], capture_output=True, text=True, timeout=10
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10
|
|
||||||
)
|
)
|
||||||
assert result.returncode == 0
|
assert result.returncode == 0
|
||||||
|
|
||||||
# Check that universal flags are present
|
# Check that universal flags are present
|
||||||
assert '--name' in result.stdout
|
assert "--name" in result.stdout
|
||||||
assert '--enhance' in result.stdout
|
assert "--enhance" in result.stdout
|
||||||
assert '--chunk-for-rag' in result.stdout
|
assert "--chunk-for-rag" in result.stdout
|
||||||
assert '--preset' in result.stdout
|
assert "--preset" in result.stdout
|
||||||
assert '--dry-run' in result.stdout
|
assert "--dry-run" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
class TestBackwardCompatibility:
|
class TestBackwardCompatibility:
|
||||||
"""Test that old commands still work."""
|
"""Test that old commands still work."""
|
||||||
@@ -126,53 +123,45 @@ class TestBackwardCompatibility:
|
|||||||
def test_scrape_command_still_works(self):
|
def test_scrape_command_still_works(self):
|
||||||
"""Old scrape command should still function."""
|
"""Old scrape command should still function."""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'scrape', '--help'],
|
["skill-seekers", "scrape", "--help"], capture_output=True, text=True, timeout=10
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10
|
|
||||||
)
|
)
|
||||||
assert result.returncode == 0
|
assert result.returncode == 0
|
||||||
assert 'scrape' in result.stdout.lower()
|
assert "scrape" in result.stdout.lower()
|
||||||
|
|
||||||
def test_github_command_still_works(self):
|
def test_github_command_still_works(self):
|
||||||
"""Old github command should still function."""
|
"""Old github command should still function."""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'github', '--help'],
|
["skill-seekers", "github", "--help"], capture_output=True, text=True, timeout=10
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10
|
|
||||||
)
|
)
|
||||||
assert result.returncode == 0
|
assert result.returncode == 0
|
||||||
assert 'github' in result.stdout.lower()
|
assert "github" in result.stdout.lower()
|
||||||
|
|
||||||
def test_analyze_command_still_works(self):
|
def test_analyze_command_still_works(self):
|
||||||
"""Old analyze command should still function."""
|
"""Old analyze command should still function."""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', 'analyze', '--help'],
|
["skill-seekers", "analyze", "--help"], capture_output=True, text=True, timeout=10
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10
|
|
||||||
)
|
)
|
||||||
assert result.returncode == 0
|
assert result.returncode == 0
|
||||||
assert 'analyze' in result.stdout.lower()
|
assert "analyze" in result.stdout.lower()
|
||||||
|
|
||||||
def test_main_help_shows_all_commands(self):
|
def test_main_help_shows_all_commands(self):
|
||||||
"""Main help should show both old and new commands."""
|
"""Main help should show both old and new commands."""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['skill-seekers', '--help'],
|
["skill-seekers", "--help"], capture_output=True, text=True, timeout=10
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10
|
|
||||||
)
|
)
|
||||||
assert result.returncode == 0
|
assert result.returncode == 0
|
||||||
# Should show create command
|
# Should show create command
|
||||||
assert 'create' in result.stdout
|
assert "create" in result.stdout
|
||||||
|
|
||||||
# Should still show old commands
|
# Should still show old commands
|
||||||
assert 'scrape' in result.stdout
|
assert "scrape" in result.stdout
|
||||||
assert 'github' in result.stdout
|
assert "github" in result.stdout
|
||||||
assert 'analyze' in result.stdout
|
assert "analyze" in result.stdout
|
||||||
|
|||||||
@@ -167,7 +167,8 @@ class TestIssue219Problem2CLIFlags(unittest.TestCase):
|
|||||||
"test/test",
|
"test/test",
|
||||||
"--name",
|
"--name",
|
||||||
"test",
|
"test",
|
||||||
"--enhance-level", "2",
|
"--enhance-level",
|
||||||
|
"2",
|
||||||
]
|
]
|
||||||
|
|
||||||
with (
|
with (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ the same arguments as the standalone scraper modules. This prevents the
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
class TestScrapeParserSync:
|
class TestScrapeParserSync:
|
||||||
"""Ensure scrape_parser has all arguments from doc_scraper."""
|
"""Ensure scrape_parser has all arguments from doc_scraper."""
|
||||||
|
|
||||||
@@ -17,12 +18,12 @@ class TestScrapeParserSync:
|
|||||||
|
|
||||||
# Get source arguments from doc_scraper
|
# Get source arguments from doc_scraper
|
||||||
source_parser = setup_argument_parser()
|
source_parser = setup_argument_parser()
|
||||||
source_count = len([a for a in source_parser._actions if a.dest != 'help'])
|
source_count = len([a for a in source_parser._actions if a.dest != "help"])
|
||||||
|
|
||||||
# Get target arguments from unified CLI parser
|
# Get target arguments from unified CLI parser
|
||||||
target_parser = argparse.ArgumentParser()
|
target_parser = argparse.ArgumentParser()
|
||||||
ScrapeParser().add_arguments(target_parser)
|
ScrapeParser().add_arguments(target_parser)
|
||||||
target_count = len([a for a in target_parser._actions if a.dest != 'help'])
|
target_count = len([a for a in target_parser._actions if a.dest != "help"])
|
||||||
|
|
||||||
assert source_count == target_count, (
|
assert source_count == target_count, (
|
||||||
f"Argument count mismatch: doc_scraper has {source_count}, "
|
f"Argument count mismatch: doc_scraper has {source_count}, "
|
||||||
@@ -36,12 +37,12 @@ class TestScrapeParserSync:
|
|||||||
|
|
||||||
# Get source arguments from doc_scraper
|
# Get source arguments from doc_scraper
|
||||||
source_parser = setup_argument_parser()
|
source_parser = setup_argument_parser()
|
||||||
source_dests = {a.dest for a in source_parser._actions if a.dest != 'help'}
|
source_dests = {a.dest for a in source_parser._actions if a.dest != "help"}
|
||||||
|
|
||||||
# Get target arguments from unified CLI parser
|
# Get target arguments from unified CLI parser
|
||||||
target_parser = argparse.ArgumentParser()
|
target_parser = argparse.ArgumentParser()
|
||||||
ScrapeParser().add_arguments(target_parser)
|
ScrapeParser().add_arguments(target_parser)
|
||||||
target_dests = {a.dest for a in target_parser._actions if a.dest != 'help'}
|
target_dests = {a.dest for a in target_parser._actions if a.dest != "help"}
|
||||||
|
|
||||||
# Check for missing arguments
|
# Check for missing arguments
|
||||||
missing = source_dests - target_dests
|
missing = source_dests - target_dests
|
||||||
@@ -64,27 +65,28 @@ class TestScrapeParserSync:
|
|||||||
break
|
break
|
||||||
|
|
||||||
assert subparsers_action is not None, "No subparsers found"
|
assert subparsers_action is not None, "No subparsers found"
|
||||||
assert 'scrape' in subparsers_action.choices, "scrape subparser not found"
|
assert "scrape" in subparsers_action.choices, "scrape subparser not found"
|
||||||
|
|
||||||
scrape_parser = subparsers_action.choices['scrape']
|
scrape_parser = subparsers_action.choices["scrape"]
|
||||||
arg_dests = {a.dest for a in scrape_parser._actions if a.dest != 'help'}
|
arg_dests = {a.dest for a in scrape_parser._actions if a.dest != "help"}
|
||||||
|
|
||||||
# Check key arguments that were missing in Issue #285
|
# Check key arguments that were missing in Issue #285
|
||||||
required_args = [
|
required_args = [
|
||||||
'interactive',
|
"interactive",
|
||||||
'url',
|
"url",
|
||||||
'verbose',
|
"verbose",
|
||||||
'quiet',
|
"quiet",
|
||||||
'resume',
|
"resume",
|
||||||
'fresh',
|
"fresh",
|
||||||
'rate_limit',
|
"rate_limit",
|
||||||
'no_rate_limit',
|
"no_rate_limit",
|
||||||
'chunk_for_rag',
|
"chunk_for_rag",
|
||||||
]
|
]
|
||||||
|
|
||||||
for arg in required_args:
|
for arg in required_args:
|
||||||
assert arg in arg_dests, f"Required argument '{arg}' missing from scrape parser"
|
assert arg in arg_dests, f"Required argument '{arg}' missing from scrape parser"
|
||||||
|
|
||||||
|
|
||||||
class TestGitHubParserSync:
|
class TestGitHubParserSync:
|
||||||
"""Ensure github_parser has all arguments from github_scraper."""
|
"""Ensure github_parser has all arguments from github_scraper."""
|
||||||
|
|
||||||
@@ -95,12 +97,12 @@ class TestGitHubParserSync:
|
|||||||
|
|
||||||
# Get source arguments from github_scraper
|
# Get source arguments from github_scraper
|
||||||
source_parser = setup_argument_parser()
|
source_parser = setup_argument_parser()
|
||||||
source_count = len([a for a in source_parser._actions if a.dest != 'help'])
|
source_count = len([a for a in source_parser._actions if a.dest != "help"])
|
||||||
|
|
||||||
# Get target arguments from unified CLI parser
|
# Get target arguments from unified CLI parser
|
||||||
target_parser = argparse.ArgumentParser()
|
target_parser = argparse.ArgumentParser()
|
||||||
GitHubParser().add_arguments(target_parser)
|
GitHubParser().add_arguments(target_parser)
|
||||||
target_count = len([a for a in target_parser._actions if a.dest != 'help'])
|
target_count = len([a for a in target_parser._actions if a.dest != "help"])
|
||||||
|
|
||||||
assert source_count == target_count, (
|
assert source_count == target_count, (
|
||||||
f"Argument count mismatch: github_scraper has {source_count}, "
|
f"Argument count mismatch: github_scraper has {source_count}, "
|
||||||
@@ -114,12 +116,12 @@ class TestGitHubParserSync:
|
|||||||
|
|
||||||
# Get source arguments from github_scraper
|
# Get source arguments from github_scraper
|
||||||
source_parser = setup_argument_parser()
|
source_parser = setup_argument_parser()
|
||||||
source_dests = {a.dest for a in source_parser._actions if a.dest != 'help'}
|
source_dests = {a.dest for a in source_parser._actions if a.dest != "help"}
|
||||||
|
|
||||||
# Get target arguments from unified CLI parser
|
# Get target arguments from unified CLI parser
|
||||||
target_parser = argparse.ArgumentParser()
|
target_parser = argparse.ArgumentParser()
|
||||||
GitHubParser().add_arguments(target_parser)
|
GitHubParser().add_arguments(target_parser)
|
||||||
target_dests = {a.dest for a in target_parser._actions if a.dest != 'help'}
|
target_dests = {a.dest for a in target_parser._actions if a.dest != "help"}
|
||||||
|
|
||||||
# Check for missing arguments
|
# Check for missing arguments
|
||||||
missing = source_dests - target_dests
|
missing = source_dests - target_dests
|
||||||
@@ -128,6 +130,7 @@ class TestGitHubParserSync:
|
|||||||
assert not missing, f"github_parser missing arguments: {missing}"
|
assert not missing, f"github_parser missing arguments: {missing}"
|
||||||
assert not extra, f"github_parser has extra arguments not in github_scraper: {extra}"
|
assert not extra, f"github_parser has extra arguments not in github_scraper: {extra}"
|
||||||
|
|
||||||
|
|
||||||
class TestUnifiedCLI:
|
class TestUnifiedCLI:
|
||||||
"""Test the unified CLI main parser."""
|
"""Test the unified CLI main parser."""
|
||||||
|
|
||||||
@@ -154,7 +157,7 @@ class TestUnifiedCLI:
|
|||||||
assert subparsers_action is not None, "No subparsers found"
|
assert subparsers_action is not None, "No subparsers found"
|
||||||
|
|
||||||
# Check expected subcommands
|
# Check expected subcommands
|
||||||
expected_commands = ['scrape', 'github']
|
expected_commands = ["scrape", "github"]
|
||||||
for cmd in expected_commands:
|
for cmd in expected_commands:
|
||||||
assert cmd in subparsers_action.choices, f"Subcommand '{cmd}' not found"
|
assert cmd in subparsers_action.choices, f"Subcommand '{cmd}' not found"
|
||||||
|
|
||||||
@@ -166,7 +169,7 @@ class TestUnifiedCLI:
|
|||||||
|
|
||||||
# This should not raise an exception
|
# This should not raise an exception
|
||||||
try:
|
try:
|
||||||
parser.parse_args(['scrape', '--help'])
|
parser.parse_args(["scrape", "--help"])
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
# --help causes SystemExit(0) which is expected
|
# --help causes SystemExit(0) which is expected
|
||||||
assert e.code == 0
|
assert e.code == 0
|
||||||
@@ -179,7 +182,7 @@ class TestUnifiedCLI:
|
|||||||
|
|
||||||
# This should not raise an exception
|
# This should not raise an exception
|
||||||
try:
|
try:
|
||||||
parser.parse_args(['github', '--help'])
|
parser.parse_args(["github", "--help"])
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
# --help causes SystemExit(0) which is expected
|
# --help causes SystemExit(0) which is expected
|
||||||
assert e.code == 0
|
assert e.code == 0
|
||||||
|
|||||||
@@ -13,47 +13,49 @@ import pytest
|
|||||||
|
|
||||||
from skill_seekers.cli.source_detector import SourceDetector, SourceInfo
|
from skill_seekers.cli.source_detector import SourceDetector, SourceInfo
|
||||||
|
|
||||||
|
|
||||||
class TestWebDetection:
|
class TestWebDetection:
|
||||||
"""Test web URL detection."""
|
"""Test web URL detection."""
|
||||||
|
|
||||||
def test_detect_full_https_url(self):
|
def test_detect_full_https_url(self):
|
||||||
"""Full HTTPS URL should be detected as web."""
|
"""Full HTTPS URL should be detected as web."""
|
||||||
info = SourceDetector.detect("https://docs.react.dev/")
|
info = SourceDetector.detect("https://docs.react.dev/")
|
||||||
assert info.type == 'web'
|
assert info.type == "web"
|
||||||
assert info.parsed['url'] == "https://docs.react.dev/"
|
assert info.parsed["url"] == "https://docs.react.dev/"
|
||||||
assert info.suggested_name == 'react'
|
assert info.suggested_name == "react"
|
||||||
|
|
||||||
def test_detect_full_http_url(self):
|
def test_detect_full_http_url(self):
|
||||||
"""Full HTTP URL should be detected as web."""
|
"""Full HTTP URL should be detected as web."""
|
||||||
info = SourceDetector.detect("http://example.com/docs")
|
info = SourceDetector.detect("http://example.com/docs")
|
||||||
assert info.type == 'web'
|
assert info.type == "web"
|
||||||
assert info.parsed['url'] == "http://example.com/docs"
|
assert info.parsed["url"] == "http://example.com/docs"
|
||||||
|
|
||||||
def test_detect_domain_only(self):
|
def test_detect_domain_only(self):
|
||||||
"""Domain without protocol should add https:// and detect as web."""
|
"""Domain without protocol should add https:// and detect as web."""
|
||||||
info = SourceDetector.detect("docs.react.dev")
|
info = SourceDetector.detect("docs.react.dev")
|
||||||
assert info.type == 'web'
|
assert info.type == "web"
|
||||||
assert info.parsed['url'] == "https://docs.react.dev"
|
assert info.parsed["url"] == "https://docs.react.dev"
|
||||||
assert info.suggested_name == 'react'
|
assert info.suggested_name == "react"
|
||||||
|
|
||||||
def test_detect_complex_url(self):
|
def test_detect_complex_url(self):
|
||||||
"""Complex URL with path should be detected as web."""
|
"""Complex URL with path should be detected as web."""
|
||||||
info = SourceDetector.detect("https://docs.python.org/3/library/")
|
info = SourceDetector.detect("https://docs.python.org/3/library/")
|
||||||
assert info.type == 'web'
|
assert info.type == "web"
|
||||||
assert info.parsed['url'] == "https://docs.python.org/3/library/"
|
assert info.parsed["url"] == "https://docs.python.org/3/library/"
|
||||||
assert info.suggested_name == 'python'
|
assert info.suggested_name == "python"
|
||||||
|
|
||||||
def test_suggested_name_removes_www(self):
|
def test_suggested_name_removes_www(self):
|
||||||
"""Should remove www. prefix from suggested name."""
|
"""Should remove www. prefix from suggested name."""
|
||||||
info = SourceDetector.detect("https://www.example.com/")
|
info = SourceDetector.detect("https://www.example.com/")
|
||||||
assert info.type == 'web'
|
assert info.type == "web"
|
||||||
assert info.suggested_name == 'example'
|
assert info.suggested_name == "example"
|
||||||
|
|
||||||
def test_suggested_name_removes_docs(self):
|
def test_suggested_name_removes_docs(self):
|
||||||
"""Should remove docs. prefix from suggested name."""
|
"""Should remove docs. prefix from suggested name."""
|
||||||
info = SourceDetector.detect("https://docs.vue.org/")
|
info = SourceDetector.detect("https://docs.vue.org/")
|
||||||
assert info.type == 'web'
|
assert info.type == "web"
|
||||||
assert info.suggested_name == 'vue'
|
assert info.suggested_name == "vue"
|
||||||
|
|
||||||
|
|
||||||
class TestGitHubDetection:
|
class TestGitHubDetection:
|
||||||
"""Test GitHub repository detection."""
|
"""Test GitHub repository detection."""
|
||||||
@@ -61,37 +63,38 @@ class TestGitHubDetection:
|
|||||||
def test_detect_owner_repo_format(self):
|
def test_detect_owner_repo_format(self):
|
||||||
"""owner/repo format should be detected as GitHub."""
|
"""owner/repo format should be detected as GitHub."""
|
||||||
info = SourceDetector.detect("facebook/react")
|
info = SourceDetector.detect("facebook/react")
|
||||||
assert info.type == 'github'
|
assert info.type == "github"
|
||||||
assert info.parsed['repo'] == "facebook/react"
|
assert info.parsed["repo"] == "facebook/react"
|
||||||
assert info.suggested_name == 'react'
|
assert info.suggested_name == "react"
|
||||||
|
|
||||||
def test_detect_github_https_url(self):
|
def test_detect_github_https_url(self):
|
||||||
"""Full GitHub HTTPS URL should be detected."""
|
"""Full GitHub HTTPS URL should be detected."""
|
||||||
info = SourceDetector.detect("https://github.com/facebook/react")
|
info = SourceDetector.detect("https://github.com/facebook/react")
|
||||||
assert info.type == 'github'
|
assert info.type == "github"
|
||||||
assert info.parsed['repo'] == "facebook/react"
|
assert info.parsed["repo"] == "facebook/react"
|
||||||
assert info.suggested_name == 'react'
|
assert info.suggested_name == "react"
|
||||||
|
|
||||||
def test_detect_github_url_with_git_suffix(self):
|
def test_detect_github_url_with_git_suffix(self):
|
||||||
"""GitHub URL with .git should strip suffix."""
|
"""GitHub URL with .git should strip suffix."""
|
||||||
info = SourceDetector.detect("https://github.com/facebook/react.git")
|
info = SourceDetector.detect("https://github.com/facebook/react.git")
|
||||||
assert info.type == 'github'
|
assert info.type == "github"
|
||||||
assert info.parsed['repo'] == "facebook/react"
|
assert info.parsed["repo"] == "facebook/react"
|
||||||
assert info.suggested_name == 'react'
|
assert info.suggested_name == "react"
|
||||||
|
|
||||||
def test_detect_github_url_without_protocol(self):
|
def test_detect_github_url_without_protocol(self):
|
||||||
"""GitHub URL without protocol should be detected."""
|
"""GitHub URL without protocol should be detected."""
|
||||||
info = SourceDetector.detect("github.com/vuejs/vue")
|
info = SourceDetector.detect("github.com/vuejs/vue")
|
||||||
assert info.type == 'github'
|
assert info.type == "github"
|
||||||
assert info.parsed['repo'] == "vuejs/vue"
|
assert info.parsed["repo"] == "vuejs/vue"
|
||||||
assert info.suggested_name == 'vue'
|
assert info.suggested_name == "vue"
|
||||||
|
|
||||||
def test_owner_repo_with_dots_and_dashes(self):
|
def test_owner_repo_with_dots_and_dashes(self):
|
||||||
"""Repo names with dots and dashes should work."""
|
"""Repo names with dots and dashes should work."""
|
||||||
info = SourceDetector.detect("microsoft/vscode-python")
|
info = SourceDetector.detect("microsoft/vscode-python")
|
||||||
assert info.type == 'github'
|
assert info.type == "github"
|
||||||
assert info.parsed['repo'] == "microsoft/vscode-python"
|
assert info.parsed["repo"] == "microsoft/vscode-python"
|
||||||
assert info.suggested_name == 'vscode-python'
|
assert info.suggested_name == "vscode-python"
|
||||||
|
|
||||||
|
|
||||||
class TestLocalDetection:
|
class TestLocalDetection:
|
||||||
"""Test local directory detection."""
|
"""Test local directory detection."""
|
||||||
@@ -107,9 +110,9 @@ class TestLocalDetection:
|
|||||||
try:
|
try:
|
||||||
os.chdir(tmp_path)
|
os.chdir(tmp_path)
|
||||||
info = SourceDetector.detect("./my_project")
|
info = SourceDetector.detect("./my_project")
|
||||||
assert info.type == 'local'
|
assert info.type == "local"
|
||||||
assert 'my_project' in info.parsed['directory']
|
assert "my_project" in info.parsed["directory"]
|
||||||
assert info.suggested_name == 'my_project'
|
assert info.suggested_name == "my_project"
|
||||||
finally:
|
finally:
|
||||||
os.chdir(original_cwd)
|
os.chdir(original_cwd)
|
||||||
|
|
||||||
@@ -120,16 +123,17 @@ class TestLocalDetection:
|
|||||||
test_dir.mkdir()
|
test_dir.mkdir()
|
||||||
|
|
||||||
info = SourceDetector.detect(str(test_dir))
|
info = SourceDetector.detect(str(test_dir))
|
||||||
assert info.type == 'local'
|
assert info.type == "local"
|
||||||
assert info.parsed['directory'] == str(test_dir.resolve())
|
assert info.parsed["directory"] == str(test_dir.resolve())
|
||||||
assert info.suggested_name == 'test_repo'
|
assert info.suggested_name == "test_repo"
|
||||||
|
|
||||||
def test_detect_current_directory(self):
|
def test_detect_current_directory(self):
|
||||||
"""Current directory (.) should be detected."""
|
"""Current directory (.) should be detected."""
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
info = SourceDetector.detect(".")
|
info = SourceDetector.detect(".")
|
||||||
assert info.type == 'local'
|
assert info.type == "local"
|
||||||
assert info.parsed['directory'] == cwd
|
assert info.parsed["directory"] == cwd
|
||||||
|
|
||||||
|
|
||||||
class TestPDFDetection:
|
class TestPDFDetection:
|
||||||
"""Test PDF file detection."""
|
"""Test PDF file detection."""
|
||||||
@@ -137,22 +141,23 @@ class TestPDFDetection:
|
|||||||
def test_detect_pdf_extension(self):
|
def test_detect_pdf_extension(self):
|
||||||
"""File with .pdf extension should be detected."""
|
"""File with .pdf extension should be detected."""
|
||||||
info = SourceDetector.detect("tutorial.pdf")
|
info = SourceDetector.detect("tutorial.pdf")
|
||||||
assert info.type == 'pdf'
|
assert info.type == "pdf"
|
||||||
assert info.parsed['file_path'] == "tutorial.pdf"
|
assert info.parsed["file_path"] == "tutorial.pdf"
|
||||||
assert info.suggested_name == 'tutorial'
|
assert info.suggested_name == "tutorial"
|
||||||
|
|
||||||
def test_detect_pdf_with_path(self):
|
def test_detect_pdf_with_path(self):
|
||||||
"""PDF file with path should be detected."""
|
"""PDF file with path should be detected."""
|
||||||
info = SourceDetector.detect("/path/to/guide.pdf")
|
info = SourceDetector.detect("/path/to/guide.pdf")
|
||||||
assert info.type == 'pdf'
|
assert info.type == "pdf"
|
||||||
assert info.parsed['file_path'] == "/path/to/guide.pdf"
|
assert info.parsed["file_path"] == "/path/to/guide.pdf"
|
||||||
assert info.suggested_name == 'guide'
|
assert info.suggested_name == "guide"
|
||||||
|
|
||||||
def test_suggested_name_removes_pdf_extension(self):
|
def test_suggested_name_removes_pdf_extension(self):
|
||||||
"""Suggested name should not include .pdf extension."""
|
"""Suggested name should not include .pdf extension."""
|
||||||
info = SourceDetector.detect("my-awesome-guide.pdf")
|
info = SourceDetector.detect("my-awesome-guide.pdf")
|
||||||
assert info.type == 'pdf'
|
assert info.type == "pdf"
|
||||||
assert info.suggested_name == 'my-awesome-guide'
|
assert info.suggested_name == "my-awesome-guide"
|
||||||
|
|
||||||
|
|
||||||
class TestConfigDetection:
|
class TestConfigDetection:
|
||||||
"""Test config file detection."""
|
"""Test config file detection."""
|
||||||
@@ -160,16 +165,17 @@ class TestConfigDetection:
|
|||||||
def test_detect_json_extension(self):
|
def test_detect_json_extension(self):
|
||||||
"""File with .json extension should be detected as config."""
|
"""File with .json extension should be detected as config."""
|
||||||
info = SourceDetector.detect("react.json")
|
info = SourceDetector.detect("react.json")
|
||||||
assert info.type == 'config'
|
assert info.type == "config"
|
||||||
assert info.parsed['config_path'] == "react.json"
|
assert info.parsed["config_path"] == "react.json"
|
||||||
assert info.suggested_name == 'react'
|
assert info.suggested_name == "react"
|
||||||
|
|
||||||
def test_detect_config_with_path(self):
|
def test_detect_config_with_path(self):
|
||||||
"""Config file with path should be detected."""
|
"""Config file with path should be detected."""
|
||||||
info = SourceDetector.detect("configs/django.json")
|
info = SourceDetector.detect("configs/django.json")
|
||||||
assert info.type == 'config'
|
assert info.type == "config"
|
||||||
assert info.parsed['config_path'] == "configs/django.json"
|
assert info.parsed["config_path"] == "configs/django.json"
|
||||||
assert info.suggested_name == 'django'
|
assert info.suggested_name == "django"
|
||||||
|
|
||||||
|
|
||||||
class TestValidation:
|
class TestValidation:
|
||||||
"""Test source validation."""
|
"""Test source validation."""
|
||||||
@@ -191,10 +197,10 @@ class TestValidation:
|
|||||||
# First try to detect it (will succeed since it looks like a path)
|
# First try to detect it (will succeed since it looks like a path)
|
||||||
with pytest.raises(ValueError, match="Directory does not exist"):
|
with pytest.raises(ValueError, match="Directory does not exist"):
|
||||||
info = SourceInfo(
|
info = SourceInfo(
|
||||||
type='local',
|
type="local",
|
||||||
parsed={'directory': nonexistent},
|
parsed={"directory": nonexistent},
|
||||||
suggested_name='test',
|
suggested_name="test",
|
||||||
raw_input=nonexistent
|
raw_input=nonexistent,
|
||||||
)
|
)
|
||||||
SourceDetector.validate_source(info)
|
SourceDetector.validate_source(info)
|
||||||
|
|
||||||
@@ -211,10 +217,10 @@ class TestValidation:
|
|||||||
"""Validation should fail for nonexistent PDF."""
|
"""Validation should fail for nonexistent PDF."""
|
||||||
with pytest.raises(ValueError, match="PDF file does not exist"):
|
with pytest.raises(ValueError, match="PDF file does not exist"):
|
||||||
info = SourceInfo(
|
info = SourceInfo(
|
||||||
type='pdf',
|
type="pdf",
|
||||||
parsed={'file_path': '/tmp/nonexistent.pdf'},
|
parsed={"file_path": "/tmp/nonexistent.pdf"},
|
||||||
suggested_name='test',
|
suggested_name="test",
|
||||||
raw_input='/tmp/nonexistent.pdf'
|
raw_input="/tmp/nonexistent.pdf",
|
||||||
)
|
)
|
||||||
SourceDetector.validate_source(info)
|
SourceDetector.validate_source(info)
|
||||||
|
|
||||||
@@ -231,13 +237,14 @@ class TestValidation:
|
|||||||
"""Validation should fail for nonexistent config."""
|
"""Validation should fail for nonexistent config."""
|
||||||
with pytest.raises(ValueError, match="Config file does not exist"):
|
with pytest.raises(ValueError, match="Config file does not exist"):
|
||||||
info = SourceInfo(
|
info = SourceInfo(
|
||||||
type='config',
|
type="config",
|
||||||
parsed={'config_path': '/tmp/nonexistent.json'},
|
parsed={"config_path": "/tmp/nonexistent.json"},
|
||||||
suggested_name='test',
|
suggested_name="test",
|
||||||
raw_input='/tmp/nonexistent.json'
|
raw_input="/tmp/nonexistent.json",
|
||||||
)
|
)
|
||||||
SourceDetector.validate_source(info)
|
SourceDetector.validate_source(info)
|
||||||
|
|
||||||
|
|
||||||
class TestAmbiguousCases:
|
class TestAmbiguousCases:
|
||||||
"""Test handling of ambiguous inputs."""
|
"""Test handling of ambiguous inputs."""
|
||||||
|
|
||||||
@@ -255,8 +262,8 @@ class TestAmbiguousCases:
|
|||||||
"""GitHub URL should be detected as github, not web."""
|
"""GitHub URL should be detected as github, not web."""
|
||||||
# Even though this is a URL, it should be detected as GitHub
|
# Even though this is a URL, it should be detected as GitHub
|
||||||
info = SourceDetector.detect("https://github.com/owner/repo")
|
info = SourceDetector.detect("https://github.com/owner/repo")
|
||||||
assert info.type == 'github'
|
assert info.type == "github"
|
||||||
assert info.parsed['repo'] == "owner/repo"
|
assert info.parsed["repo"] == "owner/repo"
|
||||||
|
|
||||||
def test_directory_takes_precedence_over_domain(self, tmp_path):
|
def test_directory_takes_precedence_over_domain(self, tmp_path):
|
||||||
"""Existing directory should be detected even if it looks like domain."""
|
"""Existing directory should be detected even if it looks like domain."""
|
||||||
@@ -266,7 +273,8 @@ class TestAmbiguousCases:
|
|||||||
|
|
||||||
info = SourceDetector.detect(str(dir_like_domain))
|
info = SourceDetector.detect(str(dir_like_domain))
|
||||||
# Should detect as local directory, not web
|
# Should detect as local directory, not web
|
||||||
assert info.type == 'local'
|
assert info.type == "local"
|
||||||
|
|
||||||
|
|
||||||
class TestRawInputPreservation:
|
class TestRawInputPreservation:
|
||||||
"""Test that raw_input is preserved correctly."""
|
"""Test that raw_input is preserved correctly."""
|
||||||
@@ -292,6 +300,7 @@ class TestRawInputPreservation:
|
|||||||
info = SourceDetector.detect(original)
|
info = SourceDetector.detect(original)
|
||||||
assert info.raw_input == original
|
assert info.raw_input == original
|
||||||
|
|
||||||
|
|
||||||
class TestEdgeCases:
|
class TestEdgeCases:
|
||||||
"""Test edge cases and corner cases."""
|
"""Test edge cases and corner cases."""
|
||||||
|
|
||||||
@@ -300,19 +309,19 @@ class TestEdgeCases:
|
|||||||
info1 = SourceDetector.detect("https://docs.react.dev/")
|
info1 = SourceDetector.detect("https://docs.react.dev/")
|
||||||
info2 = SourceDetector.detect("https://docs.react.dev")
|
info2 = SourceDetector.detect("https://docs.react.dev")
|
||||||
|
|
||||||
assert info1.type == 'web'
|
assert info1.type == "web"
|
||||||
assert info2.type == 'web'
|
assert info2.type == "web"
|
||||||
|
|
||||||
def test_uppercase_in_github_repo(self):
|
def test_uppercase_in_github_repo(self):
|
||||||
"""GitHub repos with uppercase should be detected."""
|
"""GitHub repos with uppercase should be detected."""
|
||||||
info = SourceDetector.detect("Microsoft/TypeScript")
|
info = SourceDetector.detect("Microsoft/TypeScript")
|
||||||
assert info.type == 'github'
|
assert info.type == "github"
|
||||||
assert info.parsed['repo'] == "Microsoft/TypeScript"
|
assert info.parsed["repo"] == "Microsoft/TypeScript"
|
||||||
|
|
||||||
def test_numbers_in_repo_name(self):
|
def test_numbers_in_repo_name(self):
|
||||||
"""GitHub repos with numbers should be detected."""
|
"""GitHub repos with numbers should be detected."""
|
||||||
info = SourceDetector.detect("python/cpython3.11")
|
info = SourceDetector.detect("python/cpython3.11")
|
||||||
assert info.type == 'github'
|
assert info.type == "github"
|
||||||
|
|
||||||
def test_nested_directory_path(self, tmp_path):
|
def test_nested_directory_path(self, tmp_path):
|
||||||
"""Nested directory paths should work."""
|
"""Nested directory paths should work."""
|
||||||
@@ -320,5 +329,5 @@ class TestEdgeCases:
|
|||||||
nested.mkdir(parents=True)
|
nested.mkdir(parents=True)
|
||||||
|
|
||||||
info = SourceDetector.detect(str(nested))
|
info = SourceDetector.detect(str(nested))
|
||||||
assert info.type == 'local'
|
assert info.type == "local"
|
||||||
assert info.suggested_name == 'c'
|
assert info.suggested_name == "c"
|
||||||
|
|||||||
Reference in New Issue
Block a user