feat: Add Signal Flow Analysis (C3.10) and Test Framework Detection

Comprehensive Godot signal analysis and test framework support:

## Signal Flow Analysis (C3.10)
Enhanced GDScript analyzer to extract:
- Signal declarations with documentation comments
- Signal connections (.connect() calls)
- Signal emissions (.emit() calls)
- Signal flow chains (source → signal → handler)

Created SignalFlowAnalyzer class:
- Analyzes 208 signals, 634 connections, 298 emissions (Cosmic Ideler)
- Detects event patterns:
  - EventBus Pattern (centralized event system)
  - Observer Pattern (multi-connected signals)
  - Event Chains (cascading signal emissions)
- Generates:
  - signal_flow.json (full analysis data)
  - signal_flow.mmd (Mermaid diagram)
  - signal_reference.md (human-readable docs)

Statistics:
- Signal density calculation (signals per file)
- Most connected signals ranking
- Most emitted signals ranking

## Test Framework Detection
Added support for 3 Godot test frameworks:
- **GUT** (Godot Unit Test) - extends GutTest, test_* functions
- **gdUnit4** - @suite and @test annotations
- **WAT** (WizAds Test) - extends WAT.Test

Detection results (Cosmic Ideler):
- 20 GUT test files
- 396 test cases detected

## Integration
Updated codebase_scraper.py:
- Signal flow analysis runs automatically for Godot projects
- Test framework detection integrated into code analysis
- SKILL.md shows signal statistics and test framework info
- New section: 📡 Signal Flow Analysis (C3.10)

## Results (Tested on Cosmic Ideler)
- 443/452 files analyzed (98%)
- 208 signals documented
- 634 signal connections mapped
- 298 signal emissions tracked
- 3 event patterns detected (EventBus, Observer, Event Chains)
- 20 GUT test files found with 396 test cases

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
yusyus
2026-02-02 21:44:26 +03:00
parent b252f43d0e
commit 281f6f7916
3 changed files with 499 additions and 5 deletions

View File

@@ -1669,15 +1669,47 @@ class CodeAnalyzer:
"line_number": content[: match.start()].count("\n") + 1
})
# Extract signals
# Extract signals with documentation
signal_connections = []
signal_emissions = []
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')
if len(lines) >= 2:
prev_line = lines[-1].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
})
# Extract signal connections (.connect() calls)
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
})
# Extract signal emissions (.emit() calls)
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
})
# Extract @export variables
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()
@@ -1688,15 +1720,61 @@ class CodeAnalyzer:
"export_hint": hint,
"line_number": content[: match.start()].count("\n") + 1
})
return {
# 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):
test_framework = "GUT"
# Extract test functions (test_* functions)
for func in functions:
if func["name"].startswith("test_"):
test_functions.append(func)
# gdUnit4 - @suite class annotation
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')
if func_line > 1:
prev_line = lines[func_line - 2].strip()
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):
test_framework = "WAT"
for func in functions:
if func["name"].startswith("test_"):
test_functions.append(func)
result = {
"file": file_path,
"classes": classes,
"functions": functions,
"signals": signals,
"exports": exports
"exports": exports,
"signal_connections": signal_connections,
"signal_emissions": signal_emissions,
}
# Add test framework info if detected
if test_framework:
result["test_framework"] = test_framework
result["test_functions"] = test_functions
return result
if __name__ == "__main__":
# Test the analyzer