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
|
# Import config manager for settings
|
||||||
try:
|
try:
|
||||||
from skill_seekers.cli.config_manager import get_config_manager
|
from skill_seekers.cli.config_manager import get_config_manager
|
||||||
|
|
||||||
CONFIG_AVAILABLE = True
|
CONFIG_AVAILABLE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
CONFIG_AVAILABLE = False
|
CONFIG_AVAILABLE = False
|
||||||
@@ -107,7 +108,9 @@ class AIEnhancer:
|
|||||||
logger.warning("⚠️ anthropic package not installed, falling back to LOCAL mode")
|
logger.warning("⚠️ anthropic package not installed, falling back to LOCAL mode")
|
||||||
self.mode = "local"
|
self.mode = "local"
|
||||||
except Exception as e:
|
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"
|
self.mode = "local"
|
||||||
|
|
||||||
if self.mode == "local" and self.enabled:
|
if self.mode == "local" and self.enabled:
|
||||||
@@ -212,7 +215,8 @@ DO NOT include any explanation - just write the JSON file.
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
# Try to find JSON in the response
|
# Try to find JSON in the response
|
||||||
import re
|
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:
|
if json_match:
|
||||||
return json_match.group()
|
return json_match.group()
|
||||||
logger.warning("⚠️ Could not parse JSON from LOCAL response")
|
logger.warning("⚠️ Could not parse JSON from LOCAL response")
|
||||||
|
|||||||
@@ -89,7 +89,13 @@ class ArchitecturalPatternDetector:
|
|||||||
# Framework detection patterns
|
# Framework detection patterns
|
||||||
FRAMEWORK_MARKERS = {
|
FRAMEWORK_MARKERS = {
|
||||||
# Game Engines (checked first to avoid false positives)
|
# 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/"],
|
"Unreal": ["Source/", ".uproject", "Config/DefaultEngine.ini", "Binaries/", "Content/"],
|
||||||
"Godot": ["project.godot", ".godot", ".tscn", ".tres", ".gd"],
|
"Godot": ["project.godot", ".godot", ".tscn", ".tres", ".gd"],
|
||||||
# Web Frameworks
|
# Web Frameworks
|
||||||
|
|||||||
@@ -1429,7 +1429,6 @@ class CodeAnalyzer:
|
|||||||
|
|
||||||
return comments
|
return comments
|
||||||
|
|
||||||
|
|
||||||
def _analyze_godot_scene(self, content: str, file_path: str) -> dict[str, Any]:
|
def _analyze_godot_scene(self, content: str, file_path: str) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Analyze Godot .tscn scene file.
|
Analyze Godot .tscn scene file.
|
||||||
@@ -1445,34 +1444,29 @@ class CodeAnalyzer:
|
|||||||
scripts = []
|
scripts = []
|
||||||
|
|
||||||
# Extract external resources
|
# 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()
|
res_type, path, res_id = match.groups()
|
||||||
resources.append({
|
resources.append({"type": res_type, "path": path, "id": res_id})
|
||||||
"type": res_type,
|
|
||||||
"path": path,
|
|
||||||
"id": res_id
|
|
||||||
})
|
|
||||||
|
|
||||||
# Track scripts separately
|
# Track scripts separately
|
||||||
if res_type == "Script":
|
if res_type == "Script":
|
||||||
scripts.append({
|
scripts.append({"path": path, "id": res_id})
|
||||||
"path": path,
|
|
||||||
"id": res_id
|
|
||||||
})
|
|
||||||
|
|
||||||
# Extract nodes
|
# Extract nodes
|
||||||
for match in re.finditer(r'\[node name="(.+?)".*?type="(.+?)".*?\]', content):
|
for match in re.finditer(r'\[node name="(.+?)".*?type="(.+?)".*?\]', content):
|
||||||
node_name, node_type = match.groups()
|
node_name, node_type = match.groups()
|
||||||
|
|
||||||
# Check if node has a script attached
|
# 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
|
attached_script = script_match.group(1) if script_match else None
|
||||||
|
|
||||||
nodes.append({
|
nodes.append({"name": node_name, "type": node_type, "script": attached_script})
|
||||||
"name": node_name,
|
|
||||||
"type": node_type,
|
|
||||||
"script": attached_script
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"file": file_path,
|
"file": file_path,
|
||||||
@@ -1482,8 +1476,8 @@ class CodeAnalyzer:
|
|||||||
"scene_metadata": {
|
"scene_metadata": {
|
||||||
"node_count": len(nodes),
|
"node_count": len(nodes),
|
||||||
"script_count": len(scripts),
|
"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]:
|
def _analyze_godot_resource(self, content: str, file_path: str) -> dict[str, Any]:
|
||||||
@@ -1503,35 +1497,32 @@ class CodeAnalyzer:
|
|||||||
script_path = None
|
script_path = None
|
||||||
|
|
||||||
# Extract resource header
|
# 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:
|
if header_match:
|
||||||
resource_type = header_match.group(1)
|
resource_type = header_match.group(1)
|
||||||
script_class = header_match.group(2)
|
script_class = header_match.group(2)
|
||||||
|
|
||||||
# Extract external resources
|
# 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()
|
res_type, path, res_id = match.groups()
|
||||||
resources.append({
|
resources.append({"type": res_type, "path": path, "id": res_id})
|
||||||
"type": res_type,
|
|
||||||
"path": path,
|
|
||||||
"id": res_id
|
|
||||||
})
|
|
||||||
|
|
||||||
if res_type == "Script":
|
if res_type == "Script":
|
||||||
script_path = path
|
script_path = path
|
||||||
|
|
||||||
# Extract properties from [resource] section
|
# 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:
|
if resource_section:
|
||||||
prop_text = resource_section.group(1)
|
prop_text = resource_section.group(1)
|
||||||
|
|
||||||
for line in prop_text.strip().split('\n'):
|
for line in prop_text.strip().split("\n"):
|
||||||
if '=' in line:
|
if "=" in line:
|
||||||
key, value = line.split('=', 1)
|
key, value = line.split("=", 1)
|
||||||
properties.append({
|
properties.append({"name": key.strip(), "value": value.strip()})
|
||||||
"name": key.strip(),
|
|
||||||
"value": value.strip()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"file": file_path,
|
"file": file_path,
|
||||||
@@ -1542,8 +1533,8 @@ class CodeAnalyzer:
|
|||||||
"resources": resources,
|
"resources": resources,
|
||||||
"resource_metadata": {
|
"resource_metadata": {
|
||||||
"property_count": len(properties),
|
"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]:
|
def _analyze_godot_shader(self, content: str, file_path: str) -> dict[str, Any]:
|
||||||
@@ -1562,35 +1553,26 @@ class CodeAnalyzer:
|
|||||||
shader_type = None
|
shader_type = None
|
||||||
|
|
||||||
# Extract shader type
|
# 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:
|
if type_match:
|
||||||
shader_type = type_match.group(1)
|
shader_type = type_match.group(1)
|
||||||
|
|
||||||
# Extract uniforms
|
# 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()
|
uniform_type, name, hint, default = match.groups()
|
||||||
uniforms.append({
|
uniforms.append({"name": name, "type": uniform_type, "hint": hint, "default": default})
|
||||||
"name": name,
|
|
||||||
"type": uniform_type,
|
|
||||||
"hint": hint,
|
|
||||||
"default": default
|
|
||||||
})
|
|
||||||
|
|
||||||
# Extract varying variables
|
# 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()
|
var_type, name = match.groups()
|
||||||
varyings.append({
|
varyings.append({"name": name, "type": var_type})
|
||||||
"name": name,
|
|
||||||
"type": var_type
|
|
||||||
})
|
|
||||||
|
|
||||||
# Extract functions
|
# 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()
|
func_name, params = match.groups()
|
||||||
functions.append({
|
functions.append({"name": func_name, "parameters": params.strip() if params else ""})
|
||||||
"name": func_name,
|
|
||||||
"parameters": params.strip() if params else ""
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"file": file_path,
|
"file": file_path,
|
||||||
@@ -1598,10 +1580,7 @@ class CodeAnalyzer:
|
|||||||
"uniforms": uniforms,
|
"uniforms": uniforms,
|
||||||
"varyings": varyings,
|
"varyings": varyings,
|
||||||
"functions": functions,
|
"functions": functions,
|
||||||
"shader_metadata": {
|
"shader_metadata": {"uniform_count": len(uniforms), "function_count": len(functions)},
|
||||||
"uniform_count": len(uniforms),
|
|
||||||
"function_count": len(functions)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _analyze_gdscript(self, content: str, file_path: str) -> dict[str, Any]:
|
def _analyze_gdscript(self, content: str, file_path: str) -> dict[str, Any]:
|
||||||
@@ -1621,113 +1600,131 @@ class CodeAnalyzer:
|
|||||||
exports = []
|
exports = []
|
||||||
|
|
||||||
# Extract class definition
|
# 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:
|
if class_match:
|
||||||
class_name = class_match.group(1)
|
class_name = class_match.group(1)
|
||||||
extends = class_match.group(2)
|
extends = class_match.group(2)
|
||||||
classes.append({
|
classes.append(
|
||||||
"name": class_name,
|
{
|
||||||
"bases": [extends] if extends else [],
|
"name": class_name,
|
||||||
"methods": [],
|
"bases": [extends] if extends else [],
|
||||||
"line_number": content[: class_match.start()].count("\n") + 1
|
"methods": [],
|
||||||
})
|
"line_number": content[: class_match.start()].count("\n") + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Extract functions
|
# 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()
|
func_name, params, return_type = match.groups()
|
||||||
|
|
||||||
# Parse parameters
|
# Parse parameters
|
||||||
param_list = []
|
param_list = []
|
||||||
if params.strip():
|
if params.strip():
|
||||||
for param in params.split(','):
|
for param in params.split(","):
|
||||||
param = param.strip()
|
param = param.strip()
|
||||||
if ':' in param:
|
if ":" in param:
|
||||||
# param_name: Type = default
|
# param_name: Type = default
|
||||||
parts = param.split(':')
|
parts = param.split(":")
|
||||||
name = parts[0].strip()
|
name = parts[0].strip()
|
||||||
type_and_default = parts[1].strip()
|
type_and_default = parts[1].strip()
|
||||||
|
|
||||||
param_type = type_and_default.split('=')[0].strip() if '=' in type_and_default else type_and_default
|
param_type = (
|
||||||
default = type_and_default.split('=')[1].strip() if '=' in type_and_default else None
|
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({
|
param_list.append(
|
||||||
"name": name,
|
{"name": name, "type_hint": param_type, "default": default}
|
||||||
"type_hint": param_type,
|
)
|
||||||
"default": default
|
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
param_list.append({
|
param_list.append({"name": param, "type_hint": None, "default": None})
|
||||||
"name": param,
|
|
||||||
"type_hint": None,
|
|
||||||
"default": None
|
|
||||||
})
|
|
||||||
|
|
||||||
functions.append({
|
functions.append(
|
||||||
"name": func_name,
|
{
|
||||||
"parameters": param_list,
|
"name": func_name,
|
||||||
"return_type": return_type,
|
"parameters": param_list,
|
||||||
"line_number": content[: match.start()].count("\n") + 1
|
"return_type": return_type,
|
||||||
})
|
"line_number": content[: match.start()].count("\n") + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Extract signals with documentation
|
# Extract signals with documentation
|
||||||
signal_connections = []
|
signal_connections = []
|
||||||
signal_emissions = []
|
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()
|
signal_name, params = match.groups()
|
||||||
line_number = content[: match.start()].count("\n") + 1
|
line_number = content[: match.start()].count("\n") + 1
|
||||||
|
|
||||||
# Extract documentation comment above signal (## or #)
|
# Extract documentation comment above signal (## or #)
|
||||||
doc_comment = None
|
doc_comment = None
|
||||||
lines = content[:match.start()].split('\n')
|
lines = content[: match.start()].split("\n")
|
||||||
if len(lines) >= 2:
|
if len(lines) >= 2:
|
||||||
prev_line = lines[-1].strip()
|
prev_line = lines[-1].strip()
|
||||||
if prev_line.startswith('##') or prev_line.startswith('#'):
|
if prev_line.startswith("##") or prev_line.startswith("#"):
|
||||||
doc_comment = prev_line.lstrip('#').strip()
|
doc_comment = prev_line.lstrip("#").strip()
|
||||||
|
|
||||||
signals.append({
|
signals.append(
|
||||||
"name": signal_name,
|
{
|
||||||
"parameters": params if params else "",
|
"name": signal_name,
|
||||||
"line_number": line_number,
|
"parameters": params if params else "",
|
||||||
"documentation": doc_comment
|
"line_number": line_number,
|
||||||
})
|
"documentation": doc_comment,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Extract signal connections (.connect() calls)
|
# 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_path, handler = match.groups()
|
||||||
signal_connections.append({
|
signal_connections.append(
|
||||||
"signal": signal_path,
|
{
|
||||||
"handler": handler.strip(),
|
"signal": signal_path,
|
||||||
"line_number": content[: match.start()].count("\n") + 1
|
"handler": handler.strip(),
|
||||||
})
|
"line_number": content[: match.start()].count("\n") + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Extract signal emissions (.emit() calls)
|
# 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_path, args = match.groups()
|
||||||
signal_emissions.append({
|
signal_emissions.append(
|
||||||
"signal": signal_path,
|
{
|
||||||
"arguments": args.strip() if args else "",
|
"signal": signal_path,
|
||||||
"line_number": content[: match.start()].count("\n") + 1
|
"arguments": args.strip() if args else "",
|
||||||
})
|
"line_number": content[: match.start()].count("\n") + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Extract @export variables
|
# 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()
|
hint, var_name, var_type, default = match.groups()
|
||||||
exports.append({
|
exports.append(
|
||||||
"name": var_name,
|
{
|
||||||
"type": var_type,
|
"name": var_name,
|
||||||
"default": default,
|
"type": var_type,
|
||||||
"export_hint": hint,
|
"default": default,
|
||||||
"line_number": content[: match.start()].count("\n") + 1
|
"export_hint": hint,
|
||||||
})
|
"line_number": content[: match.start()].count("\n") + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Detect test framework
|
# Detect test framework
|
||||||
test_framework = None
|
test_framework = None
|
||||||
test_functions = []
|
test_functions = []
|
||||||
|
|
||||||
# GUT (Godot Unit Test) - extends "res://addons/gut/test.gd" or extends GutTest
|
# 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 \
|
if re.search(r'extends\s+["\']?res://addons/gut/test\.gd["\']?', content) or re.search(
|
||||||
re.search(r'extends\s+GutTest', content):
|
r"extends\s+GutTest", content
|
||||||
|
):
|
||||||
test_framework = "GUT"
|
test_framework = "GUT"
|
||||||
|
|
||||||
# Extract test functions (test_* functions)
|
# Extract test functions (test_* functions)
|
||||||
@@ -1736,23 +1733,23 @@ class CodeAnalyzer:
|
|||||||
test_functions.append(func)
|
test_functions.append(func)
|
||||||
|
|
||||||
# gdUnit4 - @suite class annotation
|
# gdUnit4 - @suite class annotation
|
||||||
elif re.search(r'@suite', content):
|
elif re.search(r"@suite", content):
|
||||||
test_framework = "gdUnit4"
|
test_framework = "gdUnit4"
|
||||||
|
|
||||||
# Extract test functions (@test annotated or test_* prefix)
|
# Extract test functions (@test annotated or test_* prefix)
|
||||||
for i, func in enumerate(functions):
|
for i, func in enumerate(functions):
|
||||||
# Check for @test annotation above function
|
# Check for @test annotation above function
|
||||||
func_line = func["line_number"]
|
func_line = func["line_number"]
|
||||||
lines = content.split('\n')
|
lines = content.split("\n")
|
||||||
if func_line > 1:
|
if func_line > 1:
|
||||||
prev_line = lines[func_line - 2].strip()
|
prev_line = lines[func_line - 2].strip()
|
||||||
if prev_line.startswith('@test'):
|
if prev_line.startswith("@test"):
|
||||||
test_functions.append(func)
|
test_functions.append(func)
|
||||||
elif func["name"].startswith("test_"):
|
elif func["name"].startswith("test_"):
|
||||||
test_functions.append(func)
|
test_functions.append(func)
|
||||||
|
|
||||||
# WAT (WizAds Test) - less common
|
# 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"
|
test_framework = "WAT"
|
||||||
for func in functions:
|
for func in functions:
|
||||||
if func["name"].startswith("test_"):
|
if func["name"].startswith("test_"):
|
||||||
@@ -1815,4 +1812,3 @@ def create_sprite(texture: str) -> Node2D:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
print(f" {method['name']}({params}) -> {method['return_type']}")
|
print(f" {method['name']}({params}) -> {method['return_type']}")
|
||||||
|
|
||||||
|
|||||||
@@ -401,11 +401,13 @@ def extract_markdown_structure(content: str) -> dict[str, Any]:
|
|||||||
if header_match:
|
if header_match:
|
||||||
level = len(header_match.group(1))
|
level = len(header_match.group(1))
|
||||||
text = header_match.group(2).strip()
|
text = header_match.group(2).strip()
|
||||||
structure["headers"].append({
|
structure["headers"].append(
|
||||||
"level": level,
|
{
|
||||||
"text": text,
|
"level": level,
|
||||||
"line": i + 1,
|
"text": text,
|
||||||
})
|
"line": i + 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
# First h1 is the title
|
# First h1 is the title
|
||||||
if level == 1 and structure["title"] is None:
|
if level == 1 and structure["title"] is None:
|
||||||
structure["title"] = text
|
structure["title"] = text
|
||||||
@@ -416,24 +418,30 @@ def extract_markdown_structure(content: str) -> dict[str, Any]:
|
|||||||
language = match.group(1) or "text"
|
language = match.group(1) or "text"
|
||||||
code = match.group(2).strip()
|
code = match.group(2).strip()
|
||||||
if len(code) > 0:
|
if len(code) > 0:
|
||||||
structure["code_blocks"].append({
|
structure["code_blocks"].append(
|
||||||
"language": language,
|
{
|
||||||
"code": code[:500], # Truncate long code blocks
|
"language": language,
|
||||||
"full_length": len(code),
|
"code": code[:500], # Truncate long code blocks
|
||||||
})
|
"full_length": len(code),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Extract links
|
# Extract links
|
||||||
link_pattern = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
link_pattern = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
||||||
for match in link_pattern.finditer(content):
|
for match in link_pattern.finditer(content):
|
||||||
structure["links"].append({
|
structure["links"].append(
|
||||||
"text": match.group(1),
|
{
|
||||||
"url": match.group(2),
|
"text": match.group(1),
|
||||||
})
|
"url": match.group(2),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return structure
|
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.
|
Generate a summary of markdown content.
|
||||||
|
|
||||||
@@ -546,12 +554,14 @@ def process_markdown_docs(
|
|||||||
structure = extract_markdown_structure(content)
|
structure = extract_markdown_structure(content)
|
||||||
summary = generate_markdown_summary(content, structure)
|
summary = generate_markdown_summary(content, structure)
|
||||||
|
|
||||||
doc_data.update({
|
doc_data.update(
|
||||||
"title": structure.get("title") or md_path.stem,
|
{
|
||||||
"structure": structure,
|
"title": structure.get("title") or md_path.stem,
|
||||||
"summary": summary,
|
"structure": structure,
|
||||||
"content": content if depth == "full" else None,
|
"summary": summary,
|
||||||
})
|
"content": content if depth == "full" else None,
|
||||||
|
}
|
||||||
|
)
|
||||||
processed_docs.append(doc_data)
|
processed_docs.append(doc_data)
|
||||||
|
|
||||||
# Track categories
|
# Track categories
|
||||||
@@ -587,6 +597,7 @@ def process_markdown_docs(
|
|||||||
# Copy file to category folder
|
# Copy file to category folder
|
||||||
dest_path = category_dir / doc["filename"]
|
dest_path = category_dir / doc["filename"]
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
shutil.copy2(src_path, dest_path)
|
shutil.copy2(src_path, dest_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Failed to copy {doc['path']}: {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:
|
with open(index_json, "w", encoding="utf-8") as f:
|
||||||
json.dump(index_data, f, indent=2, default=str)
|
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}")
|
logger.info(f"📁 Saved to: {docs_output_dir}")
|
||||||
|
|
||||||
return index_data
|
return index_data
|
||||||
@@ -636,18 +649,22 @@ def _enhance_docs_api(docs: list[dict], api_key: str) -> list[dict]:
|
|||||||
"""Enhance docs using Claude API."""
|
"""Enhance docs using Claude API."""
|
||||||
try:
|
try:
|
||||||
import anthropic
|
import anthropic
|
||||||
|
|
||||||
client = anthropic.Anthropic(api_key=api_key)
|
client = anthropic.Anthropic(api_key=api_key)
|
||||||
|
|
||||||
# Batch documents for efficiency
|
# Batch documents for efficiency
|
||||||
batch_size = 10
|
batch_size = 10
|
||||||
for i in range(0, len(docs), batch_size):
|
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
|
# Create prompt for batch
|
||||||
docs_text = "\n\n".join([
|
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")
|
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:
|
if not docs_text:
|
||||||
continue
|
continue
|
||||||
@@ -666,12 +683,13 @@ Return JSON with format:
|
|||||||
response = client.messages.create(
|
response = client.messages.create(
|
||||||
model="claude-sonnet-4-20250514",
|
model="claude-sonnet-4-20250514",
|
||||||
max_tokens=2000,
|
max_tokens=2000,
|
||||||
messages=[{"role": "user", "content": prompt}]
|
messages=[{"role": "user", "content": prompt}],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parse response and merge enhancements
|
# Parse response and merge enhancements
|
||||||
try:
|
try:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
json_match = re.search(r"\{.*\}", response.content[0].text, re.DOTALL)
|
json_match = re.search(r"\{.*\}", response.content[0].text, re.DOTALL)
|
||||||
if json_match:
|
if json_match:
|
||||||
enhancements = json.loads(json_match.group())
|
enhancements = json.loads(json_match.group())
|
||||||
@@ -700,10 +718,12 @@ def _enhance_docs_local(docs: list[dict]) -> list[dict]:
|
|||||||
if not docs_with_summary:
|
if not docs_with_summary:
|
||||||
return docs
|
return docs
|
||||||
|
|
||||||
docs_text = "\n\n".join([
|
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
|
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.
|
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:
|
if result.returncode == 0 and result.stdout:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
json_match = re.search(r"\{.*\}", result.stdout, re.DOTALL)
|
json_match = re.search(r"\{.*\}", result.stdout, re.DOTALL)
|
||||||
if json_match:
|
if json_match:
|
||||||
enhancements = json.loads(json_match.group())
|
enhancements = json.loads(json_match.group())
|
||||||
@@ -801,7 +822,9 @@ def analyze_codebase(
|
|||||||
|
|
||||||
if enhance_level > 0:
|
if enhance_level > 0:
|
||||||
level_names = {1: "SKILL.md only", 2: "SKILL.md+Architecture+Config", 3: "full"}
|
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
|
# Resolve directory to absolute path to avoid relative_to() errors
|
||||||
directory = Path(directory).resolve()
|
directory = Path(directory).resolve()
|
||||||
|
|
||||||
@@ -1411,7 +1434,9 @@ Use this skill when you need to:
|
|||||||
skill_content += "- **Architecture**: `references/architecture/` - Architectural patterns\n"
|
skill_content += "- **Architecture**: `references/architecture/` - Architectural patterns\n"
|
||||||
refs_added = True
|
refs_added = True
|
||||||
if extract_docs and (output_dir / "documentation").exists():
|
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
|
refs_added = True
|
||||||
|
|
||||||
if not refs_added:
|
if not refs_added:
|
||||||
@@ -1691,10 +1716,7 @@ def _format_signal_flow_section(output_dir: Path, results: dict[str, Any]) -> st
|
|||||||
content += "\n"
|
content += "\n"
|
||||||
|
|
||||||
# Test framework detection
|
# Test framework detection
|
||||||
test_files = [
|
test_files = [f for f in results.get("files", []) if f.get("test_framework")]
|
||||||
f for f in results.get("files", [])
|
|
||||||
if f.get("test_framework")
|
|
||||||
]
|
|
||||||
|
|
||||||
if test_files:
|
if test_files:
|
||||||
frameworks = {}
|
frameworks = {}
|
||||||
@@ -1732,7 +1754,15 @@ def _format_documentation_section(_output_dir: Path, docs_data: dict[str, Any])
|
|||||||
content += f"**Categories:** {len(categories)}\n\n"
|
content += f"**Categories:** {len(categories)}\n\n"
|
||||||
|
|
||||||
# List documents by category (most important first)
|
# 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
|
# Sort categories by priority
|
||||||
sorted_categories = []
|
sorted_categories = []
|
||||||
@@ -1779,6 +1809,7 @@ def _format_documentation_section(_output_dir: Path, docs_data: dict[str, Any])
|
|||||||
if all_topics:
|
if all_topics:
|
||||||
# Deduplicate and count
|
# Deduplicate and count
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
topic_counts = Counter(all_topics)
|
topic_counts = Counter(all_topics)
|
||||||
top_topics = [t for t, _ in topic_counts.most_common(10)]
|
top_topics = [t for t, _ in topic_counts.most_common(10)]
|
||||||
content += f"**Key Topics:** {', '.join(top_topics)}\n\n"
|
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
|
for setting in cf.get("settings", [])[:5]: # First 5 settings per file
|
||||||
# Support both "type" (from config_extractor) and "value_type" (legacy)
|
# Support both "type" (from config_extractor) and "value_type" (legacy)
|
||||||
value_type = setting.get("type", setting.get("value_type", "unknown"))
|
value_type = setting.get("type", setting.get("value_type", "unknown"))
|
||||||
settings_summary.append(
|
settings_summary.append(f" - {setting['key']}: {setting['value']} ({value_type})")
|
||||||
f" - {setting['key']}: {setting['value']} ({value_type})"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Support both "type" (from config_extractor) and "config_type" (legacy)
|
# Support both "type" (from config_extractor) and "config_type" (legacy)
|
||||||
config_type = cf.get("type", cf.get("config_type", "unknown"))
|
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"))
|
config_type = cf.get("type", cf.get("config_type", "unknown"))
|
||||||
settings_preview = []
|
settings_preview = []
|
||||||
for s in cf.get("settings", [])[:3]: # Show first 3 settings
|
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"""
|
config_data.append(f"""
|
||||||
### {cf["relative_path"]} ({config_type})
|
### {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)
|
potential_files.append(json_file)
|
||||||
|
|
||||||
# Try to load the most recent JSON file with expected structure
|
# Try to load the most recent JSON file with expected structure
|
||||||
for json_file in sorted(
|
for json_file in sorted(potential_files, key=lambda f: f.stat().st_mtime, reverse=True):
|
||||||
potential_files, key=lambda f: f.stat().st_mtime, reverse=True
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
with open(json_file) as f:
|
with open(json_file) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|||||||
@@ -429,7 +429,9 @@ class ConfigParser:
|
|||||||
# JSON array at root - extract from each dict item
|
# JSON array at root - extract from each dict item
|
||||||
for idx, item in enumerate(data):
|
for idx, item in enumerate(data):
|
||||||
if isinstance(item, dict):
|
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:
|
else:
|
||||||
# Primitive value at root (string, number, etc.) - skip
|
# Primitive value at root (string, number, etc.) - skip
|
||||||
logger.debug(f"Skipping JSON with primitive root: {config_file.relative_path}")
|
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
|
# YAML array at root - extract from each dict item
|
||||||
for idx, item in enumerate(data):
|
for idx, item in enumerate(data):
|
||||||
if isinstance(item, dict):
|
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:
|
except yaml.YAMLError as e:
|
||||||
config_file.parse_errors.append(f"YAML parse error: {str(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
|
# Extract extends with class name: extends MyBaseClass
|
||||||
# Note: This creates a symbolic dependency that may not resolve to a file
|
# 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):
|
for match in re.finditer(extends_class_pattern, content):
|
||||||
class_name = match.group(1)
|
class_name = match.group(1)
|
||||||
line_num = content[: match.start()].count("\n") + 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("--file-patterns", help="Comma-separated file patterns")
|
||||||
analyze_parser.add_argument(
|
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(
|
analyze_parser.add_argument(
|
||||||
"--enhance-level",
|
"--enhance-level",
|
||||||
type=int,
|
type=int,
|
||||||
choices=[0, 1, 2, 3],
|
choices=[0, 1, 2, 3],
|
||||||
default=None,
|
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("--skip-api-reference", action="store_true", help="Skip API docs")
|
||||||
analyze_parser.add_argument(
|
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-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-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("--no-comments", action="store_true", help="Skip comments")
|
||||||
analyze_parser.add_argument("--verbose", action="store_true", help="Verbose logging")
|
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)
|
# Handle preset flags (depth and features)
|
||||||
if args.quick:
|
if args.quick:
|
||||||
# Quick = surface depth + skip advanced features + no AI
|
# Quick = surface depth + skip advanced features + no AI
|
||||||
sys.argv.extend([
|
sys.argv.extend(
|
||||||
"--depth", "surface",
|
[
|
||||||
"--skip-patterns",
|
"--depth",
|
||||||
"--skip-test-examples",
|
"surface",
|
||||||
"--skip-how-to-guides",
|
"--skip-patterns",
|
||||||
"--skip-config-patterns",
|
"--skip-test-examples",
|
||||||
])
|
"--skip-how-to-guides",
|
||||||
|
"--skip-config-patterns",
|
||||||
|
]
|
||||||
|
)
|
||||||
elif args.comprehensive:
|
elif args.comprehensive:
|
||||||
# Comprehensive = full depth + all features (AI level is separate)
|
# Comprehensive = full depth + all features (AI level is separate)
|
||||||
sys.argv.extend(["--depth", "full"])
|
sys.argv.extend(["--depth", "full"])
|
||||||
@@ -588,6 +595,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
# Use default from config (default: 1)
|
# Use default from config (default: 1)
|
||||||
try:
|
try:
|
||||||
from skill_seekers.cli.config_manager import get_config_manager
|
from skill_seekers.cli.config_manager import get_config_manager
|
||||||
|
|
||||||
config = get_config_manager()
|
config = get_config_manager()
|
||||||
enhance_level = config.get_default_enhance_level()
|
enhance_level = config.get_default_enhance_level()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -144,9 +144,7 @@ class SignalFlowAnalyzer:
|
|||||||
|
|
||||||
# Observer pattern - signals with multiple connections
|
# Observer pattern - signals with multiple connections
|
||||||
multi_connected = {
|
multi_connected = {
|
||||||
sig: len(conns)
|
sig: len(conns) for sig, conns in self.signal_connections.items() if len(conns) >= 3
|
||||||
for sig, conns in self.signal_connections.items()
|
|
||||||
if len(conns) >= 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if multi_connected:
|
if multi_connected:
|
||||||
@@ -170,9 +168,7 @@ class SignalFlowAnalyzer:
|
|||||||
def _calculate_statistics(self) -> dict[str, Any]:
|
def _calculate_statistics(self) -> dict[str, Any]:
|
||||||
"""Calculate signal usage statistics."""
|
"""Calculate signal usage statistics."""
|
||||||
total_signals = len(self.signal_declarations)
|
total_signals = len(self.signal_declarations)
|
||||||
total_connections = sum(
|
total_connections = sum(len(conns) for conns in self.signal_connections.values())
|
||||||
len(conns) for conns in self.signal_connections.values()
|
|
||||||
)
|
|
||||||
total_emissions = sum(len(emits) for emits in self.signal_emissions.items())
|
total_emissions = sum(len(emits) for emits in self.signal_emissions.items())
|
||||||
|
|
||||||
# Find most connected signals
|
# Find most connected signals
|
||||||
@@ -181,17 +177,13 @@ class SignalFlowAnalyzer:
|
|||||||
)[:5]
|
)[:5]
|
||||||
|
|
||||||
# Find most emitted signals
|
# Find most emitted signals
|
||||||
most_emitted = sorted(
|
most_emitted = sorted(self.signal_emissions.items(), key=lambda x: len(x[1]), reverse=True)[
|
||||||
self.signal_emissions.items(), key=lambda x: len(x[1]), reverse=True
|
:5
|
||||||
)[:5]
|
]
|
||||||
|
|
||||||
# Signal density (signals per GDScript file)
|
# Signal density (signals per GDScript file)
|
||||||
gdscript_files = sum(
|
gdscript_files = sum(1 for f in self.files if f.get("language") == "GDScript")
|
||||||
1 for f in self.files if f.get("language") == "GDScript"
|
signal_density = total_signals / gdscript_files if gdscript_files > 0 else 0
|
||||||
)
|
|
||||||
signal_density = (
|
|
||||||
total_signals / gdscript_files if gdscript_files > 0 else 0
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total_signals": total_signals,
|
"total_signals": total_signals,
|
||||||
@@ -200,12 +192,10 @@ class SignalFlowAnalyzer:
|
|||||||
"signal_density": round(signal_density, 2),
|
"signal_density": round(signal_density, 2),
|
||||||
"gdscript_files": gdscript_files,
|
"gdscript_files": gdscript_files,
|
||||||
"most_connected_signals": [
|
"most_connected_signals": [
|
||||||
{"signal": sig, "connection_count": len(conns)}
|
{"signal": sig, "connection_count": len(conns)} for sig, conns in most_connected
|
||||||
for sig, conns in most_connected
|
|
||||||
],
|
],
|
||||||
"most_emitted_signals": [
|
"most_emitted_signals": [
|
||||||
{"signal": sig, "emission_count": len(emits)}
|
{"signal": sig, "emission_count": len(emits)} for sig, emits in most_emitted
|
||||||
for sig, emits in most_emitted
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,9 +262,7 @@ class SignalFlowAnalyzer:
|
|||||||
|
|
||||||
return patterns[:10] # Top 10 most used signals
|
return patterns[:10] # Top 10 most used signals
|
||||||
|
|
||||||
def generate_how_to_guides(
|
def generate_how_to_guides(self, output_dir: Path, ai_mode: str = "LOCAL") -> str:
|
||||||
self, output_dir: Path, ai_mode: str = "LOCAL"
|
|
||||||
) -> str:
|
|
||||||
"""
|
"""
|
||||||
Generate signal-based how-to guides using AI.
|
Generate signal-based how-to guides using AI.
|
||||||
|
|
||||||
@@ -297,7 +285,9 @@ class SignalFlowAnalyzer:
|
|||||||
|
|
||||||
for i, pattern in enumerate(patterns, 1):
|
for i, pattern in enumerate(patterns, 1):
|
||||||
signal_name = pattern["signal_name"]
|
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"
|
guide_content += "\n---\n\n"
|
||||||
|
|
||||||
@@ -313,9 +303,7 @@ class SignalFlowAnalyzer:
|
|||||||
|
|
||||||
return str(guide_file)
|
return str(guide_file)
|
||||||
|
|
||||||
def _generate_signal_guide(
|
def _generate_signal_guide(self, pattern: dict[str, Any], ai_mode: str) -> str:
|
||||||
self, pattern: dict[str, Any], ai_mode: str
|
|
||||||
) -> str:
|
|
||||||
"""
|
"""
|
||||||
Generate a how-to guide for a single signal using AI.
|
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)
|
guide_file = self.generate_how_to_guides(output_dir, ai_mode)
|
||||||
if guide_file:
|
if guide_file:
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.info(f"📚 Generated signal how-to guides: {guide_file}")
|
logger.info(f"📚 Generated signal how-to guides: {guide_file}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.warning(f"Failed to generate signal how-to guides: {e}")
|
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 Signals**: {stats['total_signals']}")
|
||||||
lines.append(f"- **Total Connections**: {stats['total_connections']}")
|
lines.append(f"- **Total Connections**: {stats['total_connections']}")
|
||||||
lines.append(f"- **Total Emissions**: {stats['total_emissions']}")
|
lines.append(f"- **Total Emissions**: {stats['total_emissions']}")
|
||||||
lines.append(
|
lines.append(f"- **Signal Density**: {stats['signal_density']} signals per file\n")
|
||||||
f"- **Signal Density**: {stats['signal_density']} signals per file\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Patterns
|
# Patterns
|
||||||
if analysis["patterns"]:
|
if analysis["patterns"]:
|
||||||
@@ -480,9 +468,7 @@ class SignalFlowAnalyzer:
|
|||||||
if stats["most_connected_signals"]:
|
if stats["most_connected_signals"]:
|
||||||
lines.append("## Most Connected Signals\n")
|
lines.append("## Most Connected Signals\n")
|
||||||
for item in stats["most_connected_signals"]:
|
for item in stats["most_connected_signals"]:
|
||||||
lines.append(
|
lines.append(f"- **{item['signal']}**: {item['connection_count']} connections")
|
||||||
f"- **{item['signal']}**: {item['connection_count']} connections"
|
|
||||||
)
|
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
with open(output_dir / "signal_reference.md", "w") as f:
|
with open(output_dir / "signal_reference.md", "w") as f:
|
||||||
|
|||||||
@@ -816,7 +816,11 @@ class GenericTestAnalyzer:
|
|||||||
# Find next method (setup or test)
|
# Find next method (setup or test)
|
||||||
next_pattern = patterns.get("setup", patterns["test_function"])
|
next_pattern = patterns.get("setup", patterns["test_function"])
|
||||||
next_setup = re.search(next_pattern, code[setup_start:])
|
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]
|
setup_body = code[setup_start:setup_end]
|
||||||
|
|
||||||
example = self._create_example(
|
example = self._create_example(
|
||||||
|
|||||||
@@ -63,17 +63,20 @@ class TestAnalyzeSubcommand(unittest.TestCase):
|
|||||||
|
|
||||||
def test_all_skip_flags(self):
|
def test_all_skip_flags(self):
|
||||||
"""Test all skip flags are properly parsed."""
|
"""Test all skip flags are properly parsed."""
|
||||||
args = self.parser.parse_args([
|
args = self.parser.parse_args(
|
||||||
"analyze",
|
[
|
||||||
"--directory", ".",
|
"analyze",
|
||||||
"--skip-api-reference",
|
"--directory",
|
||||||
"--skip-dependency-graph",
|
".",
|
||||||
"--skip-patterns",
|
"--skip-api-reference",
|
||||||
"--skip-test-examples",
|
"--skip-dependency-graph",
|
||||||
"--skip-how-to-guides",
|
"--skip-patterns",
|
||||||
"--skip-config-patterns",
|
"--skip-test-examples",
|
||||||
"--skip-docs"
|
"--skip-how-to-guides",
|
||||||
])
|
"--skip-config-patterns",
|
||||||
|
"--skip-docs",
|
||||||
|
]
|
||||||
|
)
|
||||||
self.assertTrue(args.skip_api_reference)
|
self.assertTrue(args.skip_api_reference)
|
||||||
self.assertTrue(args.skip_dependency_graph)
|
self.assertTrue(args.skip_dependency_graph)
|
||||||
self.assertTrue(args.skip_patterns)
|
self.assertTrue(args.skip_patterns)
|
||||||
|
|||||||
2
uv.lock
generated
2
uv.lock
generated
@@ -1846,7 +1846,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "skill-seekers"
|
name = "skill-seekers"
|
||||||
version = "2.7.4"
|
version = "2.9.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anthropic" },
|
{ name = "anthropic" },
|
||||||
|
|||||||
Reference in New Issue
Block a user