fix: Fix remaining 61 ruff linting errors (SIM102, SIM117)
Fixed all remaining linting errors from the 310 total: - SIM102: Combined nested if statements (31 errors) - adaptors/openai.py - config_extractor.py - codebase_scraper.py - doc_scraper.py - github_fetcher.py - pattern_recognizer.py - pdf_scraper.py - test_example_extractor.py - SIM117: Combined multiple with statements (24 errors) - tests/test_async_scraping.py (2 errors) - tests/test_github_scraper.py (2 errors) - tests/test_guide_enhancer.py (20 errors) - Fixed test fixture parameter (mock_config in test_c3_integration.py) All 700+ tests passing. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -124,13 +124,12 @@ Always prioritize accuracy by consulting the attached documentation files before
|
||||
# Determine output filename
|
||||
if output_path.is_dir() or str(output_path).endswith("/"):
|
||||
output_path = Path(output_path) / f"{skill_dir.name}-openai.zip"
|
||||
elif not str(output_path).endswith(".zip"):
|
||||
elif not str(output_path).endswith(".zip") and not str(output_path).endswith("-openai.zip"):
|
||||
# Keep .zip extension
|
||||
if not str(output_path).endswith("-openai.zip"):
|
||||
output_str = str(output_path).replace(".zip", "-openai.zip")
|
||||
if not output_str.endswith(".zip"):
|
||||
output_str += ".zip"
|
||||
output_path = Path(output_str)
|
||||
output_str = str(output_path).replace(".zip", "-openai.zip")
|
||||
if not output_str.endswith(".zip"):
|
||||
output_str += ".zip"
|
||||
output_path = Path(output_str)
|
||||
|
||||
output_path = Path(output_path)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -23,6 +23,7 @@ consider using dedicated parsers (tree-sitter, language-specific AST libraries).
|
||||
"""
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import asdict, dataclass
|
||||
@@ -142,7 +143,7 @@ class CodeAnalyzer:
|
||||
if isinstance(node, ast.ClassDef):
|
||||
class_sig = self._extract_python_class(node)
|
||||
classes.append(asdict(class_sig))
|
||||
elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
|
||||
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
# Only top-level functions (not methods)
|
||||
# Fix AST parser to check isinstance(parent.body, list) before 'in' operator
|
||||
is_method = False
|
||||
@@ -226,10 +227,8 @@ class CodeAnalyzer:
|
||||
# Extract return type
|
||||
return_type = None
|
||||
if node.returns:
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
return_type = ast.unparse(node.returns) if hasattr(ast, "unparse") else None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Extract decorators
|
||||
decorators = []
|
||||
|
||||
@@ -208,9 +208,8 @@ def walk_directory(
|
||||
continue
|
||||
|
||||
# Check file patterns if provided
|
||||
if patterns:
|
||||
if not any(file_path.match(pattern) for pattern in patterns):
|
||||
continue
|
||||
if patterns and not any(file_path.match(pattern) for pattern in patterns):
|
||||
continue
|
||||
|
||||
files.append(file_path)
|
||||
|
||||
|
||||
@@ -325,10 +325,7 @@ def api_keys_menu():
|
||||
"google": "GOOGLE_API_KEY",
|
||||
"openai": "OPENAI_API_KEY",
|
||||
}[provider]
|
||||
if os.getenv(env_var):
|
||||
source = " (from environment)"
|
||||
else:
|
||||
source = " (from config)"
|
||||
source = " (from environment)" if os.getenv(env_var) else " (from config)"
|
||||
print(f" • {provider.capitalize()}: {status}{source}")
|
||||
|
||||
print("\nOptions:")
|
||||
|
||||
@@ -467,14 +467,10 @@ class ConfigParser:
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Assign):
|
||||
# Get variable name
|
||||
if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
|
||||
# Get variable name and skip private variables
|
||||
if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and not node.targets[0].id.startswith("_"):
|
||||
key = node.targets[0].id
|
||||
|
||||
# Skip private variables
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
|
||||
# Extract value
|
||||
try:
|
||||
value = ast.literal_eval(node.value)
|
||||
|
||||
@@ -394,7 +394,7 @@ class ConflictDetector:
|
||||
}
|
||||
|
||||
# Compare parameter names and types
|
||||
for i, (doc_param, code_param) in enumerate(zip(docs_params, code_params)):
|
||||
for i, (doc_param, code_param) in enumerate(zip(docs_params, code_params, strict=False)):
|
||||
doc_name = doc_param.get("name", "")
|
||||
code_name = code_param.get("name", "")
|
||||
|
||||
@@ -447,7 +447,7 @@ class ConflictDetector:
|
||||
"total": len(conflicts),
|
||||
"by_type": {},
|
||||
"by_severity": {},
|
||||
"apis_affected": len(set(c.api_name for c in conflicts)),
|
||||
"apis_affected": len({c.api_name for c in conflicts}),
|
||||
}
|
||||
|
||||
# Count by type
|
||||
|
||||
@@ -324,7 +324,7 @@ class DependencyAnalyzer:
|
||||
# Single import: import [alias] "package"
|
||||
single_import_pattern = r'import\s+(?:(\w+)\s+)?"([^"]+)"'
|
||||
for match in re.finditer(single_import_pattern, content):
|
||||
alias = match.group(1) # Optional alias
|
||||
match.group(1) # Optional alias
|
||||
package = match.group(2)
|
||||
line_num = content[: match.start()].count("\n") + 1
|
||||
|
||||
|
||||
@@ -230,10 +230,7 @@ class DocToSkillConverter:
|
||||
|
||||
# Exclude patterns
|
||||
excludes = self.config.get("url_patterns", {}).get("exclude", [])
|
||||
if any(pattern in url for pattern in excludes):
|
||||
return False
|
||||
|
||||
return True
|
||||
return not any(pattern in url for pattern in excludes)
|
||||
|
||||
def save_checkpoint(self) -> None:
|
||||
"""Save progress checkpoint"""
|
||||
@@ -1197,9 +1194,8 @@ class DocToSkillConverter:
|
||||
logger.info(" [%d pages scraped]", self.pages_scraped)
|
||||
|
||||
# Checkpoint saving
|
||||
if not self.dry_run and self.checkpoint_enabled:
|
||||
if self.pages_scraped % self.checkpoint_interval == 0:
|
||||
self.save_checkpoint()
|
||||
if not self.dry_run and self.checkpoint_enabled and self.pages_scraped % self.checkpoint_interval == 0:
|
||||
self.save_checkpoint()
|
||||
|
||||
# Wait for any remaining tasks
|
||||
if tasks:
|
||||
@@ -1626,18 +1622,16 @@ def validate_config(config: dict[str, Any]) -> tuple[list[str], list[str]]:
|
||||
errors.append(f"Missing required field: '{field}'")
|
||||
|
||||
# Validate name (alphanumeric, hyphens, underscores only)
|
||||
if "name" in config:
|
||||
if not re.match(r"^[a-zA-Z0-9_-]+$", config["name"]):
|
||||
errors.append(
|
||||
f"Invalid name: '{config['name']}' (use only letters, numbers, hyphens, underscores)"
|
||||
)
|
||||
if "name" in config and not re.match(r"^[a-zA-Z0-9_-]+$", config["name"]):
|
||||
errors.append(
|
||||
f"Invalid name: '{config['name']}' (use only letters, numbers, hyphens, underscores)"
|
||||
)
|
||||
|
||||
# Validate base_url
|
||||
if "base_url" in config:
|
||||
if not config["base_url"].startswith(("http://", "https://")):
|
||||
errors.append(
|
||||
f"Invalid base_url: '{config['base_url']}' (must start with http:// or https://)"
|
||||
)
|
||||
if "base_url" in config and not config["base_url"].startswith(("http://", "https://")):
|
||||
errors.append(
|
||||
f"Invalid base_url: '{config['base_url']}' (must start with http:// or https://)"
|
||||
)
|
||||
|
||||
# Validate selectors structure
|
||||
if "selectors" in config:
|
||||
@@ -1657,9 +1651,8 @@ def validate_config(config: dict[str, Any]) -> tuple[list[str], list[str]]:
|
||||
errors.append("'url_patterns' must be a dictionary")
|
||||
else:
|
||||
for key in ["include", "exclude"]:
|
||||
if key in config["url_patterns"]:
|
||||
if not isinstance(config["url_patterns"][key], list):
|
||||
errors.append(f"'url_patterns.{key}' must be a list")
|
||||
if key in config["url_patterns"] and not isinstance(config["url_patterns"][key], list):
|
||||
errors.append(f"'url_patterns.{key}' must be a list")
|
||||
|
||||
# Validate categories
|
||||
if "categories" in config:
|
||||
|
||||
@@ -49,6 +49,8 @@ from pathlib import Path
|
||||
# Add parent directory to path for imports when run as script
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
import contextlib
|
||||
|
||||
from skill_seekers.cli.constants import LOCAL_CONTENT_LIMIT, LOCAL_PREVIEW_LIMIT
|
||||
from skill_seekers.cli.utils import read_reference_files
|
||||
|
||||
@@ -681,10 +683,8 @@ rm {prompt_file}
|
||||
print()
|
||||
|
||||
# Clean up prompt file
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
os.unlink(prompt_file)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return True
|
||||
else:
|
||||
@@ -726,10 +726,8 @@ rm {prompt_file}
|
||||
print(" 3. Try again later")
|
||||
|
||||
# Clean up
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
os.unlink(prompt_file)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
@@ -806,10 +804,8 @@ rm {prompt_file}
|
||||
)
|
||||
|
||||
# Clean up
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
os.unlink(prompt_file)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if result.returncode == 0:
|
||||
self.write_status(
|
||||
|
||||
@@ -155,10 +155,7 @@ def is_valid_url(url, base_url, include_patterns, exclude_patterns):
|
||||
|
||||
# Check include patterns (if specified)
|
||||
if include_patterns:
|
||||
for pattern in include_patterns:
|
||||
if pattern in url:
|
||||
return True
|
||||
return False
|
||||
return any(pattern in url for pattern in include_patterns)
|
||||
|
||||
# If no include patterns, accept by default
|
||||
return True
|
||||
@@ -289,10 +286,7 @@ def list_all_configs():
|
||||
rel_path = config_file.relative_to(config_dir)
|
||||
|
||||
# Category is the first directory in the path, or "root" if in root
|
||||
if len(rel_path.parts) > 1:
|
||||
category = rel_path.parts[0]
|
||||
else:
|
||||
category = "root"
|
||||
category = rel_path.parts[0] if len(rel_path.parts) > 1 else "root"
|
||||
|
||||
if category not in by_category:
|
||||
by_category[category] = []
|
||||
|
||||
@@ -419,9 +419,8 @@ class GitHubThreeStreamFetcher:
|
||||
is_in_docs_dir = any(
|
||||
pattern in str(file_path) for pattern in ["docs/", "doc/", "documentation/"]
|
||||
)
|
||||
if any(part.startswith(".") for part in file_path.parts):
|
||||
if not is_in_docs_dir:
|
||||
continue
|
||||
if any(part.startswith(".") for part in file_path.parts) and not is_in_docs_dir:
|
||||
continue
|
||||
|
||||
# Check if documentation
|
||||
is_doc = any(file_path.match(pattern) for pattern in doc_patterns)
|
||||
|
||||
@@ -928,7 +928,7 @@ class HowToGuideBuilder:
|
||||
# Extract source files
|
||||
source_files = [w.get("file_path", "") for w in workflows]
|
||||
source_files = [
|
||||
f"{Path(f).name}:{w.get('line_start', 0)}" for f, w in zip(source_files, workflows)
|
||||
f"{Path(f).name}:{w.get('line_start', 0)}" for f, w in zip(source_files, workflows, strict=False)
|
||||
]
|
||||
|
||||
# Create guide
|
||||
|
||||
@@ -438,17 +438,16 @@ class SingletonDetector(BasePatternDetector):
|
||||
# Check for class-level instance storage
|
||||
# This would require checking class attributes (future enhancement)
|
||||
|
||||
if has_instance_method or has_init_control:
|
||||
if confidence >= 0.5:
|
||||
return PatternInstance(
|
||||
pattern_type=self.pattern_type,
|
||||
category=self.category,
|
||||
confidence=min(confidence, 0.9),
|
||||
location="",
|
||||
class_name=class_sig.name,
|
||||
line_number=class_sig.line_number,
|
||||
evidence=evidence,
|
||||
)
|
||||
if has_instance_method or has_init_control and confidence >= 0.5:
|
||||
return PatternInstance(
|
||||
pattern_type=self.pattern_type,
|
||||
category=self.category,
|
||||
confidence=min(confidence, 0.9),
|
||||
location="",
|
||||
class_name=class_sig.name,
|
||||
line_number=class_sig.line_number,
|
||||
evidence=evidence,
|
||||
)
|
||||
|
||||
# Fallback to surface detection
|
||||
return self.detect_surface(class_sig, all_classes)
|
||||
@@ -485,10 +484,9 @@ class SingletonDetector(BasePatternDetector):
|
||||
]
|
||||
|
||||
for pattern in caching_patterns:
|
||||
if pattern in file_content:
|
||||
if pattern not in " ".join(evidence):
|
||||
evidence.append(f"Instance caching detected: {pattern}")
|
||||
confidence += 0.1
|
||||
if pattern in file_content and pattern not in " ".join(evidence):
|
||||
evidence.append(f"Instance caching detected: {pattern}")
|
||||
confidence += 0.1
|
||||
|
||||
# Cap confidence at 0.95 (never 100% certain without runtime analysis)
|
||||
result.confidence = min(confidence, 0.95)
|
||||
@@ -1126,13 +1124,12 @@ class AdapterDetector(BasePatternDetector):
|
||||
|
||||
# Check __init__ for composition (takes adaptee)
|
||||
init_method = next((m for m in class_sig.methods if m.name == "__init__"), None)
|
||||
if init_method:
|
||||
if len(init_method.parameters) > 1: # More than just 'self'
|
||||
param_names = [p.name for p in init_method.parameters if p.name != "self"]
|
||||
adaptee_names = ["adaptee", "wrapped", "client", "service", "api", "source"]
|
||||
if any(name in param_names for name in adaptee_names):
|
||||
evidence.append(f"Takes adaptee in constructor: {param_names}")
|
||||
confidence += 0.4
|
||||
if init_method and len(init_method.parameters) > 1:
|
||||
param_names = [p.name for p in init_method.parameters if p.name != "self"]
|
||||
adaptee_names = ["adaptee", "wrapped", "client", "service", "api", "source"]
|
||||
if any(name in param_names for name in adaptee_names):
|
||||
evidence.append(f"Takes adaptee in constructor: {param_names}")
|
||||
confidence += 0.4
|
||||
|
||||
# Check if implements interface (has base class)
|
||||
if class_sig.base_classes:
|
||||
@@ -1521,9 +1518,8 @@ class LanguageAdapter:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
|
||||
# Strategy: Duck typing common in Python
|
||||
elif pattern.pattern_type == "Strategy":
|
||||
if "duck typing" in evidence_str or "protocol" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
elif pattern.pattern_type == "Strategy" and "duck typing" in evidence_str or "protocol" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
|
||||
# JavaScript/TypeScript adaptations
|
||||
elif language in ["JavaScript", "TypeScript"]:
|
||||
@@ -1539,10 +1535,9 @@ class LanguageAdapter:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
|
||||
# Observer: Event emitters are built-in
|
||||
elif pattern.pattern_type == "Observer":
|
||||
if "eventemitter" in evidence_str or "event" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
pattern.evidence.append("EventEmitter pattern detected")
|
||||
elif pattern.pattern_type == "Observer" and "eventemitter" in evidence_str or "event" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
pattern.evidence.append("EventEmitter pattern detected")
|
||||
|
||||
# Java/C# adaptations (interface-heavy languages)
|
||||
elif language in ["Java", "C#"]:
|
||||
@@ -1557,9 +1552,8 @@ class LanguageAdapter:
|
||||
pattern.evidence.append("Abstract Factory pattern")
|
||||
|
||||
# Template Method: Abstract classes common
|
||||
elif pattern.pattern_type == "TemplateMethod":
|
||||
if "abstract" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
elif pattern.pattern_type == "TemplateMethod" and "abstract" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
|
||||
# Go adaptations
|
||||
elif language == "Go":
|
||||
@@ -1570,9 +1564,8 @@ class LanguageAdapter:
|
||||
pattern.evidence.append("Go sync.Once idiom")
|
||||
|
||||
# Strategy: Interfaces are implicit
|
||||
elif pattern.pattern_type == "Strategy":
|
||||
if "interface{}" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
elif pattern.pattern_type == "Strategy" and "interface{}" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
|
||||
# Rust adaptations
|
||||
elif language == "Rust":
|
||||
@@ -1588,9 +1581,8 @@ class LanguageAdapter:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
|
||||
# Adapter: Trait adapters are common
|
||||
elif pattern.pattern_type == "Adapter":
|
||||
if "trait" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
elif pattern.pattern_type == "Adapter" and "trait" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
|
||||
# C++ adaptations
|
||||
elif language == "C++":
|
||||
@@ -1601,9 +1593,8 @@ class LanguageAdapter:
|
||||
pattern.evidence.append("Meyer's Singleton (static local)")
|
||||
|
||||
# Factory: Template-based factories
|
||||
elif pattern.pattern_type == "Factory":
|
||||
if "template" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
elif pattern.pattern_type == "Factory" and "template" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
|
||||
# Ruby adaptations
|
||||
elif language == "Ruby":
|
||||
@@ -1614,9 +1605,8 @@ class LanguageAdapter:
|
||||
pattern.evidence.append("Ruby Singleton module")
|
||||
|
||||
# Builder: Method chaining is idiomatic
|
||||
elif pattern.pattern_type == "Builder":
|
||||
if "method chaining" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
elif pattern.pattern_type == "Builder" and "method chaining" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
|
||||
# PHP adaptations
|
||||
elif language == "PHP":
|
||||
@@ -1626,9 +1616,8 @@ class LanguageAdapter:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
|
||||
# Factory: Static factory methods
|
||||
elif pattern.pattern_type == "Factory":
|
||||
if "static" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
elif pattern.pattern_type == "Factory" and "static" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
|
||||
return pattern
|
||||
|
||||
|
||||
@@ -786,10 +786,7 @@ class PDFExtractor:
|
||||
page = self.doc.load_page(page_num)
|
||||
|
||||
# Extract plain text (with OCR if enabled - Priority 2)
|
||||
if self.use_ocr:
|
||||
text = self.extract_text_with_ocr(page)
|
||||
else:
|
||||
text = page.get_text("text")
|
||||
text = self.extract_text_with_ocr(page) if self.use_ocr else page.get_text("text")
|
||||
|
||||
# Extract markdown (better structure preservation)
|
||||
markdown = page.get_text("markdown")
|
||||
|
||||
@@ -593,9 +593,8 @@ def main():
|
||||
converter = PDFToSkillConverter(config)
|
||||
|
||||
# Extract if needed
|
||||
if config.get("pdf_path"):
|
||||
if not converter.extract_pdf():
|
||||
sys.exit(1)
|
||||
if config.get("pdf_path") and not converter.extract_pdf():
|
||||
sys.exit(1)
|
||||
|
||||
# Build skill
|
||||
converter.build_skill()
|
||||
|
||||
@@ -78,7 +78,7 @@ def check_first_run():
|
||||
flag_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
flag_file.touch()
|
||||
|
||||
response = input("\nPress Enter to continue...")
|
||||
input("\nPress Enter to continue...")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -197,9 +197,8 @@ class PythonTestAnalyzer:
|
||||
examples.extend(self._extract_from_test_class(node, file_path, imports))
|
||||
|
||||
# Find test functions (pytest)
|
||||
elif isinstance(node, ast.FunctionDef):
|
||||
if self._is_test_function(node):
|
||||
examples.extend(self._extract_from_test_function(node, file_path, imports))
|
||||
elif isinstance(node, ast.FunctionDef) and self._is_test_function(node):
|
||||
examples.extend(self._extract_from_test_function(node, file_path, imports))
|
||||
|
||||
return examples
|
||||
|
||||
@@ -209,9 +208,8 @@ class PythonTestAnalyzer:
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
imports.extend([alias.name for alias in node.names])
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
if node.module:
|
||||
imports.append(node.module)
|
||||
elif isinstance(node, ast.ImportFrom) and node.module:
|
||||
imports.append(node.module)
|
||||
return imports
|
||||
|
||||
def _is_test_class(self, node: ast.ClassDef) -> bool:
|
||||
@@ -234,9 +232,8 @@ class PythonTestAnalyzer:
|
||||
return True
|
||||
# Has @pytest.mark decorator
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Attribute):
|
||||
if "pytest" in ast.unparse(decorator):
|
||||
return True
|
||||
if isinstance(decorator, ast.Attribute) and "pytest" in ast.unparse(decorator):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _extract_from_test_class(
|
||||
@@ -364,37 +361,36 @@ class PythonTestAnalyzer:
|
||||
examples = []
|
||||
|
||||
for node in ast.walk(func_node):
|
||||
if isinstance(node, ast.Assign):
|
||||
if isinstance(node.value, ast.Call):
|
||||
# Check if meaningful instantiation
|
||||
if self._is_meaningful_instantiation(node):
|
||||
code = ast.unparse(node)
|
||||
if isinstance(node, ast.Assign) and isinstance(node.value, ast.Call):
|
||||
# Check if meaningful instantiation
|
||||
if self._is_meaningful_instantiation(node):
|
||||
code = ast.unparse(node)
|
||||
|
||||
# Skip trivial or mock-only
|
||||
if len(code) < 20 or "Mock()" in code:
|
||||
continue
|
||||
# Skip trivial or mock-only
|
||||
if len(code) < 20 or "Mock()" in code:
|
||||
continue
|
||||
|
||||
# Get class name
|
||||
class_name = self._get_class_name(node.value)
|
||||
# Get class name
|
||||
class_name = self._get_class_name(node.value)
|
||||
|
||||
example = TestExample(
|
||||
example_id=self._generate_id(code),
|
||||
test_name=func_node.name,
|
||||
category="instantiation",
|
||||
code=code,
|
||||
language="Python",
|
||||
description=f"Instantiate {class_name}: {description}",
|
||||
expected_behavior=self._extract_assertion_after(func_node, node),
|
||||
setup_code=setup_code,
|
||||
file_path=file_path,
|
||||
line_start=node.lineno,
|
||||
line_end=node.end_lineno or node.lineno,
|
||||
complexity_score=self._calculate_complexity(code),
|
||||
confidence=0.8,
|
||||
tags=tags,
|
||||
dependencies=imports,
|
||||
)
|
||||
examples.append(example)
|
||||
example = TestExample(
|
||||
example_id=self._generate_id(code),
|
||||
test_name=func_node.name,
|
||||
category="instantiation",
|
||||
code=code,
|
||||
language="Python",
|
||||
description=f"Instantiate {class_name}: {description}",
|
||||
expected_behavior=self._extract_assertion_after(func_node, node),
|
||||
setup_code=setup_code,
|
||||
file_path=file_path,
|
||||
line_start=node.lineno,
|
||||
line_end=node.end_lineno or node.lineno,
|
||||
complexity_score=self._calculate_complexity(code),
|
||||
confidence=0.8,
|
||||
tags=tags,
|
||||
dependencies=imports,
|
||||
)
|
||||
examples.append(example)
|
||||
|
||||
return examples
|
||||
|
||||
@@ -540,10 +536,7 @@ class PythonTestAnalyzer:
|
||||
|
||||
# Must have at least one argument or keyword argument
|
||||
call = node.value
|
||||
if call.args or call.keywords:
|
||||
return True
|
||||
|
||||
return False
|
||||
return bool(call.args or call.keywords)
|
||||
|
||||
def _get_class_name(self, call_node: ast.Call) -> str:
|
||||
"""Extract class name from Call node"""
|
||||
|
||||
@@ -143,7 +143,7 @@ def test_config_validation_errors():
|
||||
try:
|
||||
# validate_config() calls .validate() automatically
|
||||
_validator = validate_config(config_path)
|
||||
assert False, "Should have raised error for invalid source type"
|
||||
raise AssertionError("Should have raised error for invalid source type")
|
||||
except ValueError as e:
|
||||
assert "Invalid" in str(e) or "invalid" in str(e)
|
||||
print(" ✓ Invalid source type correctly rejected")
|
||||
|
||||
@@ -87,10 +87,7 @@ class UnifiedSkillBuilder:
|
||||
skill_mds = {}
|
||||
|
||||
# Determine base directory for source SKILL.md files
|
||||
if self.cache_dir:
|
||||
sources_dir = Path(self.cache_dir) / "sources"
|
||||
else:
|
||||
sources_dir = Path("output")
|
||||
sources_dir = Path(self.cache_dir) / "sources" if self.cache_dir else Path("output")
|
||||
|
||||
# Load documentation SKILL.md
|
||||
docs_skill_path = sources_dir / f"{self.name}_docs" / "SKILL.md"
|
||||
|
||||
Reference in New Issue
Block a user