style: Format code with ruff formatter

- Auto-format 11 files to comply with ruff formatting standards
- Fixes CI/CD formatter check failures

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
yusyus
2026-02-03 21:37:54 +03:00
parent b01dfc5251
commit 4e8ad835ed
12 changed files with 271 additions and 231 deletions

View File

@@ -36,6 +36,7 @@ logger = logging.getLogger(__name__)
# Import config manager for settings
try:
from skill_seekers.cli.config_manager import get_config_manager
CONFIG_AVAILABLE = True
except ImportError:
CONFIG_AVAILABLE = False
@@ -107,7 +108,9 @@ class AIEnhancer:
logger.warning("⚠️ anthropic package not installed, falling back to LOCAL mode")
self.mode = "local"
except Exception as e:
logger.warning(f"⚠️ Failed to initialize API client: {e}, falling back to LOCAL mode")
logger.warning(
f"⚠️ Failed to initialize API client: {e}, falling back to LOCAL mode"
)
self.mode = "local"
if self.mode == "local" and self.enabled:
@@ -212,7 +215,8 @@ DO NOT include any explanation - just write the JSON file.
except json.JSONDecodeError:
# Try to find JSON in the response
import re
json_match = re.search(r'\[[\s\S]*\]|\{[\s\S]*\}', response_text)
json_match = re.search(r"\[[\s\S]*\]|\{[\s\S]*\}", response_text)
if json_match:
return json_match.group()
logger.warning("⚠️ Could not parse JSON from LOCAL response")

View File

@@ -89,7 +89,13 @@ class ArchitecturalPatternDetector:
# Framework detection patterns
FRAMEWORK_MARKERS = {
# Game Engines (checked first to avoid false positives)
"Unity": ["Assembly-CSharp.csproj", "UnityEngine.dll", "ProjectSettings/ProjectVersion.txt", ".unity", "Library/"],
"Unity": [
"Assembly-CSharp.csproj",
"UnityEngine.dll",
"ProjectSettings/ProjectVersion.txt",
".unity",
"Library/",
],
"Unreal": ["Source/", ".uproject", "Config/DefaultEngine.ini", "Binaries/", "Content/"],
"Godot": ["project.godot", ".godot", ".tscn", ".tres", ".gd"],
# Web Frameworks

View File

@@ -1429,7 +1429,6 @@ class CodeAnalyzer:
return comments
def _analyze_godot_scene(self, content: str, file_path: str) -> dict[str, Any]:
"""
Analyze Godot .tscn scene file.
@@ -1445,34 +1444,29 @@ class CodeAnalyzer:
scripts = []
# Extract external resources
for match in re.finditer(r'\[ext_resource.*?type="(.+?)".*?path="(.+?)".*?id="(.+?)"\]', content):
for match in re.finditer(
r'\[ext_resource.*?type="(.+?)".*?path="(.+?)".*?id="(.+?)"\]', content
):
res_type, path, res_id = match.groups()
resources.append({
"type": res_type,
"path": path,
"id": res_id
})
resources.append({"type": res_type, "path": path, "id": res_id})
# Track scripts separately
if res_type == "Script":
scripts.append({
"path": path,
"id": res_id
})
scripts.append({"path": path, "id": res_id})
# Extract nodes
for match in re.finditer(r'\[node name="(.+?)".*?type="(.+?)".*?\]', content):
node_name, node_type = match.groups()
# Check if node has a script attached
script_match = re.search(rf'\[node name="{re.escape(node_name)}".*?script = ExtResource\("(.+?)"\)', content, re.DOTALL)
script_match = re.search(
rf'\[node name="{re.escape(node_name)}".*?script = ExtResource\("(.+?)"\)',
content,
re.DOTALL,
)
attached_script = script_match.group(1) if script_match else None
nodes.append({
"name": node_name,
"type": node_type,
"script": attached_script
})
nodes.append({"name": node_name, "type": node_type, "script": attached_script})
return {
"file": file_path,
@@ -1482,8 +1476,8 @@ class CodeAnalyzer:
"scene_metadata": {
"node_count": len(nodes),
"script_count": len(scripts),
"resource_count": len(resources)
}
"resource_count": len(resources),
},
}
def _analyze_godot_resource(self, content: str, file_path: str) -> dict[str, Any]:
@@ -1503,35 +1497,32 @@ class CodeAnalyzer:
script_path = None
# Extract resource header
header_match = re.search(r'\[gd_resource type="(.+?)"(?:\s+script_class="(.+?)")?\s+', content)
header_match = re.search(
r'\[gd_resource type="(.+?)"(?:\s+script_class="(.+?)")?\s+', content
)
if header_match:
resource_type = header_match.group(1)
script_class = header_match.group(2)
# Extract external resources
for match in re.finditer(r'\[ext_resource.*?type="(.+?)".*?path="(.+?)".*?id="(.+?)"\]', content):
for match in re.finditer(
r'\[ext_resource.*?type="(.+?)".*?path="(.+?)".*?id="(.+?)"\]', content
):
res_type, path, res_id = match.groups()
resources.append({
"type": res_type,
"path": path,
"id": res_id
})
resources.append({"type": res_type, "path": path, "id": res_id})
if res_type == "Script":
script_path = path
# Extract properties from [resource] section
resource_section = re.search(r'\[resource\](.*?)(?:\n\[|$)', content, re.DOTALL)
resource_section = re.search(r"\[resource\](.*?)(?:\n\[|$)", content, re.DOTALL)
if resource_section:
prop_text = resource_section.group(1)
for line in prop_text.strip().split('\n'):
if '=' in line:
key, value = line.split('=', 1)
properties.append({
"name": key.strip(),
"value": value.strip()
})
for line in prop_text.strip().split("\n"):
if "=" in line:
key, value = line.split("=", 1)
properties.append({"name": key.strip(), "value": value.strip()})
return {
"file": file_path,
@@ -1542,8 +1533,8 @@ class CodeAnalyzer:
"resources": resources,
"resource_metadata": {
"property_count": len(properties),
"dependency_count": len(resources)
}
"dependency_count": len(resources),
},
}
def _analyze_godot_shader(self, content: str, file_path: str) -> dict[str, Any]:
@@ -1562,35 +1553,26 @@ class CodeAnalyzer:
shader_type = None
# Extract shader type
type_match = re.search(r'shader_type\s+(\w+)', content)
type_match = re.search(r"shader_type\s+(\w+)", content)
if type_match:
shader_type = type_match.group(1)
# Extract uniforms
for match in re.finditer(r'uniform\s+(\w+)\s+(\w+)(?:\s*:\s*(.+?))?(?:\s*=\s*(.+?))?;', content):
for match in re.finditer(
r"uniform\s+(\w+)\s+(\w+)(?:\s*:\s*(.+?))?(?:\s*=\s*(.+?))?;", content
):
uniform_type, name, hint, default = match.groups()
uniforms.append({
"name": name,
"type": uniform_type,
"hint": hint,
"default": default
})
uniforms.append({"name": name, "type": uniform_type, "hint": hint, "default": default})
# Extract varying variables
for match in re.finditer(r'varying\s+(\w+)\s+(\w+)', content):
for match in re.finditer(r"varying\s+(\w+)\s+(\w+)", content):
var_type, name = match.groups()
varyings.append({
"name": name,
"type": var_type
})
varyings.append({"name": name, "type": var_type})
# Extract functions
for match in re.finditer(r'void\s+(\w+)\s*\(([^)]*)\)', content):
for match in re.finditer(r"void\s+(\w+)\s*\(([^)]*)\)", content):
func_name, params = match.groups()
functions.append({
"name": func_name,
"parameters": params.strip() if params else ""
})
functions.append({"name": func_name, "parameters": params.strip() if params else ""})
return {
"file": file_path,
@@ -1598,10 +1580,7 @@ class CodeAnalyzer:
"uniforms": uniforms,
"varyings": varyings,
"functions": functions,
"shader_metadata": {
"uniform_count": len(uniforms),
"function_count": len(functions)
}
"shader_metadata": {"uniform_count": len(uniforms), "function_count": len(functions)},
}
def _analyze_gdscript(self, content: str, file_path: str) -> dict[str, Any]:
@@ -1621,113 +1600,131 @@ class CodeAnalyzer:
exports = []
# Extract class definition
class_match = re.search(r'class_name\s+(\w+)(?:\s+extends\s+(\w+))?', content)
class_match = re.search(r"class_name\s+(\w+)(?:\s+extends\s+(\w+))?", content)
if class_match:
class_name = class_match.group(1)
extends = class_match.group(2)
classes.append({
"name": class_name,
"bases": [extends] if extends else [],
"methods": [],
"line_number": content[: class_match.start()].count("\n") + 1
})
classes.append(
{
"name": class_name,
"bases": [extends] if extends else [],
"methods": [],
"line_number": content[: class_match.start()].count("\n") + 1,
}
)
# Extract functions
for match in re.finditer(r'func\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*(\w+))?:', content):
for match in re.finditer(r"func\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*(\w+))?:", content):
func_name, params, return_type = match.groups()
# Parse parameters
param_list = []
if params.strip():
for param in params.split(','):
for param in params.split(","):
param = param.strip()
if ':' in param:
if ":" in param:
# param_name: Type = default
parts = param.split(':')
parts = param.split(":")
name = parts[0].strip()
type_and_default = parts[1].strip()
param_type = type_and_default.split('=')[0].strip() if '=' in type_and_default else type_and_default
default = type_and_default.split('=')[1].strip() if '=' in type_and_default else None
param_type = (
type_and_default.split("=")[0].strip()
if "=" in type_and_default
else type_and_default
)
default = (
type_and_default.split("=")[1].strip()
if "=" in type_and_default
else None
)
param_list.append({
"name": name,
"type_hint": param_type,
"default": default
})
param_list.append(
{"name": name, "type_hint": param_type, "default": default}
)
else:
param_list.append({
"name": param,
"type_hint": None,
"default": None
})
param_list.append({"name": param, "type_hint": None, "default": None})
functions.append({
"name": func_name,
"parameters": param_list,
"return_type": return_type,
"line_number": content[: match.start()].count("\n") + 1
})
functions.append(
{
"name": func_name,
"parameters": param_list,
"return_type": return_type,
"line_number": content[: match.start()].count("\n") + 1,
}
)
# Extract signals with documentation
signal_connections = []
signal_emissions = []
for match in re.finditer(r'signal\s+(\w+)(?:\(([^)]*)\))?', content):
for match in re.finditer(r"signal\s+(\w+)(?:\(([^)]*)\))?", content):
signal_name, params = match.groups()
line_number = content[: match.start()].count("\n") + 1
# Extract documentation comment above signal (## or #)
doc_comment = None
lines = content[:match.start()].split('\n')
lines = content[: match.start()].split("\n")
if len(lines) >= 2:
prev_line = lines[-1].strip()
if prev_line.startswith('##') or prev_line.startswith('#'):
doc_comment = prev_line.lstrip('#').strip()
if prev_line.startswith("##") or prev_line.startswith("#"):
doc_comment = prev_line.lstrip("#").strip()
signals.append({
"name": signal_name,
"parameters": params if params else "",
"line_number": line_number,
"documentation": doc_comment
})
signals.append(
{
"name": signal_name,
"parameters": params if params else "",
"line_number": line_number,
"documentation": doc_comment,
}
)
# Extract signal connections (.connect() calls)
for match in re.finditer(r'(\w+(?:\.\w+)*)\.connect\(([^)]+)\)', content):
for match in re.finditer(r"(\w+(?:\.\w+)*)\.connect\(([^)]+)\)", content):
signal_path, handler = match.groups()
signal_connections.append({
"signal": signal_path,
"handler": handler.strip(),
"line_number": content[: match.start()].count("\n") + 1
})
signal_connections.append(
{
"signal": signal_path,
"handler": handler.strip(),
"line_number": content[: match.start()].count("\n") + 1,
}
)
# Extract signal emissions (.emit() calls)
for match in re.finditer(r'(\w+(?:\.\w+)*)\.emit\(([^)]*)\)', content):
for match in re.finditer(r"(\w+(?:\.\w+)*)\.emit\(([^)]*)\)", content):
signal_path, args = match.groups()
signal_emissions.append({
"signal": signal_path,
"arguments": args.strip() if args else "",
"line_number": content[: match.start()].count("\n") + 1
})
signal_emissions.append(
{
"signal": signal_path,
"arguments": args.strip() if args else "",
"line_number": content[: match.start()].count("\n") + 1,
}
)
# Extract @export variables
for match in re.finditer(r'@export(?:\(([^)]+)\))?\s+var\s+(\w+)(?:\s*:\s*(\w+))?(?:\s*=\s*(.+?))?(?:\n|$)', content):
for match in re.finditer(
r"@export(?:\(([^)]+)\))?\s+var\s+(\w+)(?:\s*:\s*(\w+))?(?:\s*=\s*(.+?))?(?:\n|$)",
content,
):
hint, var_name, var_type, default = match.groups()
exports.append({
"name": var_name,
"type": var_type,
"default": default,
"export_hint": hint,
"line_number": content[: match.start()].count("\n") + 1
})
exports.append(
{
"name": var_name,
"type": var_type,
"default": default,
"export_hint": hint,
"line_number": content[: match.start()].count("\n") + 1,
}
)
# Detect test framework
test_framework = None
test_functions = []
# GUT (Godot Unit Test) - extends "res://addons/gut/test.gd" or extends GutTest
if re.search(r'extends\s+["\']?res://addons/gut/test\.gd["\']?', content) or \
re.search(r'extends\s+GutTest', content):
if re.search(r'extends\s+["\']?res://addons/gut/test\.gd["\']?', content) or re.search(
r"extends\s+GutTest", content
):
test_framework = "GUT"
# Extract test functions (test_* functions)
@@ -1736,23 +1733,23 @@ class CodeAnalyzer:
test_functions.append(func)
# gdUnit4 - @suite class annotation
elif re.search(r'@suite', content):
elif re.search(r"@suite", content):
test_framework = "gdUnit4"
# Extract test functions (@test annotated or test_* prefix)
for i, func in enumerate(functions):
# Check for @test annotation above function
func_line = func["line_number"]
lines = content.split('\n')
lines = content.split("\n")
if func_line > 1:
prev_line = lines[func_line - 2].strip()
if prev_line.startswith('@test'):
if prev_line.startswith("@test"):
test_functions.append(func)
elif func["name"].startswith("test_"):
test_functions.append(func)
# WAT (WizAds Test) - less common
elif re.search(r'extends\s+WAT\.Test', content):
elif re.search(r"extends\s+WAT\.Test", content):
test_framework = "WAT"
for func in functions:
if func["name"].startswith("test_"):
@@ -1815,4 +1812,3 @@ def create_sprite(texture: str) -> Node2D:
]
)
print(f" {method['name']}({params}) -> {method['return_type']}")

View File

@@ -401,11 +401,13 @@ def extract_markdown_structure(content: str) -> dict[str, Any]:
if header_match:
level = len(header_match.group(1))
text = header_match.group(2).strip()
structure["headers"].append({
"level": level,
"text": text,
"line": i + 1,
})
structure["headers"].append(
{
"level": level,
"text": text,
"line": i + 1,
}
)
# First h1 is the title
if level == 1 and structure["title"] is None:
structure["title"] = text
@@ -416,24 +418,30 @@ def extract_markdown_structure(content: str) -> dict[str, Any]:
language = match.group(1) or "text"
code = match.group(2).strip()
if len(code) > 0:
structure["code_blocks"].append({
"language": language,
"code": code[:500], # Truncate long code blocks
"full_length": len(code),
})
structure["code_blocks"].append(
{
"language": language,
"code": code[:500], # Truncate long code blocks
"full_length": len(code),
}
)
# Extract links
link_pattern = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
for match in link_pattern.finditer(content):
structure["links"].append({
"text": match.group(1),
"url": match.group(2),
})
structure["links"].append(
{
"text": match.group(1),
"url": match.group(2),
}
)
return structure
def generate_markdown_summary(content: str, structure: dict[str, Any], max_length: int = 500) -> str:
def generate_markdown_summary(
content: str, structure: dict[str, Any], max_length: int = 500
) -> str:
"""
Generate a summary of markdown content.
@@ -546,12 +554,14 @@ def process_markdown_docs(
structure = extract_markdown_structure(content)
summary = generate_markdown_summary(content, structure)
doc_data.update({
"title": structure.get("title") or md_path.stem,
"structure": structure,
"summary": summary,
"content": content if depth == "full" else None,
})
doc_data.update(
{
"title": structure.get("title") or md_path.stem,
"structure": structure,
"summary": summary,
"content": content if depth == "full" else None,
}
)
processed_docs.append(doc_data)
# Track categories
@@ -587,6 +597,7 @@ def process_markdown_docs(
# Copy file to category folder
dest_path = category_dir / doc["filename"]
import shutil
shutil.copy2(src_path, dest_path)
except Exception as e:
logger.debug(f"Failed to copy {doc['path']}: {e}")
@@ -602,7 +613,9 @@ def process_markdown_docs(
with open(index_json, "w", encoding="utf-8") as f:
json.dump(index_data, f, indent=2, default=str)
logger.info(f"✅ Processed {len(processed_docs)} documentation files in {len(categories)} categories")
logger.info(
f"✅ Processed {len(processed_docs)} documentation files in {len(categories)} categories"
)
logger.info(f"📁 Saved to: {docs_output_dir}")
return index_data
@@ -636,18 +649,22 @@ def _enhance_docs_api(docs: list[dict], api_key: str) -> list[dict]:
"""Enhance docs using Claude API."""
try:
import anthropic
client = anthropic.Anthropic(api_key=api_key)
# Batch documents for efficiency
batch_size = 10
for i in range(0, len(docs), batch_size):
batch = docs[i:i + batch_size]
batch = docs[i : i + batch_size]
# Create prompt for batch
docs_text = "\n\n".join([
f"## {d.get('title', d['filename'])}\nCategory: {d['category']}\nSummary: {d.get('summary', 'N/A')}"
for d in batch if d.get("summary")
])
docs_text = "\n\n".join(
[
f"## {d.get('title', d['filename'])}\nCategory: {d['category']}\nSummary: {d.get('summary', 'N/A')}"
for d in batch
if d.get("summary")
]
)
if not docs_text:
continue
@@ -666,12 +683,13 @@ Return JSON with format:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
messages=[{"role": "user", "content": prompt}],
)
# Parse response and merge enhancements
try:
import re
json_match = re.search(r"\{.*\}", response.content[0].text, re.DOTALL)
if json_match:
enhancements = json.loads(json_match.group())
@@ -700,10 +718,12 @@ def _enhance_docs_local(docs: list[dict]) -> list[dict]:
if not docs_with_summary:
return docs
docs_text = "\n\n".join([
f"## {d.get('title', d['filename'])}\nCategory: {d['category']}\nPath: {d['path']}\nSummary: {d.get('summary', 'N/A')}"
for d in docs_with_summary[:20] # Limit to 20 docs
])
docs_text = "\n\n".join(
[
f"## {d.get('title', d['filename'])}\nCategory: {d['category']}\nPath: {d['path']}\nSummary: {d.get('summary', 'N/A')}"
for d in docs_with_summary[:20] # Limit to 20 docs
]
)
prompt = f"""Analyze these documentation files from a codebase and provide insights.
@@ -734,6 +754,7 @@ Output JSON only:
if result.returncode == 0 and result.stdout:
import re
json_match = re.search(r"\{.*\}", result.stdout, re.DOTALL)
if json_match:
enhancements = json.loads(json_match.group())
@@ -801,7 +822,9 @@ def analyze_codebase(
if enhance_level > 0:
level_names = {1: "SKILL.md only", 2: "SKILL.md+Architecture+Config", 3: "full"}
logger.info(f"🤖 AI Enhancement Level: {enhance_level} ({level_names.get(enhance_level, 'unknown')})")
logger.info(
f"🤖 AI Enhancement Level: {enhance_level} ({level_names.get(enhance_level, 'unknown')})"
)
# Resolve directory to absolute path to avoid relative_to() errors
directory = Path(directory).resolve()
@@ -1411,7 +1434,9 @@ Use this skill when you need to:
skill_content += "- **Architecture**: `references/architecture/` - Architectural patterns\n"
refs_added = True
if extract_docs and (output_dir / "documentation").exists():
skill_content += "- **Documentation**: `references/documentation/` - Project documentation\n"
skill_content += (
"- **Documentation**: `references/documentation/` - Project documentation\n"
)
refs_added = True
if not refs_added:
@@ -1691,10 +1716,7 @@ def _format_signal_flow_section(output_dir: Path, results: dict[str, Any]) -> st
content += "\n"
# Test framework detection
test_files = [
f for f in results.get("files", [])
if f.get("test_framework")
]
test_files = [f for f in results.get("files", []) if f.get("test_framework")]
if test_files:
frameworks = {}
@@ -1732,7 +1754,15 @@ def _format_documentation_section(_output_dir: Path, docs_data: dict[str, Any])
content += f"**Categories:** {len(categories)}\n\n"
# List documents by category (most important first)
priority_order = ["overview", "architecture", "guides", "workflows", "features", "api", "examples"]
priority_order = [
"overview",
"architecture",
"guides",
"workflows",
"features",
"api",
"examples",
]
# Sort categories by priority
sorted_categories = []
@@ -1779,6 +1809,7 @@ def _format_documentation_section(_output_dir: Path, docs_data: dict[str, Any])
if all_topics:
# Deduplicate and count
from collections import Counter
topic_counts = Counter(all_topics)
top_topics = [t for t, _ in topic_counts.most_common(10)]
content += f"**Key Topics:** {', '.join(top_topics)}\n\n"

View File

@@ -167,9 +167,7 @@ class ConfigEnhancer:
for setting in cf.get("settings", [])[:5]: # First 5 settings per file
# Support both "type" (from config_extractor) and "value_type" (legacy)
value_type = setting.get("type", setting.get("value_type", "unknown"))
settings_summary.append(
f" - {setting['key']}: {setting['value']} ({value_type})"
)
settings_summary.append(f" - {setting['key']}: {setting['value']} ({value_type})")
# Support both "type" (from config_extractor) and "config_type" (legacy)
config_type = cf.get("type", cf.get("config_type", "unknown"))
@@ -306,7 +304,9 @@ Focus on actionable insights that help developers understand and improve their c
config_type = cf.get("type", cf.get("config_type", "unknown"))
settings_preview = []
for s in cf.get("settings", [])[:3]: # Show first 3 settings
settings_preview.append(f" - {s.get('key', 'unknown')}: {str(s.get('value', ''))[:50]}")
settings_preview.append(
f" - {s.get('key', 'unknown')}: {str(s.get('value', ''))[:50]}"
)
config_data.append(f"""
### {cf["relative_path"]} ({config_type})
@@ -431,9 +431,7 @@ DO NOT explain your work - just write the JSON file directly.
potential_files.append(json_file)
# Try to load the most recent JSON file with expected structure
for json_file in sorted(
potential_files, key=lambda f: f.stat().st_mtime, reverse=True
):
for json_file in sorted(potential_files, key=lambda f: f.stat().st_mtime, reverse=True):
try:
with open(json_file) as f:
data = json.load(f)

View File

@@ -429,7 +429,9 @@ class ConfigParser:
# JSON array at root - extract from each dict item
for idx, item in enumerate(data):
if isinstance(item, dict):
self._extract_settings_from_dict(item, config_file, parent_path=[f"[{idx}]"])
self._extract_settings_from_dict(
item, config_file, parent_path=[f"[{idx}]"]
)
else:
# Primitive value at root (string, number, etc.) - skip
logger.debug(f"Skipping JSON with primitive root: {config_file.relative_path}")
@@ -452,7 +454,9 @@ class ConfigParser:
# YAML array at root - extract from each dict item
for idx, item in enumerate(data):
if isinstance(item, dict):
self._extract_settings_from_dict(item, config_file, parent_path=[f"[{idx}]"])
self._extract_settings_from_dict(
item, config_file, parent_path=[f"[{idx}]"]
)
except yaml.YAMLError as e:
config_file.parse_errors.append(f"YAML parse error: {str(e)}")

View File

@@ -272,7 +272,7 @@ class DependencyAnalyzer:
# Extract extends with class name: extends MyBaseClass
# Note: This creates a symbolic dependency that may not resolve to a file
extends_class_pattern = r'extends\s+([A-Z]\w+)'
extends_class_pattern = r"extends\s+([A-Z]\w+)"
for match in re.finditer(extends_class_pattern, content):
class_name = match.group(1)
line_num = content[: match.start()].count("\n") + 1

View File

@@ -300,14 +300,16 @@ For more information: https://github.com/yusufkaraaslan/Skill_Seekers
)
analyze_parser.add_argument("--file-patterns", help="Comma-separated file patterns")
analyze_parser.add_argument(
"--enhance", action="store_true", help="Enable AI enhancement (default level 1 = SKILL.md only)"
"--enhance",
action="store_true",
help="Enable AI enhancement (default level 1 = SKILL.md only)",
)
analyze_parser.add_argument(
"--enhance-level",
type=int,
choices=[0, 1, 2, 3],
default=None,
help="AI enhancement level: 0=off, 1=SKILL.md only (default), 2=+Architecture+Config, 3=full"
help="AI enhancement level: 0=off, 1=SKILL.md only (default), 2=+Architecture+Config, 3=full",
)
analyze_parser.add_argument("--skip-api-reference", action="store_true", help="Skip API docs")
analyze_parser.add_argument(
@@ -321,7 +323,9 @@ For more information: https://github.com/yusufkaraaslan/Skill_Seekers
)
analyze_parser.add_argument("--skip-how-to-guides", action="store_true", help="Skip guides")
analyze_parser.add_argument("--skip-config-patterns", action="store_true", help="Skip config")
analyze_parser.add_argument("--skip-docs", action="store_true", help="Skip project docs (README, docs/)")
analyze_parser.add_argument(
"--skip-docs", action="store_true", help="Skip project docs (README, docs/)"
)
analyze_parser.add_argument("--no-comments", action="store_true", help="Skip comments")
analyze_parser.add_argument("--verbose", action="store_true", help="Verbose logging")
@@ -565,13 +569,16 @@ def main(argv: list[str] | None = None) -> int:
# Handle preset flags (depth and features)
if args.quick:
# Quick = surface depth + skip advanced features + no AI
sys.argv.extend([
"--depth", "surface",
"--skip-patterns",
"--skip-test-examples",
"--skip-how-to-guides",
"--skip-config-patterns",
])
sys.argv.extend(
[
"--depth",
"surface",
"--skip-patterns",
"--skip-test-examples",
"--skip-how-to-guides",
"--skip-config-patterns",
]
)
elif args.comprehensive:
# Comprehensive = full depth + all features (AI level is separate)
sys.argv.extend(["--depth", "full"])
@@ -588,6 +595,7 @@ def main(argv: list[str] | None = None) -> int:
# Use default from config (default: 1)
try:
from skill_seekers.cli.config_manager import get_config_manager
config = get_config_manager()
enhance_level = config.get_default_enhance_level()
except Exception:

View File

@@ -144,9 +144,7 @@ class SignalFlowAnalyzer:
# Observer pattern - signals with multiple connections
multi_connected = {
sig: len(conns)
for sig, conns in self.signal_connections.items()
if len(conns) >= 3
sig: len(conns) for sig, conns in self.signal_connections.items() if len(conns) >= 3
}
if multi_connected:
@@ -170,9 +168,7 @@ class SignalFlowAnalyzer:
def _calculate_statistics(self) -> dict[str, Any]:
"""Calculate signal usage statistics."""
total_signals = len(self.signal_declarations)
total_connections = sum(
len(conns) for conns in self.signal_connections.values()
)
total_connections = sum(len(conns) for conns in self.signal_connections.values())
total_emissions = sum(len(emits) for emits in self.signal_emissions.items())
# Find most connected signals
@@ -181,17 +177,13 @@ class SignalFlowAnalyzer:
)[:5]
# Find most emitted signals
most_emitted = sorted(
self.signal_emissions.items(), key=lambda x: len(x[1]), reverse=True
)[:5]
most_emitted = sorted(self.signal_emissions.items(), key=lambda x: len(x[1]), reverse=True)[
:5
]
# Signal density (signals per GDScript file)
gdscript_files = sum(
1 for f in self.files if f.get("language") == "GDScript"
)
signal_density = (
total_signals / gdscript_files if gdscript_files > 0 else 0
)
gdscript_files = sum(1 for f in self.files if f.get("language") == "GDScript")
signal_density = total_signals / gdscript_files if gdscript_files > 0 else 0
return {
"total_signals": total_signals,
@@ -200,12 +192,10 @@ class SignalFlowAnalyzer:
"signal_density": round(signal_density, 2),
"gdscript_files": gdscript_files,
"most_connected_signals": [
{"signal": sig, "connection_count": len(conns)}
for sig, conns in most_connected
{"signal": sig, "connection_count": len(conns)} for sig, conns in most_connected
],
"most_emitted_signals": [
{"signal": sig, "emission_count": len(emits)}
for sig, emits in most_emitted
{"signal": sig, "emission_count": len(emits)} for sig, emits in most_emitted
],
}
@@ -272,9 +262,7 @@ class SignalFlowAnalyzer:
return patterns[:10] # Top 10 most used signals
def generate_how_to_guides(
self, output_dir: Path, ai_mode: str = "LOCAL"
) -> str:
def generate_how_to_guides(self, output_dir: Path, ai_mode: str = "LOCAL") -> str:
"""
Generate signal-based how-to guides using AI.
@@ -297,7 +285,9 @@ class SignalFlowAnalyzer:
for i, pattern in enumerate(patterns, 1):
signal_name = pattern["signal_name"]
guide_content += f"{i}. [How to use `{signal_name}`](#{signal_name.lower().replace('_', '-')})\n"
guide_content += (
f"{i}. [How to use `{signal_name}`](#{signal_name.lower().replace('_', '-')})\n"
)
guide_content += "\n---\n\n"
@@ -313,9 +303,7 @@ class SignalFlowAnalyzer:
return str(guide_file)
def _generate_signal_guide(
self, pattern: dict[str, Any], ai_mode: str
) -> str:
def _generate_signal_guide(self, pattern: dict[str, Any], ai_mode: str) -> str:
"""
Generate a how-to guide for a single signal using AI.
@@ -434,10 +422,12 @@ class SignalFlowAnalyzer:
guide_file = self.generate_how_to_guides(output_dir, ai_mode)
if guide_file:
import logging
logger = logging.getLogger(__name__)
logger.info(f"📚 Generated signal how-to guides: {guide_file}")
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.warning(f"Failed to generate signal how-to guides: {e}")
@@ -453,9 +443,7 @@ class SignalFlowAnalyzer:
lines.append(f"- **Total Signals**: {stats['total_signals']}")
lines.append(f"- **Total Connections**: {stats['total_connections']}")
lines.append(f"- **Total Emissions**: {stats['total_emissions']}")
lines.append(
f"- **Signal Density**: {stats['signal_density']} signals per file\n"
)
lines.append(f"- **Signal Density**: {stats['signal_density']} signals per file\n")
# Patterns
if analysis["patterns"]:
@@ -480,9 +468,7 @@ class SignalFlowAnalyzer:
if stats["most_connected_signals"]:
lines.append("## Most Connected Signals\n")
for item in stats["most_connected_signals"]:
lines.append(
f"- **{item['signal']}**: {item['connection_count']} connections"
)
lines.append(f"- **{item['signal']}**: {item['connection_count']} connections")
lines.append("")
with open(output_dir / "signal_reference.md", "w") as f:

View File

@@ -816,7 +816,11 @@ class GenericTestAnalyzer:
# Find next method (setup or test)
next_pattern = patterns.get("setup", patterns["test_function"])
next_setup = re.search(next_pattern, code[setup_start:])
setup_end = setup_start + next_setup.start() if next_setup else min(setup_start + 500, len(code))
setup_end = (
setup_start + next_setup.start()
if next_setup
else min(setup_start + 500, len(code))
)
setup_body = code[setup_start:setup_end]
example = self._create_example(

View File

@@ -63,17 +63,20 @@ class TestAnalyzeSubcommand(unittest.TestCase):
def test_all_skip_flags(self):
"""Test all skip flags are properly parsed."""
args = self.parser.parse_args([
"analyze",
"--directory", ".",
"--skip-api-reference",
"--skip-dependency-graph",
"--skip-patterns",
"--skip-test-examples",
"--skip-how-to-guides",
"--skip-config-patterns",
"--skip-docs"
])
args = self.parser.parse_args(
[
"analyze",
"--directory",
".",
"--skip-api-reference",
"--skip-dependency-graph",
"--skip-patterns",
"--skip-test-examples",
"--skip-how-to-guides",
"--skip-config-patterns",
"--skip-docs",
]
)
self.assertTrue(args.skip_api_reference)
self.assertTrue(args.skip_dependency_graph)
self.assertTrue(args.skip_patterns)

2
uv.lock generated
View File

@@ -1846,7 +1846,7 @@ wheels = [
[[package]]
name = "skill-seekers"
version = "2.7.4"
version = "2.9.0"
source = { editable = "." }
dependencies = [
{ name = "anthropic" },