style: Run ruff format on 15 files (CI fix)
CI uses 'ruff format' not 'black' - applied proper formatting: Files reformatted by ruff: - config_extractor.py - doc_scraper.py - how_to_guide_builder.py - llms_txt_parser.py - pattern_recognizer.py - test_example_extractor.py - unified_codebase_analyzer.py - test_architecture_scenarios.py - test_async_scraping.py - test_github_scraper.py - test_guide_enhancer.py - test_install_agent.py - test_issue_219_e2e.py - test_llms_txt_downloader.py - test_skip_llms_txt.py Fixes CI formatting check failure. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -89,9 +89,7 @@ class ConfigExtractionResult:
|
||||
config_files: list[ConfigFile] = field(default_factory=list)
|
||||
total_files: int = 0
|
||||
total_settings: int = 0
|
||||
detected_patterns: dict[str, list[str]] = field(
|
||||
default_factory=dict
|
||||
) # pattern -> files
|
||||
detected_patterns: dict[str, list[str]] = field(default_factory=dict) # pattern -> files
|
||||
errors: list[str] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
@@ -241,9 +239,7 @@ class ConfigFileDetector:
|
||||
"*.egg-info",
|
||||
}
|
||||
|
||||
def find_config_files(
|
||||
self, directory: Path, max_files: int = 100
|
||||
) -> list[ConfigFile]:
|
||||
def find_config_files(self, directory: Path, max_files: int = 100) -> list[ConfigFile]:
|
||||
"""
|
||||
Find all configuration files in directory.
|
||||
|
||||
@@ -314,10 +310,7 @@ class ConfigFileDetector:
|
||||
filename = file_path.name.lower()
|
||||
|
||||
# Database configs
|
||||
if any(
|
||||
word in path_lower
|
||||
for word in ["database", "db", "postgres", "mysql", "mongo"]
|
||||
):
|
||||
if any(word in path_lower for word in ["database", "db", "postgres", "mysql", "mongo"]):
|
||||
return "database_configuration"
|
||||
|
||||
# API configs
|
||||
@@ -333,9 +326,7 @@ class ConfigFileDetector:
|
||||
return "docker_configuration"
|
||||
|
||||
# CI/CD configs
|
||||
if any(
|
||||
word in path_lower for word in [".travis", ".gitlab", ".github", "ci", "cd"]
|
||||
):
|
||||
if any(word in path_lower for word in [".travis", ".gitlab", ".github", "ci", "cd"]):
|
||||
return "ci_cd_configuration"
|
||||
|
||||
# Package configs
|
||||
@@ -347,11 +338,7 @@ class ConfigFileDetector:
|
||||
return "typescript_configuration"
|
||||
|
||||
# Framework configs
|
||||
if (
|
||||
"next.config" in filename
|
||||
or "vue.config" in filename
|
||||
or "webpack.config" in filename
|
||||
):
|
||||
if "next.config" in filename or "vue.config" in filename or "webpack.config" in filename:
|
||||
return "framework_configuration"
|
||||
|
||||
# Environment configs
|
||||
@@ -531,9 +518,7 @@ class ConfigParser:
|
||||
for match in re.finditer(pattern, config_file.raw_content):
|
||||
if len(match.groups()) >= 2:
|
||||
key = match.group(1)
|
||||
value = (
|
||||
match.group(3) if len(match.groups()) > 2 else match.group(2)
|
||||
)
|
||||
value = match.group(3) if len(match.groups()) > 2 else match.group(2)
|
||||
|
||||
setting = ConfigSetting(
|
||||
key=key, value=value, value_type=self._infer_type(value)
|
||||
@@ -579,9 +564,7 @@ class ConfigParser:
|
||||
for key, value in data.items():
|
||||
if isinstance(value, dict):
|
||||
# Recurse into nested dicts
|
||||
self._extract_settings_from_dict(
|
||||
value, config_file, parent_path + [key]
|
||||
)
|
||||
self._extract_settings_from_dict(value, config_file, parent_path + [key])
|
||||
else:
|
||||
setting = ConfigSetting(
|
||||
key=".".join(parent_path + [key]) if parent_path else key,
|
||||
@@ -872,9 +855,7 @@ def main():
|
||||
print("\n📊 Summary:")
|
||||
print(f" Config files found: {result.total_files}")
|
||||
print(f" Total settings: {result.total_settings}")
|
||||
print(
|
||||
f" Detected patterns: {', '.join(result.detected_patterns.keys()) or 'None'}"
|
||||
)
|
||||
print(f" Detected patterns: {', '.join(result.detected_patterns.keys()) or 'None'}")
|
||||
|
||||
if "ai_enhancements" in output_dict:
|
||||
print(f" ✨ AI enhancements: Yes ({enhance_mode} mode)")
|
||||
|
||||
@@ -148,9 +148,7 @@ def infer_description_from_docs(
|
||||
|
||||
|
||||
class DocToSkillConverter:
|
||||
def __init__(
|
||||
self, config: dict[str, Any], dry_run: bool = False, resume: bool = False
|
||||
) -> None:
|
||||
def __init__(self, config: dict[str, Any], dry_run: bool = False, resume: bool = False) -> None:
|
||||
self.config = config
|
||||
self.name = config["name"]
|
||||
self.base_url = config["base_url"]
|
||||
@@ -165,9 +163,7 @@ class DocToSkillConverter:
|
||||
# Checkpoint config
|
||||
checkpoint_config = config.get("checkpoint", {})
|
||||
self.checkpoint_enabled = checkpoint_config.get("enabled", False)
|
||||
self.checkpoint_interval = checkpoint_config.get(
|
||||
"interval", DEFAULT_CHECKPOINT_INTERVAL
|
||||
)
|
||||
self.checkpoint_interval = checkpoint_config.get("interval", DEFAULT_CHECKPOINT_INTERVAL)
|
||||
|
||||
# llms.txt detection state
|
||||
skip_llms_txt_value = config.get("skip_llms_txt", False)
|
||||
@@ -322,9 +318,7 @@ class DocToSkillConverter:
|
||||
for h in main.find_all(["h1", "h2", "h3", "h4", "h5", "h6"]):
|
||||
text = self.clean_text(h.get_text())
|
||||
if text:
|
||||
page["headings"].append(
|
||||
{"level": h.name, "text": text, "id": h.get("id", "")}
|
||||
)
|
||||
page["headings"].append({"level": h.name, "text": text, "id": h.get("id", "")})
|
||||
|
||||
# Extract code with language detection
|
||||
code_selector = selectors.get("code_blocks", "pre code")
|
||||
@@ -391,9 +385,7 @@ class DocToSkillConverter:
|
||||
import re
|
||||
|
||||
# Detect if content is actually HTML (some .md URLs return HTML)
|
||||
if content.strip().startswith("<!DOCTYPE") or content.strip().startswith(
|
||||
"<html"
|
||||
):
|
||||
if content.strip().startswith("<!DOCTYPE") or content.strip().startswith("<html"):
|
||||
return self._extract_html_as_markdown(content, url)
|
||||
|
||||
page = {
|
||||
@@ -432,9 +424,7 @@ class DocToSkillConverter:
|
||||
code_blocks = re.findall(r"```(\w+)?\n(.*?)```", content, re.DOTALL)
|
||||
for lang, code in code_blocks:
|
||||
if len(code.strip()) > 10:
|
||||
page["code_samples"].append(
|
||||
{"code": code.strip(), "language": lang or "unknown"}
|
||||
)
|
||||
page["code_samples"].append({"code": code.strip(), "language": lang or "unknown"})
|
||||
|
||||
# Extract content (paragraphs)
|
||||
content_no_code = re.sub(r"```.*?```", "", content, flags=re.DOTALL)
|
||||
@@ -458,11 +448,7 @@ class DocToSkillConverter:
|
||||
# Strip anchor fragments
|
||||
full_url = full_url.split("#")[0]
|
||||
# Only include .md URLs to avoid client-side rendered HTML pages
|
||||
if (
|
||||
".md" in full_url
|
||||
and self.is_valid_url(full_url)
|
||||
and full_url not in page["links"]
|
||||
):
|
||||
if ".md" in full_url and self.is_valid_url(full_url) and full_url not in page["links"]:
|
||||
page["links"].append(full_url)
|
||||
|
||||
return page
|
||||
@@ -526,18 +512,14 @@ class DocToSkillConverter:
|
||||
for h in main.find_all(["h1", "h2", "h3", "h4", "h5", "h6"]):
|
||||
text = self.clean_text(h.get_text())
|
||||
if text:
|
||||
page["headings"].append(
|
||||
{"level": h.name, "text": text, "id": h.get("id", "")}
|
||||
)
|
||||
page["headings"].append({"level": h.name, "text": text, "id": h.get("id", "")})
|
||||
|
||||
# Extract code blocks
|
||||
for code_elem in main.select("pre code, pre"):
|
||||
code = code_elem.get_text()
|
||||
if len(code.strip()) > 10:
|
||||
lang = self.detect_language(code_elem, code)
|
||||
page["code_samples"].append(
|
||||
{"code": code.strip(), "language": lang}
|
||||
)
|
||||
page["code_samples"].append({"code": code.strip(), "language": lang})
|
||||
|
||||
# Extract paragraphs
|
||||
paragraphs = []
|
||||
@@ -558,9 +540,7 @@ class DocToSkillConverter:
|
||||
|
||||
# Log low-confidence detections for debugging
|
||||
if confidence < 0.5:
|
||||
logger.debug(
|
||||
f"Low confidence language detection: {lang} ({confidence:.2f})"
|
||||
)
|
||||
logger.debug(f"Low confidence language detection: {lang} ({confidence:.2f})")
|
||||
|
||||
return lang # Return string for backward compatibility
|
||||
|
||||
@@ -573,10 +553,7 @@ class DocToSkillConverter:
|
||||
# Look for "Example:" or "Pattern:" sections
|
||||
for elem in main.find_all(["p", "div"]):
|
||||
text = elem.get_text().lower()
|
||||
if any(
|
||||
word in text
|
||||
for word in ["example:", "pattern:", "usage:", "typical use"]
|
||||
):
|
||||
if any(word in text for word in ["example:", "pattern:", "usage:", "typical use"]):
|
||||
# Get the code that follows
|
||||
next_code = elem.find_next(["pre", "code"])
|
||||
if next_code:
|
||||
@@ -598,9 +575,7 @@ class DocToSkillConverter:
|
||||
"""Save page data (skip pages with empty content)"""
|
||||
# Skip pages with empty or very short content
|
||||
if not page.get("content") or len(page.get("content", "")) < 50:
|
||||
logger.debug(
|
||||
"Skipping page with empty/short content: %s", page.get("url", "unknown")
|
||||
)
|
||||
logger.debug("Skipping page with empty/short content: %s", page.get("url", "unknown"))
|
||||
return
|
||||
|
||||
url_hash = hashlib.md5(page["url"].encode()).hexdigest()[:10]
|
||||
@@ -648,10 +623,7 @@ class DocToSkillConverter:
|
||||
|
||||
# Add new URLs
|
||||
for link in page["links"]:
|
||||
if (
|
||||
link not in self.visited_urls
|
||||
and link not in self.pending_urls
|
||||
):
|
||||
if link not in self.visited_urls and link not in self.pending_urls:
|
||||
self.pending_urls.append(link)
|
||||
else:
|
||||
# Single-threaded mode (no lock needed)
|
||||
@@ -672,9 +644,7 @@ class DocToSkillConverter:
|
||||
except Exception as e:
|
||||
if self.workers > 1:
|
||||
with self.lock:
|
||||
logger.error(
|
||||
" ✗ Error scraping %s: %s: %s", url, type(e).__name__, e
|
||||
)
|
||||
logger.error(" ✗ Error scraping %s: %s: %s", url, type(e).__name__, e)
|
||||
else:
|
||||
logger.error(" ✗ Error scraping page: %s: %s", type(e).__name__, e)
|
||||
logger.error(" URL: %s", url)
|
||||
@@ -792,9 +762,7 @@ class DocToSkillConverter:
|
||||
# Check for explicit config URL first
|
||||
explicit_url = self.config.get("llms_txt_url")
|
||||
if explicit_url:
|
||||
logger.info(
|
||||
"\n📌 Using explicit llms_txt_url from config: %s", explicit_url
|
||||
)
|
||||
logger.info("\n📌 Using explicit llms_txt_url from config: %s", explicit_url)
|
||||
|
||||
# Download explicit file first
|
||||
downloader = LlmsTxtDownloader(explicit_url)
|
||||
@@ -915,9 +883,7 @@ class DocToSkillConverter:
|
||||
logger.info(" ✓ %s (%d chars)", filename, len(content))
|
||||
|
||||
if not downloaded:
|
||||
logger.warning(
|
||||
"⚠️ Failed to download any variants, falling back to HTML scraping"
|
||||
)
|
||||
logger.warning("⚠️ Failed to download any variants, falling back to HTML scraping")
|
||||
return False
|
||||
|
||||
# Save ALL variants to references/
|
||||
@@ -1032,9 +998,7 @@ class DocToSkillConverter:
|
||||
|
||||
# Single-threaded mode (original sequential logic)
|
||||
if self.workers <= 1:
|
||||
while self.pending_urls and (
|
||||
unlimited or len(self.visited_urls) < preview_limit
|
||||
):
|
||||
while self.pending_urls and (unlimited or len(self.visited_urls) < preview_limit):
|
||||
url = self.pending_urls.popleft()
|
||||
|
||||
if url in self.visited_urls:
|
||||
@@ -1046,9 +1010,7 @@ class DocToSkillConverter:
|
||||
# Just show what would be scraped
|
||||
logger.info(" [Preview] %s", url)
|
||||
try:
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Documentation Scraper - Dry Run)"
|
||||
}
|
||||
headers = {"User-Agent": "Mozilla/5.0 (Documentation Scraper - Dry Run)"}
|
||||
response = requests.get(url, headers=headers, timeout=10)
|
||||
soup = BeautifulSoup(response.content, "html.parser")
|
||||
|
||||
@@ -1060,16 +1022,11 @@ class DocToSkillConverter:
|
||||
if main:
|
||||
for link in main.find_all("a", href=True):
|
||||
href = urljoin(url, link["href"])
|
||||
if (
|
||||
self.is_valid_url(href)
|
||||
and href not in self.visited_urls
|
||||
):
|
||||
if self.is_valid_url(href) and href not in self.visited_urls:
|
||||
self.pending_urls.append(href)
|
||||
except Exception as e:
|
||||
# Failed to extract links in fast mode, continue anyway
|
||||
logger.warning(
|
||||
"⚠️ Warning: Could not extract links from %s: %s", url, e
|
||||
)
|
||||
logger.warning("⚠️ Warning: Could not extract links from %s: %s", url, e)
|
||||
else:
|
||||
self.scrape_page(url)
|
||||
self.pages_scraped += 1
|
||||
@@ -1092,9 +1049,7 @@ class DocToSkillConverter:
|
||||
with ThreadPoolExecutor(max_workers=self.workers) as executor:
|
||||
futures = []
|
||||
|
||||
while self.pending_urls and (
|
||||
unlimited or len(self.visited_urls) < preview_limit
|
||||
):
|
||||
while self.pending_urls and (unlimited or len(self.visited_urls) < preview_limit):
|
||||
# Get next batch of URLs (thread-safe)
|
||||
batch = []
|
||||
batch_size = min(self.workers * 2, len(self.pending_urls))
|
||||
@@ -1152,9 +1107,7 @@ class DocToSkillConverter:
|
||||
self.pages_scraped += 1
|
||||
|
||||
if self.dry_run:
|
||||
logger.info(
|
||||
"\n✅ Dry run complete: would scrape ~%d pages", len(self.visited_urls)
|
||||
)
|
||||
logger.info("\n✅ Dry run complete: would scrape ~%d pages", len(self.visited_urls))
|
||||
if len(self.visited_urls) >= preview_limit:
|
||||
logger.info(
|
||||
" (showing first %d, actual scraping may find more)",
|
||||
@@ -1221,9 +1174,7 @@ class DocToSkillConverter:
|
||||
) as client:
|
||||
tasks = []
|
||||
|
||||
while self.pending_urls and (
|
||||
unlimited or len(self.visited_urls) < preview_limit
|
||||
):
|
||||
while self.pending_urls and (unlimited or len(self.visited_urls) < preview_limit):
|
||||
# Get next batch of URLs
|
||||
batch = []
|
||||
batch_size = min(self.workers * 2, len(self.pending_urls))
|
||||
@@ -1271,9 +1222,7 @@ class DocToSkillConverter:
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
if self.dry_run:
|
||||
logger.info(
|
||||
"\n✅ Dry run complete: would scrape ~%d pages", len(self.visited_urls)
|
||||
)
|
||||
logger.info("\n✅ Dry run complete: would scrape ~%d pages", len(self.visited_urls))
|
||||
if len(self.visited_urls) >= preview_limit:
|
||||
logger.info(
|
||||
" (showing first %d, actual scraping may find more)",
|
||||
@@ -1323,9 +1272,7 @@ class DocToSkillConverter:
|
||||
|
||||
return pages
|
||||
|
||||
def smart_categorize(
|
||||
self, pages: list[dict[str, Any]]
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
def smart_categorize(self, pages: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Improved categorization with better pattern matching"""
|
||||
category_defs = self.config.get("categories", {})
|
||||
|
||||
@@ -1377,18 +1324,14 @@ class DocToSkillConverter:
|
||||
for page in pages:
|
||||
path = urlparse(page["url"]).path
|
||||
segments = [
|
||||
s
|
||||
for s in path.split("/")
|
||||
if s and s not in ["en", "stable", "latest", "docs"]
|
||||
s for s in path.split("/") if s and s not in ["en", "stable", "latest", "docs"]
|
||||
]
|
||||
|
||||
for seg in segments:
|
||||
url_segments[seg] += 1
|
||||
|
||||
# Top segments become categories
|
||||
top_segments = sorted(url_segments.items(), key=lambda x: x[1], reverse=True)[
|
||||
:8
|
||||
]
|
||||
top_segments = sorted(url_segments.items(), key=lambda x: x[1], reverse=True)[:8]
|
||||
|
||||
categories = {}
|
||||
for seg, count in top_segments:
|
||||
@@ -1408,9 +1351,7 @@ class DocToSkillConverter:
|
||||
|
||||
return categories
|
||||
|
||||
def generate_quick_reference(
|
||||
self, pages: list[dict[str, Any]]
|
||||
) -> list[dict[str, str]]:
|
||||
def generate_quick_reference(self, pages: list[dict[str, Any]]) -> list[dict[str, str]]:
|
||||
"""Generate quick reference from common patterns (NEW FEATURE)"""
|
||||
quick_ref = []
|
||||
|
||||
@@ -1492,9 +1433,7 @@ class DocToSkillConverter:
|
||||
if pages:
|
||||
first_page_html = pages[0].get("raw_html", "")
|
||||
break
|
||||
description = infer_description_from_docs(
|
||||
self.base_url, first_page_html, self.name
|
||||
)
|
||||
description = infer_description_from_docs(self.base_url, first_page_html, self.name)
|
||||
else:
|
||||
description = self.config["description"]
|
||||
|
||||
@@ -1502,9 +1441,7 @@ class DocToSkillConverter:
|
||||
example_codes = []
|
||||
for pages in categories.values():
|
||||
for page in pages[:3]: # First 3 pages per category
|
||||
for sample in page.get("code_samples", [])[
|
||||
:2
|
||||
]: # First 2 samples per page
|
||||
for sample in page.get("code_samples", [])[:2]: # First 2 samples per page
|
||||
code = sample.get("code", sample if isinstance(sample, str) else "")
|
||||
lang = sample.get("language", "unknown")
|
||||
if len(code) < 200 and lang != "unknown":
|
||||
@@ -1554,9 +1491,7 @@ This skill should be triggered when:
|
||||
content += pattern.get("code", "")[:300]
|
||||
content += "\n```\n\n"
|
||||
else:
|
||||
content += (
|
||||
"*Quick reference patterns will be added as you use the skill.*\n\n"
|
||||
)
|
||||
content += "*Quick reference patterns will be added as you use the skill.*\n\n"
|
||||
|
||||
# Add example codes from docs
|
||||
if example_codes:
|
||||
@@ -1571,9 +1506,7 @@ This skill includes comprehensive documentation in `references/`:
|
||||
"""
|
||||
|
||||
for cat in sorted(categories.keys()):
|
||||
content += (
|
||||
f"- **{cat}.md** - {cat.replace('_', ' ').title()} documentation\n"
|
||||
)
|
||||
content += f"- **{cat}.md** - {cat.replace('_', ' ').title()} documentation\n"
|
||||
|
||||
content += """
|
||||
Use `view` to read specific reference files when detailed information is needed.
|
||||
@@ -1721,9 +1654,7 @@ def validate_config(config: dict[str, Any]) -> tuple[list[str], list[str]]:
|
||||
)
|
||||
|
||||
# Validate base_url
|
||||
if "base_url" in config and not config["base_url"].startswith(
|
||||
("http://", "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://)"
|
||||
)
|
||||
@@ -1840,18 +1771,12 @@ def load_config(config_path: str) -> dict[str, Any]:
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("❌ Error: Invalid JSON in config file: %s", config_path)
|
||||
logger.error(" Details: %s", e)
|
||||
logger.error(
|
||||
" Suggestion: Check syntax at line %d, column %d", e.lineno, e.colno
|
||||
)
|
||||
logger.error(" Suggestion: Check syntax at line %d, column %d", e.lineno, e.colno)
|
||||
sys.exit(1)
|
||||
except FileNotFoundError:
|
||||
logger.error("❌ Error: Config file not found: %s", config_path)
|
||||
logger.error(
|
||||
" Suggestion: Create a config file or use an existing one from configs/"
|
||||
)
|
||||
logger.error(
|
||||
" Available configs: react.json, vue.json, django.json, godot.json"
|
||||
)
|
||||
logger.error(" Suggestion: Create a config file or use an existing one from configs/")
|
||||
logger.error(" Available configs: react.json, vue.json, django.json, godot.json")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate config
|
||||
@@ -1869,9 +1794,7 @@ def load_config(config_path: str) -> dict[str, Any]:
|
||||
logger.error("❌ Configuration validation errors in %s:", config_path)
|
||||
for error in errors:
|
||||
logger.error(" - %s", error)
|
||||
logger.error(
|
||||
"\n Suggestion: Fix the above errors or check configs/ for working examples"
|
||||
)
|
||||
logger.error("\n Suggestion: Fix the above errors or check configs/ for working examples")
|
||||
sys.exit(1)
|
||||
|
||||
return config
|
||||
@@ -2025,9 +1948,7 @@ def setup_argument_parser() -> argparse.ArgumentParser:
|
||||
action="store_true",
|
||||
help="Resume from last checkpoint (for interrupted scrapes)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fresh", action="store_true", help="Clear checkpoint and start fresh"
|
||||
)
|
||||
parser.add_argument("--fresh", action="store_true", help="Clear checkpoint and start fresh")
|
||||
parser.add_argument(
|
||||
"--rate-limit",
|
||||
"-r",
|
||||
@@ -2126,15 +2047,11 @@ def get_configuration(args: argparse.Namespace) -> dict[str, Any]:
|
||||
if args.workers:
|
||||
# Validate workers count
|
||||
if args.workers < 1:
|
||||
logger.error(
|
||||
"❌ Error: --workers must be at least 1 (got %d)", args.workers
|
||||
)
|
||||
logger.error("❌ Error: --workers must be at least 1 (got %d)", args.workers)
|
||||
logger.error(" Suggestion: Use --workers 1 (default) or omit the flag")
|
||||
sys.exit(1)
|
||||
if args.workers > 10:
|
||||
logger.warning(
|
||||
"⚠️ Warning: --workers capped at 10 (requested %d)", args.workers
|
||||
)
|
||||
logger.warning("⚠️ Warning: --workers capped at 10 (requested %d)", args.workers)
|
||||
args.workers = 10
|
||||
config["workers"] = args.workers
|
||||
if args.workers > 1:
|
||||
@@ -2336,9 +2253,7 @@ def execute_enhancement(config: dict[str, Any], args: argparse.Namespace) -> Non
|
||||
# Suggest enhancement if not done
|
||||
if not args.enhance and not args.enhance_local:
|
||||
logger.info("\n💡 Optional: Enhance SKILL.md with Claude:")
|
||||
logger.info(
|
||||
" Local (recommended): skill-seekers-enhance output/%s/", config["name"]
|
||||
)
|
||||
logger.info(" Local (recommended): skill-seekers-enhance output/%s/", config["name"])
|
||||
logger.info(" or re-run with: --enhance-local")
|
||||
logger.info(
|
||||
" API-based: skill-seekers-enhance-api output/%s/",
|
||||
|
||||
@@ -79,9 +79,7 @@ class WorkflowStep:
|
||||
setup_required: str | None = None
|
||||
explanation: str | None = None # Why this step matters
|
||||
common_pitfall: str | None = None # Warning for this step
|
||||
common_variations: list[str] = field(
|
||||
default_factory=list
|
||||
) # AI: Alternative approaches
|
||||
common_variations: list[str] = field(default_factory=list) # AI: Alternative approaches
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -223,9 +221,7 @@ class WorkflowAnalyzer:
|
||||
# Check if next statement is assertion (verification)
|
||||
idx = statements.index(stmt)
|
||||
verification = None
|
||||
if idx + 1 < len(statements) and isinstance(
|
||||
statements[idx + 1], ast.Assert
|
||||
):
|
||||
if idx + 1 < len(statements) and isinstance(statements[idx + 1], ast.Assert):
|
||||
verification = ast.get_source_segment(code, statements[idx + 1])
|
||||
|
||||
steps.append(
|
||||
@@ -244,9 +240,7 @@ class WorkflowAnalyzer:
|
||||
|
||||
return steps
|
||||
|
||||
def _extract_steps_heuristic(
|
||||
self, code: str, _workflow: dict
|
||||
) -> list[WorkflowStep]:
|
||||
def _extract_steps_heuristic(self, code: str, _workflow: dict) -> list[WorkflowStep]:
|
||||
"""Extract steps using heuristics (for non-Python or invalid syntax)"""
|
||||
steps = []
|
||||
lines = code.split("\n")
|
||||
@@ -282,9 +276,7 @@ class WorkflowAnalyzer:
|
||||
step_code = "\n".join(current_step)
|
||||
description = self._infer_description_from_code(step_code)
|
||||
steps.append(
|
||||
WorkflowStep(
|
||||
step_number=step_num, code=step_code, description=description
|
||||
)
|
||||
WorkflowStep(step_number=step_num, code=step_code, description=description)
|
||||
)
|
||||
|
||||
return steps
|
||||
@@ -454,9 +446,7 @@ class WorkflowGrouper:
|
||||
groups = self._group_by_file_path(workflows)
|
||||
return groups
|
||||
|
||||
def _group_by_ai_tutorial_group(
|
||||
self, workflows: list[dict]
|
||||
) -> dict[str, list[dict]]:
|
||||
def _group_by_ai_tutorial_group(self, workflows: list[dict]) -> dict[str, list[dict]]:
|
||||
"""Group by AI-generated tutorial_group (from C3.6 enhancement)"""
|
||||
groups = defaultdict(list)
|
||||
ungrouped = []
|
||||
@@ -914,9 +904,7 @@ class HowToGuideBuilder:
|
||||
"""Filter to workflow category only"""
|
||||
return [ex for ex in examples if ex.get("category") == "workflow"]
|
||||
|
||||
def _create_guide(
|
||||
self, title: str, workflows: list[dict], enhancer=None
|
||||
) -> HowToGuide:
|
||||
def _create_guide(self, title: str, workflows: list[dict], enhancer=None) -> HowToGuide:
|
||||
"""
|
||||
Generate single guide from workflow(s).
|
||||
|
||||
@@ -974,18 +962,14 @@ class HowToGuideBuilder:
|
||||
|
||||
# Add AI enhancements if enhancer is available
|
||||
if enhancer:
|
||||
self._enhance_guide_with_ai(
|
||||
guide, primary_workflow.get("ai_analysis", {}), enhancer
|
||||
)
|
||||
self._enhance_guide_with_ai(guide, primary_workflow.get("ai_analysis", {}), enhancer)
|
||||
elif self.enhance_with_ai and primary_workflow.get("ai_analysis"):
|
||||
# Fallback to old enhancement method (basic)
|
||||
self._enhance_guide_with_ai_basic(guide, primary_workflow["ai_analysis"])
|
||||
|
||||
return guide
|
||||
|
||||
def _generate_overview(
|
||||
self, primary_workflow: dict, _all_workflows: list[dict]
|
||||
) -> str:
|
||||
def _generate_overview(self, primary_workflow: dict, _all_workflows: list[dict]) -> str:
|
||||
"""Generate guide overview"""
|
||||
# Try to get explanation from AI analysis
|
||||
if primary_workflow.get("ai_analysis"):
|
||||
@@ -1019,10 +1003,7 @@ class HowToGuideBuilder:
|
||||
# Prepare guide data for enhancer
|
||||
guide_data = {
|
||||
"title": guide.title,
|
||||
"steps": [
|
||||
{"description": step.description, "code": step.code}
|
||||
for step in guide.steps
|
||||
],
|
||||
"steps": [{"description": step.description, "code": step.code} for step in guide.steps],
|
||||
"language": "python", # TODO: Detect from code
|
||||
"prerequisites": guide.prerequisites,
|
||||
"description": guide.overview,
|
||||
@@ -1055,9 +1036,7 @@ class HowToGuideBuilder:
|
||||
if "use_cases" in enhanced_data:
|
||||
guide.use_cases = enhanced_data["use_cases"]
|
||||
|
||||
logger.info(
|
||||
f"✨ Enhanced guide '{guide.title}' with comprehensive AI improvements"
|
||||
)
|
||||
logger.info(f"✨ Enhanced guide '{guide.title}' with comprehensive AI improvements")
|
||||
|
||||
def _enhance_guide_with_ai_basic(self, guide: HowToGuide, ai_analysis: dict):
|
||||
"""
|
||||
@@ -1122,9 +1101,7 @@ class HowToGuideBuilder:
|
||||
|
||||
for guide in guides:
|
||||
# Generate filename from title
|
||||
filename = (
|
||||
guide.title.lower().replace(" ", "-").replace(":", "") + ".md"
|
||||
)
|
||||
filename = guide.title.lower().replace(" ", "-").replace(":", "") + ".md"
|
||||
file_path = use_case_dir / filename
|
||||
|
||||
# Generate and save markdown
|
||||
@@ -1135,9 +1112,7 @@ class HowToGuideBuilder:
|
||||
index_markdown = self.generator.generate_index(collection.guides)
|
||||
(output_dir / "index.md").write_text(index_markdown, encoding="utf-8")
|
||||
|
||||
logger.info(
|
||||
f"✅ Saved {collection.total_guides} guides + index to {output_dir}"
|
||||
)
|
||||
logger.info(f"✅ Saved {collection.total_guides} guides + index to {output_dir}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -1244,9 +1219,7 @@ Grouping Strategies:
|
||||
# Extract from directory using test example extractor
|
||||
print("⚠️ Directory input requires test example extractor")
|
||||
print(" Please use test_examples.json output from C3.2")
|
||||
print(
|
||||
f" Or run: skill-seekers extract-test-examples {input_path} --json > examples.json"
|
||||
)
|
||||
print(f" Or run: skill-seekers extract-test-examples {input_path} --json > examples.json")
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
|
||||
@@ -127,9 +127,7 @@ class LlmsTxtParser:
|
||||
# Extract code blocks
|
||||
code_blocks = re.findall(r"```(\w+)?\n(.*?)```", content, re.DOTALL)
|
||||
for lang, code in code_blocks:
|
||||
page["code_samples"].append(
|
||||
{"code": code.strip(), "language": lang or "unknown"}
|
||||
)
|
||||
page["code_samples"].append({"code": code.strip(), "language": lang or "unknown"})
|
||||
|
||||
# Extract h2/h3 headings
|
||||
headings = re.findall(r"^(#{2,3})\s+(.+)$", content, re.MULTILINE)
|
||||
@@ -146,9 +144,7 @@ class LlmsTxtParser:
|
||||
content_no_code = re.sub(r"```.*?```", "", content, flags=re.DOTALL)
|
||||
|
||||
# Extract paragraphs
|
||||
paragraphs = [
|
||||
p.strip() for p in content_no_code.split("\n\n") if len(p.strip()) > 20
|
||||
]
|
||||
paragraphs = [p.strip() for p in content_no_code.split("\n\n") if len(p.strip()) > 20]
|
||||
page["content"] = "\n\n".join(paragraphs)
|
||||
|
||||
return page
|
||||
|
||||
@@ -237,9 +237,7 @@ class PatternRecognizer:
|
||||
self.detectors.append(TemplateMethodDetector(self.depth))
|
||||
self.detectors.append(ChainOfResponsibilityDetector(self.depth))
|
||||
|
||||
def analyze_file(
|
||||
self, file_path: str, content: str, language: str
|
||||
) -> PatternReport:
|
||||
def analyze_file(self, file_path: str, content: str, language: str) -> PatternReport:
|
||||
"""
|
||||
Analyze a single file for design patterns.
|
||||
|
||||
@@ -581,9 +579,7 @@ class FactoryDetector(BasePatternDetector):
|
||||
|
||||
# Check if multiple factory methods exist (Abstract Factory pattern)
|
||||
if len(factory_methods) >= 2:
|
||||
evidence.append(
|
||||
f"Multiple factory methods: {', '.join(factory_methods[:3])}"
|
||||
)
|
||||
evidence.append(f"Multiple factory methods: {', '.join(factory_methods[:3])}")
|
||||
confidence += 0.2
|
||||
|
||||
# Check for inheritance (factory hierarchy)
|
||||
@@ -800,35 +796,25 @@ class StrategyDetector(BasePatternDetector):
|
||||
]
|
||||
|
||||
if siblings:
|
||||
evidence.append(
|
||||
f"Part of strategy family with: {', '.join(siblings[:3])}"
|
||||
)
|
||||
evidence.append(f"Part of strategy family with: {', '.join(siblings[:3])}")
|
||||
confidence += 0.5
|
||||
|
||||
if base_class and (
|
||||
"strategy" in base_class.lower() or "policy" in base_class.lower()
|
||||
):
|
||||
if base_class and ("strategy" in base_class.lower() or "policy" in base_class.lower()):
|
||||
evidence.append(f"Inherits from strategy base: {base_class}")
|
||||
confidence += 0.3
|
||||
|
||||
# Check if this is a strategy base class
|
||||
# (has subclasses in same file)
|
||||
subclasses = [
|
||||
cls.name for cls in all_classes if class_sig.name in cls.base_classes
|
||||
]
|
||||
subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes]
|
||||
|
||||
if len(subclasses) >= 2:
|
||||
evidence.append(
|
||||
f"Strategy base with implementations: {', '.join(subclasses[:3])}"
|
||||
)
|
||||
evidence.append(f"Strategy base with implementations: {', '.join(subclasses[:3])}")
|
||||
confidence += 0.6
|
||||
|
||||
# Check for single dominant method (strategy interface)
|
||||
if len(class_sig.methods) == 1 or len(class_sig.methods) == 2:
|
||||
# Single method or method + __init__
|
||||
main_method = [
|
||||
m for m in class_sig.methods if m.name not in ["__init__", "__new__"]
|
||||
]
|
||||
main_method = [m for m in class_sig.methods if m.name not in ["__init__", "__new__"]]
|
||||
if main_method:
|
||||
evidence.append(f"Strategy interface method: {main_method[0].name}")
|
||||
confidence += 0.2
|
||||
@@ -939,8 +925,7 @@ class DecoratorDetector(BasePatternDetector):
|
||||
if init_method and len(init_method.parameters) > 1: # More than just 'self'
|
||||
param_names = [p.name for p in init_method.parameters if p.name != "self"]
|
||||
if any(
|
||||
name in ["wrapped", "component", "inner", "obj", "target"]
|
||||
for name in param_names
|
||||
name in ["wrapped", "component", "inner", "obj", "target"] for name in param_names
|
||||
):
|
||||
evidence.append(f"Takes wrapped object in constructor: {param_names}")
|
||||
confidence += 0.4
|
||||
@@ -1298,9 +1283,7 @@ class TemplateMethodDetector(BasePatternDetector):
|
||||
class_lower = class_sig.name.lower()
|
||||
if any(keyword in class_lower for keyword in template_keywords):
|
||||
# Check if has subclasses
|
||||
subclasses = [
|
||||
cls.name for cls in all_classes if class_sig.name in cls.base_classes
|
||||
]
|
||||
subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes]
|
||||
|
||||
if subclasses:
|
||||
return PatternInstance(
|
||||
@@ -1310,9 +1293,7 @@ class TemplateMethodDetector(BasePatternDetector):
|
||||
location="",
|
||||
class_name=class_sig.name,
|
||||
line_number=class_sig.line_number,
|
||||
evidence=[
|
||||
f"Abstract base with subclasses: {', '.join(subclasses[:2])}"
|
||||
],
|
||||
evidence=[f"Abstract base with subclasses: {', '.join(subclasses[:2])}"],
|
||||
related_classes=subclasses,
|
||||
)
|
||||
|
||||
@@ -1329,9 +1310,7 @@ class TemplateMethodDetector(BasePatternDetector):
|
||||
# 3. Has template method that orchestrates
|
||||
|
||||
# Check for subclasses
|
||||
subclasses = [
|
||||
cls.name for cls in all_classes if class_sig.name in cls.base_classes
|
||||
]
|
||||
subclasses = [cls.name for cls in all_classes if class_sig.name in cls.base_classes]
|
||||
|
||||
if len(subclasses) >= 1:
|
||||
evidence.append(f"Base class with {len(subclasses)} implementations")
|
||||
@@ -1467,8 +1446,7 @@ class ChainOfResponsibilityDetector(BasePatternDetector):
|
||||
|
||||
# Check for set_next() method
|
||||
has_set_next = any(
|
||||
"next" in m.name.lower()
|
||||
and ("set" in m.name.lower() or "add" in m.name.lower())
|
||||
"next" in m.name.lower() and ("set" in m.name.lower() or "add" in m.name.lower())
|
||||
for m in class_sig.methods
|
||||
)
|
||||
|
||||
@@ -1489,9 +1467,7 @@ class ChainOfResponsibilityDetector(BasePatternDetector):
|
||||
]
|
||||
|
||||
if siblings and has_next_ref:
|
||||
evidence.append(
|
||||
f"Part of handler chain with: {', '.join(siblings[:2])}"
|
||||
)
|
||||
evidence.append(f"Part of handler chain with: {', '.join(siblings[:2])}")
|
||||
confidence += 0.2
|
||||
|
||||
if confidence >= 0.5:
|
||||
@@ -1590,9 +1566,7 @@ class LanguageAdapter:
|
||||
pattern.evidence.append("Abstract Factory pattern")
|
||||
|
||||
# Template Method: Abstract classes common
|
||||
elif (
|
||||
pattern.pattern_type == "TemplateMethod" and "abstract" in evidence_str
|
||||
):
|
||||
elif pattern.pattern_type == "TemplateMethod" and "abstract" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.1, 1.0)
|
||||
|
||||
# Go adaptations
|
||||
@@ -1645,9 +1619,7 @@ class LanguageAdapter:
|
||||
pattern.evidence.append("Ruby Singleton module")
|
||||
|
||||
# Builder: Method chaining is idiomatic
|
||||
elif (
|
||||
pattern.pattern_type == "Builder" and "method chaining" in evidence_str
|
||||
):
|
||||
elif pattern.pattern_type == "Builder" and "method chaining" in evidence_str:
|
||||
pattern.confidence = min(pattern.confidence + 0.05, 1.0)
|
||||
|
||||
# PHP adaptations
|
||||
@@ -1702,9 +1674,7 @@ Supported Languages:
|
||||
action="append",
|
||||
help="Source file to analyze (can be specified multiple times)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--directory", help="Directory to analyze (analyzes all source files)"
|
||||
)
|
||||
parser.add_argument("--directory", help="Directory to analyze (analyzes all source files)")
|
||||
parser.add_argument(
|
||||
"--output", help="Output directory for results (default: current directory)"
|
||||
)
|
||||
|
||||
@@ -194,15 +194,11 @@ class PythonTestAnalyzer:
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
if self._is_test_class(node):
|
||||
examples.extend(
|
||||
self._extract_from_test_class(node, file_path, imports)
|
||||
)
|
||||
examples.extend(self._extract_from_test_class(node, file_path, imports))
|
||||
|
||||
# Find test functions (pytest)
|
||||
elif isinstance(node, ast.FunctionDef) and self._is_test_function(node):
|
||||
examples.extend(
|
||||
self._extract_from_test_function(node, file_path, imports)
|
||||
)
|
||||
examples.extend(self._extract_from_test_function(node, file_path, imports))
|
||||
|
||||
return examples
|
||||
|
||||
@@ -236,9 +232,7 @@ class PythonTestAnalyzer:
|
||||
return True
|
||||
# Has @pytest.mark decorator
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Attribute) and "pytest" in ast.unparse(
|
||||
decorator
|
||||
):
|
||||
if isinstance(decorator, ast.Attribute) and "pytest" in ast.unparse(decorator):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -255,9 +249,7 @@ class PythonTestAnalyzer:
|
||||
for node in class_node.body:
|
||||
if isinstance(node, ast.FunctionDef) and node.name.startswith("test_"):
|
||||
examples.extend(
|
||||
self._analyze_test_body(
|
||||
node, file_path, imports, setup_code=setup_code
|
||||
)
|
||||
self._analyze_test_body(node, file_path, imports, setup_code=setup_code)
|
||||
)
|
||||
|
||||
return examples
|
||||
@@ -269,9 +261,7 @@ class PythonTestAnalyzer:
|
||||
# Check for fixture parameters
|
||||
fixture_setup = self._extract_fixtures(func_node)
|
||||
|
||||
return self._analyze_test_body(
|
||||
func_node, file_path, imports, setup_code=fixture_setup
|
||||
)
|
||||
return self._analyze_test_body(func_node, file_path, imports, setup_code=fixture_setup)
|
||||
|
||||
def _extract_setup_method(self, class_node: ast.ClassDef) -> str | None:
|
||||
"""Extract setUp method code"""
|
||||
@@ -328,9 +318,7 @@ class PythonTestAnalyzer:
|
||||
examples.extend(configs)
|
||||
|
||||
# 4. Multi-step workflows (integration tests)
|
||||
workflows = self._find_workflows(
|
||||
func_node, file_path, docstring, setup_code, tags, imports
|
||||
)
|
||||
workflows = self._find_workflows(func_node, file_path, docstring, setup_code, tags, imports)
|
||||
examples.extend(workflows)
|
||||
|
||||
return examples
|
||||
@@ -491,9 +479,7 @@ class PythonTestAnalyzer:
|
||||
code=code,
|
||||
language="Python",
|
||||
description=f"Configuration example: {description}",
|
||||
expected_behavior=self._extract_assertion_after(
|
||||
func_node, node
|
||||
),
|
||||
expected_behavior=self._extract_assertion_after(func_node, node),
|
||||
setup_code=setup_code,
|
||||
file_path=file_path,
|
||||
line_start=node.lineno,
|
||||
@@ -594,9 +580,7 @@ class PythonTestAnalyzer:
|
||||
integration_keywords = ["workflow", "integration", "end_to_end", "e2e", "full"]
|
||||
return any(keyword in test_name for keyword in integration_keywords)
|
||||
|
||||
def _extract_assertion_after(
|
||||
self, func_node: ast.FunctionDef, target_node: ast.AST
|
||||
) -> str:
|
||||
def _extract_assertion_after(self, func_node: ast.FunctionDef, target_node: ast.AST) -> str:
|
||||
"""Find assertion that follows the target node"""
|
||||
found_target = False
|
||||
for stmt in func_node.body:
|
||||
@@ -727,8 +711,7 @@ class GenericTestAnalyzer:
|
||||
code=config_match.group(0),
|
||||
language=language,
|
||||
file_path=file_path,
|
||||
line_number=code[: start_pos + config_match.start()].count("\n")
|
||||
+ 1,
|
||||
line_number=code[: start_pos + config_match.start()].count("\n") + 1,
|
||||
)
|
||||
examples.append(example)
|
||||
|
||||
@@ -871,9 +854,7 @@ class TestExampleExtractor:
|
||||
logger.warning(f"⚠️ Failed to initialize AI enhancer: {e}")
|
||||
self.enhance_with_ai = False
|
||||
|
||||
def extract_from_directory(
|
||||
self, directory: Path, recursive: bool = True
|
||||
) -> ExampleReport:
|
||||
def extract_from_directory(self, directory: Path, recursive: bool = True) -> ExampleReport:
|
||||
"""Extract examples from all test files in directory"""
|
||||
directory = Path(directory)
|
||||
|
||||
@@ -927,13 +908,11 @@ class TestExampleExtractor:
|
||||
# Limit per file
|
||||
if len(filtered_examples) > self.max_per_file:
|
||||
# Sort by confidence and take top N
|
||||
filtered_examples = sorted(
|
||||
filtered_examples, key=lambda x: x.confidence, reverse=True
|
||||
)[: self.max_per_file]
|
||||
filtered_examples = sorted(filtered_examples, key=lambda x: x.confidence, reverse=True)[
|
||||
: self.max_per_file
|
||||
]
|
||||
|
||||
logger.info(
|
||||
f"Extracted {len(filtered_examples)} examples from {file_path.name}"
|
||||
)
|
||||
logger.info(f"Extracted {len(filtered_examples)} examples from {file_path.name}")
|
||||
|
||||
return filtered_examples
|
||||
|
||||
@@ -988,9 +967,7 @@ class TestExampleExtractor:
|
||||
|
||||
# Calculate averages
|
||||
avg_complexity = (
|
||||
sum(ex.complexity_score for ex in examples) / len(examples)
|
||||
if examples
|
||||
else 0.0
|
||||
sum(ex.complexity_score for ex in examples) / len(examples) if examples else 0.0
|
||||
)
|
||||
high_value_count = sum(1 for ex in examples if ex.confidence > 0.7)
|
||||
|
||||
@@ -1050,9 +1027,7 @@ Examples:
|
||||
help="Maximum examples per file (default: 10)",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="Output JSON format")
|
||||
parser.add_argument(
|
||||
"--markdown", action="store_true", help="Output Markdown format"
|
||||
)
|
||||
parser.add_argument("--markdown", action="store_true", help="Output Markdown format")
|
||||
parser.add_argument(
|
||||
"--recursive",
|
||||
action="store_true",
|
||||
@@ -1079,9 +1054,7 @@ Examples:
|
||||
examples = extractor.extract_from_file(Path(args.file))
|
||||
report = extractor._create_report(examples, file_path=args.file)
|
||||
else:
|
||||
report = extractor.extract_from_directory(
|
||||
Path(args.directory), recursive=args.recursive
|
||||
)
|
||||
report = extractor.extract_from_directory(Path(args.directory), recursive=args.recursive)
|
||||
|
||||
# Output results
|
||||
if args.json:
|
||||
|
||||
@@ -124,9 +124,7 @@ class UnifiedCodebaseAnalyzer:
|
||||
AnalysisResult with all 3 streams
|
||||
"""
|
||||
# Use three-stream fetcher
|
||||
fetcher = GitHubThreeStreamFetcher(
|
||||
repo_url, self.github_token, interactive=interactive
|
||||
)
|
||||
fetcher = GitHubThreeStreamFetcher(repo_url, self.github_token, interactive=interactive)
|
||||
three_streams = fetcher.fetch(output_dir)
|
||||
|
||||
# Analyze code with specified depth
|
||||
@@ -245,9 +243,7 @@ class UnifiedCodebaseAnalyzer:
|
||||
basic = self.basic_analysis(directory)
|
||||
|
||||
# Run full C3.x analysis using existing codebase_scraper
|
||||
print(
|
||||
"🔍 Running C3.x components (patterns, examples, guides, configs, architecture)..."
|
||||
)
|
||||
print("🔍 Running C3.x components (patterns, examples, guides, configs, architecture)...")
|
||||
|
||||
try:
|
||||
# Import codebase analyzer
|
||||
@@ -282,19 +278,11 @@ class UnifiedCodebaseAnalyzer:
|
||||
c3x = {**basic, "analysis_type": "c3x", **c3x_data}
|
||||
|
||||
print("✅ C3.x analysis complete!")
|
||||
print(
|
||||
f" - {len(c3x_data.get('c3_1_patterns', []))} design patterns detected"
|
||||
)
|
||||
print(
|
||||
f" - {c3x_data.get('c3_2_examples_count', 0)} test examples extracted"
|
||||
)
|
||||
print(
|
||||
f" - {len(c3x_data.get('c3_3_guides', []))} how-to guides generated"
|
||||
)
|
||||
print(f" - {len(c3x_data.get('c3_1_patterns', []))} design patterns detected")
|
||||
print(f" - {c3x_data.get('c3_2_examples_count', 0)} test examples extracted")
|
||||
print(f" - {len(c3x_data.get('c3_3_guides', []))} how-to guides generated")
|
||||
print(f" - {len(c3x_data.get('c3_4_configs', []))} config files analyzed")
|
||||
print(
|
||||
f" - {len(c3x_data.get('c3_7_architecture', []))} architectural patterns found"
|
||||
)
|
||||
print(f" - {len(c3x_data.get('c3_7_architecture', []))} architectural patterns found")
|
||||
|
||||
return c3x
|
||||
|
||||
@@ -451,9 +439,7 @@ class UnifiedCodebaseAnalyzer:
|
||||
|
||||
if item.is_dir():
|
||||
# Only include immediate subdirectories
|
||||
structure["children"].append(
|
||||
{"name": item.name, "type": "directory"}
|
||||
)
|
||||
structure["children"].append({"name": item.name, "type": "directory"})
|
||||
elif item.is_file():
|
||||
structure["children"].append(
|
||||
{"name": item.name, "type": "file", "extension": item.suffix}
|
||||
|
||||
Reference in New Issue
Block a user