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:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']}")
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user