perf(entropy): Fix nested imports in hot paths
Hoisted imports from inside frequently-called functions to module level: app_controller.py: - Added traceback and inspect at module level - Removed 3 nested traceback imports from exception handlers gui_2.py: - Added traceback at module level - Removed nested traceback import from _gui_func exception handler - Kept uvicorn lazy-loaded (only for --headless mode) multi_agent_conductor.py: - Removed unused 'import sys' from run() - Removed redundant nested imports (already at module level) Also adds audit scripts and entropy findings documentation.
This commit is contained in:
@@ -94,5 +94,13 @@
|
|||||||
"C:\\Users\\Ed\\AppData\\Local\\Temp\\pytest-of-Ed\\pytest-847\\test_force_full0\\other.txt": {
|
"C:\\Users\\Ed\\AppData\\Local\\Temp\\pytest-of-Ed\\pytest-847\\test_force_full0\\other.txt": {
|
||||||
"hash": "04d61c0832f9cbc2a210334352425d2519890a0a5945da96ccc5bd9ff101c4d3",
|
"hash": "04d61c0832f9cbc2a210334352425d2519890a0a5945da96ccc5bd9ff101c4d3",
|
||||||
"summary": "This document is a plain text file containing ten lines of content, with the first eight lines previewed. Its purpose appears to be simply to hold and present this sequential text.\n\n**Outline:**\n**TXT** \u2014 10 lines\npreview:\n```\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\n```"
|
"summary": "This document is a plain text file containing ten lines of content, with the first eight lines previewed. Its purpose appears to be simply to hold and present this sequential text.\n\n**Outline:**\n**TXT** \u2014 10 lines\npreview:\n```\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\n```"
|
||||||
|
},
|
||||||
|
"C:\\Users\\Ed\\AppData\\Local\\Temp\\pytest-of-Ed\\pytest-848\\test_auto_aggregate_skip0\\file1.txt": {
|
||||||
|
"hash": "d0b425e00e15a0d36b9b361f02bab63563aed6cb4665083905386c55d5b679fa",
|
||||||
|
"summary": "This document contains a single line of text, \"content1\". Its purpose and key takeaways are limited to this singular piece of content.\n\n**Outline:**\n**TXT** \u2014 1 lines\npreview:\n```\ncontent1\n```"
|
||||||
|
},
|
||||||
|
"C:\\Users\\Ed\\AppData\\Local\\Temp\\pytest-of-Ed\\pytest-848\\test_force_full0\\other.txt": {
|
||||||
|
"hash": "04d61c0832f9cbc2a210334352425d2519890a0a5945da96ccc5bd9ff101c4d3",
|
||||||
|
"summary": "This document is a simple text file containing ten lines of content, with the first eight lines previewed. Its purpose appears to be for basic data storage or as a placeholder.\n\n**Outline:**\n**TXT** \u2014 10 lines\npreview:\n```\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\n```"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Entropy Audit Report: src/
|
||||||
|
|
||||||
|
**Files Analyzed:** 48
|
||||||
|
**Total Lines:** 22,222
|
||||||
|
**Issues Found:** 1050
|
||||||
|
|
||||||
|
## Summary by Severity
|
||||||
|
|
||||||
|
- **High:** 12
|
||||||
|
- **Medium:** 1
|
||||||
|
- **Low:** 1037
|
||||||
|
|
||||||
|
## Summary by Category
|
||||||
|
|
||||||
|
- **long_function:** 12
|
||||||
|
- **magic_number:** 928
|
||||||
|
- **tech_debt:** 109
|
||||||
|
- **too_many_params:** 1
|
||||||
|
|
||||||
|
## High Severity Issues
|
||||||
|
|
||||||
|
### src\ai_client.py
|
||||||
|
- **Line 940:** Function `_send_gemini` is 229 lines (>200)
|
||||||
|
- Detail: `Lines 940-1169`
|
||||||
|
|
||||||
|
### src\ai_client.py
|
||||||
|
- **Line 1660:** Function `_send_deepseek` is 251 lines (>200)
|
||||||
|
- Detail: `Lines 1660-1911`
|
||||||
|
|
||||||
|
### src\ai_client.py
|
||||||
|
- **Line 1913:** Function `_send_minimax` is 216 lines (>200)
|
||||||
|
- Detail: `Lines 1913-2129`
|
||||||
|
|
||||||
|
### src\api_hooks.py
|
||||||
|
- **Line 88:** Function `do_GET` is 205 lines (>200)
|
||||||
|
- Detail: `Lines 88-293`
|
||||||
|
|
||||||
|
### src\api_hooks.py
|
||||||
|
- **Line 295:** Function `do_POST` is 350 lines (>200)
|
||||||
|
- Detail: `Lines 295-645`
|
||||||
|
|
||||||
|
### src\app_controller.py
|
||||||
|
- **Line 137:** Function `__init__` is 332 lines (>200)
|
||||||
|
- Detail: `Lines 137-469`
|
||||||
|
|
||||||
|
### src\app_controller.py
|
||||||
|
- **Line 716:** Function `_process_pending_gui_tasks` is 264 lines (>200)
|
||||||
|
- Detail: `Lines 716-980`
|
||||||
|
|
||||||
|
### src\app_controller.py
|
||||||
|
- **Line 1924:** Function `create_api` is 234 lines (>200)
|
||||||
|
- Detail: `Lines 1924-2158`
|
||||||
|
|
||||||
|
### src\gui_2.py
|
||||||
|
- **Line 750:** Function `_gui_func` is 580 lines (>200)
|
||||||
|
- Detail: `Lines 750-1330`
|
||||||
|
|
||||||
|
### src\gui_2.py
|
||||||
|
- **Line 2730:** Function `_render_discussion_panel` is 376 lines (>200)
|
||||||
|
- Detail: `Lines 2730-3106`
|
||||||
|
|
||||||
|
### src\gui_2.py
|
||||||
|
- **Line 4059:** Function `_render_mma_dashboard` is 420 lines (>200)
|
||||||
|
- Detail: `Lines 4059-4479`
|
||||||
|
|
||||||
|
### src\multi_agent_conductor.py
|
||||||
|
- **Line 403:** Function `run_worker_lifecycle` is 210 lines (>200)
|
||||||
|
- Detail: `Lines 403-613`
|
||||||
|
|
||||||
|
|
||||||
|
## Medium Severity Issues
|
||||||
|
|
||||||
|
- **Line 2236** (src\ai_client.py): Function `send` has 12 parameters
|
||||||
@@ -1,68 +1,69 @@
|
|||||||
# Entropy Audit Findings: Data-Oriented Python Optimization Pass
|
# Entropy Audit Findings: Data-Oriented Python Optimization Pass
|
||||||
|
|
||||||
## Phase 5 Status: In Progress
|
## Phase 5 Status: In Progress - Focused Audit Complete
|
||||||
|
|
||||||
## Issues Found and Fixed
|
**Approach:** Muratori-style - focused on actual issues, not style. "The less Python the better" means:
|
||||||
|
- Duplicate logic (same thing done in multiple places) = BAD
|
||||||
|
- Long functions that are linear and single-purpose = OK
|
||||||
|
- Nested imports in hot paths = BAD (performance)
|
||||||
|
- Mutable default arguments = BAD (bugs)
|
||||||
|
|
||||||
### 1. Duplicate Line Bug in `app_controller.py`
|
## Already Fixed This Session
|
||||||
**Location:** `rag_emb_provider.setter` (around line 541)
|
|
||||||
**Issue:** Two identical lines in setter:
|
|
||||||
```python
|
|
||||||
if self.rag_engine: self.rag_engine = rag_engine.RAGEngine(self.rag_config, self.active_project_root)
|
|
||||||
if self.rag_engine: self.rag_engine = rag_engine.RAGEngine(self.rag_config, self.active_project_root)
|
|
||||||
```
|
|
||||||
**Fix:** Removed duplicate line.
|
|
||||||
**Status:** FIXED (commit f6feab9)
|
|
||||||
|
|
||||||
### 2. FALSE POSITIVE: Python Property Definitions
|
### ✓ GUI Indentation Bug causing crash (commit f6feab9)
|
||||||
**Location:** `app_controller.py`, `gui_2.py`
|
The `_render_mma_dashboard` had code incorrectly indented inside an `if` block.
|
||||||
**Issue:** Audit script flagged `@property` getter/setter pairs as "duplicate definitions"
|
|
||||||
**Explanation:** These are correct Python property patterns (getter + setter), not bugs
|
|
||||||
**Status:** NOT AN ISSUE - False positive from audit script
|
|
||||||
|
|
||||||
## Issues Identified (Not Fixed - Require Design Decisions)
|
### ✓ Duplicate Line Bug in `rag_emb_provider.setter` (commit f6feab9)
|
||||||
|
`app_controller.py` had two identical lines.
|
||||||
|
|
||||||
### 3. Duplicate Blocking/Unblocking Logic
|
### ✓ Nested Imports in Hot Paths (commit 54afbb9)
|
||||||
**Location:** `gui_2.py` vs `dag_engine.py`
|
|
||||||
|
|
||||||
**gui_2.py has:**
|
**`multi_agent_conductor.py`:**
|
||||||
- `_cb_block_ticket()` - manual transitive blocking with while loop
|
- Removed `import sys` from inside `run()` - was unused
|
||||||
- `_cb_unblock_ticket()` - manual transitive unblocking with while loop
|
- `from src.personas import PersonaManager` and `from src import paths` were already available at module level
|
||||||
- `_reorder_ticket()` - manual dependency validation
|
|
||||||
|
|
||||||
**dag_engine.py has:**
|
**`gui_2.py`:**
|
||||||
- `cascade_blocks()` - transitive blocking propagation
|
- Removed `import traceback` from inside `_gui_func` exception handler
|
||||||
- `topological_sort()` - dependency ordering
|
- `import uvicorn` in `run()` remains lazy-loaded for `--headless` mode only
|
||||||
|
|
||||||
**Issue:** Both do similar transitive closure operations on tickets, but on different data structures:
|
**`app_controller.py`:**
|
||||||
- gui_2.py: `List[Dict[str, Any]]` (active_tickets)
|
- Added `import traceback` and `import inspect` at module level
|
||||||
- dag_engine.py: `List[Ticket]` (Ticket objects from models.py)
|
- Removed 3 nested `import traceback` from `_process_pending_gui_tasks`, `_handle_request_event`, `_do_generate`
|
||||||
|
|
||||||
**Impact:** Potential state inconsistency if GUI state and DAG engine state diverge
|
## Actual Issues Found (Design - Require Architecture Changes)
|
||||||
**Recommendation:** Refactor gui_2.py to call controller/engine methods rather than duplicating logic
|
|
||||||
|
|
||||||
### 4. Two Parallel Ticket Representations
|
### 1. Parallel Ticket Representations
|
||||||
**Locations:**
|
**Severity:** HIGH - Maintenance burden
|
||||||
- `app_controller.py`: `self.active_tickets: List[Dict[str, Any]]` (dictionaries)
|
|
||||||
- `dag_engine.py`: `self.tickets: List[Ticket]` (dataclass instances)
|
|
||||||
|
|
||||||
**Issue:** Ticket data is converted between dict and Ticket object forms. The `_load_active_tickets` method (line 3184) does:
|
`active_tickets` (Dict-based) is accessed/modified in THREE files:
|
||||||
```python
|
- `api_hooks.py` - API endpoint handling
|
||||||
self.active_tickets = [asdict(t) if not isinstance(t, dict) else t for t in self.active_track.tickets]
|
- `app_controller.py` - Main controller state
|
||||||
```
|
- `gui_2.py` - UI state
|
||||||
|
|
||||||
**Recommendation:** Consider unifying to a single representation, or ensure clear ownership of state management.
|
While `dag_engine.py` uses `List[Ticket]` objects. This creates state sync burden.
|
||||||
|
|
||||||
|
### 2. Duplicate Blocking Logic
|
||||||
|
**Severity:** MEDIUM - Potential state inconsistency
|
||||||
|
|
||||||
|
| Component | Has Blocking Logic? |
|
||||||
|
|-----------|-------------------|
|
||||||
|
| gui_2.py | Yes: `_cb_block_ticket`, `_cb_unblock_ticket` |
|
||||||
|
| dag_engine.py | Yes: `cascade_blocks` |
|
||||||
|
|
||||||
|
If GUI manually blocks tickets without going through DAG, state can diverge.
|
||||||
|
|
||||||
## Issues Not Addressed (Lower Priority)
|
## Issues Not Addressed (Lower Priority)
|
||||||
|
|
||||||
### 5. Widespread Mixed Indentation
|
### Widespread Mixed Indentation
|
||||||
Many files contain 4-space indentation blocks within predominantly 1-space indented code. This is a style inconsistency but not a functional bug.
|
Many files have 4-space blocks within 1-space files. Style inconsistency only.
|
||||||
|
|
||||||
### 6. Import Consolidation
|
### Pattern Usage Across Files
|
||||||
Multiple files have similar import patterns. Could benefit from a central `src/__init__.py` with commonly used imports, but this is cosmetic.
|
These patterns appear in multiple files (by design - not duplicates):
|
||||||
|
- `calculate_track_progress`: gui_2.py, project_manager.py
|
||||||
|
- `topological_sort`: app_controller.py, conductor_tech_lead.py, dag_engine.py
|
||||||
|
- `push_mma_state`: app_controller.py, gui_2.py
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
- **Fixed:** 1 bug (duplicate line in rag_emb_provider setter)
|
- **Fixed:** Nested imports in hot paths (performance), 2 bugs
|
||||||
- **False Positives:** Property definitions flagged incorrectly by audit
|
- **Design Issues:** 2 (parallel ticket state, duplicate blocking logic) - require architectural changes
|
||||||
- **Design Issues:** 2 (duplicate blocking logic, parallel ticket representations)
|
- **Cosmetic:** Mixed indentation - intentional for readability in some places
|
||||||
- **Cosmetic:** Mixed indentation, import patterns
|
|
||||||
|
|||||||
@@ -0,0 +1,266 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Comprehensive Entropy Audit Script for Manual Slop src/
|
||||||
|
Checks for:
|
||||||
|
1. Duplicate function definitions
|
||||||
|
2. Duplicate class definitions
|
||||||
|
3. Very long functions (>200 lines)
|
||||||
|
4. Nested imports within functions
|
||||||
|
5. Inconsistent patterns (TODO, FIXME comments)
|
||||||
|
6. Cyclomatic complexity indicators (nested conditionals)
|
||||||
|
7. Dead code indicators (unused variables, commented out code)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Dict, Set, Optional
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EntropyIssue:
|
||||||
|
file: str
|
||||||
|
line: int
|
||||||
|
severity: str # 'high', 'medium', 'low'
|
||||||
|
category: str
|
||||||
|
description: str
|
||||||
|
detail: str = ""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FileAnalysis:
|
||||||
|
path: str
|
||||||
|
size_kb: float
|
||||||
|
issues: List[EntropyIssue] = field(default_factory=list)
|
||||||
|
stats: Dict = field(default_factory=dict)
|
||||||
|
|
||||||
|
class EntropyAuditor:
|
||||||
|
def __init__(self, src_dir: str = "src"):
|
||||||
|
self.src_dir = Path(src_dir)
|
||||||
|
self.issues: List[EntropyIssue] = []
|
||||||
|
self.files_analyzed = 0
|
||||||
|
self.total_lines = 0
|
||||||
|
|
||||||
|
def analyze_file(self, filepath: Path) -> FileAnalysis:
|
||||||
|
with open(filepath, encoding='utf-8', errors='ignore') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
lines = content.split('\n')
|
||||||
|
self.total_lines += len(lines)
|
||||||
|
|
||||||
|
analysis = FileAnalysis(
|
||||||
|
path=str(filepath),
|
||||||
|
size_kb=filepath.stat().st_size / 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1. Check for nested imports
|
||||||
|
self._check_nested_imports(filepath, content)
|
||||||
|
|
||||||
|
# 2. Check for very long functions
|
||||||
|
self._check_long_functions(filepath, content)
|
||||||
|
|
||||||
|
# 3. Check for TODO/FIXME
|
||||||
|
self._check_todos(filepath, content)
|
||||||
|
|
||||||
|
# 4. Check for nested depth (complexity)
|
||||||
|
self._check_nesting_depth(filepath, lines)
|
||||||
|
|
||||||
|
# 5. Check for duplicate code patterns
|
||||||
|
self._check_duplicate_patterns(filepath, lines)
|
||||||
|
|
||||||
|
# 6. Check for magic numbers
|
||||||
|
self._check_magic_numbers(filepath, lines)
|
||||||
|
|
||||||
|
return analysis
|
||||||
|
|
||||||
|
def _check_nested_imports(self, filepath: Path, content: str) -> None:
|
||||||
|
"""Check for imports inside function bodies."""
|
||||||
|
tree = ast.parse(content, filename=str(filepath))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.FunctionDef):
|
||||||
|
for child in ast.walk(node):
|
||||||
|
if isinstance(child, (ast.Import, ast.ImportFrom)):
|
||||||
|
# Check if it's not at module level
|
||||||
|
if not any(isinstance(p, (ast.Import, ast.ImportFrom)) for p in tree.body):
|
||||||
|
line = child.lineno or 0
|
||||||
|
self.issues.append(EntropyIssue(
|
||||||
|
file=str(filepath),
|
||||||
|
line=line,
|
||||||
|
severity='medium',
|
||||||
|
category='nested_import',
|
||||||
|
description=f'Nested import in function `{node.name}`',
|
||||||
|
detail=ast.unparse(child)[:100]
|
||||||
|
))
|
||||||
|
|
||||||
|
def _check_long_functions(self, filepath: Path, content: str) -> None:
|
||||||
|
"""Check for functions with >200 lines or >10 parameters."""
|
||||||
|
tree = ast.parse(content, filename=str(filepath))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||||
|
if node.end_lineno and node.lineno:
|
||||||
|
length = node.end_lineno - node.lineno
|
||||||
|
if length > 200:
|
||||||
|
self.issues.append(EntropyIssue(
|
||||||
|
file=str(filepath),
|
||||||
|
line=node.lineno,
|
||||||
|
severity='high',
|
||||||
|
category='long_function',
|
||||||
|
description=f'Function `{node.name}` is {length} lines (>{200})',
|
||||||
|
detail=f'Lines {node.lineno}-{node.end_lineno}'
|
||||||
|
))
|
||||||
|
if len(node.args.args) > 10:
|
||||||
|
self.issues.append(EntropyIssue(
|
||||||
|
file=str(filepath),
|
||||||
|
line=node.lineno,
|
||||||
|
severity='medium',
|
||||||
|
category='too_many_params',
|
||||||
|
description=f'Function `{node.name}` has {len(node.args.args)} parameters',
|
||||||
|
detail=str(node.args.args[:5]) + '...'
|
||||||
|
))
|
||||||
|
|
||||||
|
def _check_todos(self, filepath: Path, content: str) -> None:
|
||||||
|
"""Check for TODO/FIXME/BUG comments."""
|
||||||
|
for i, line in enumerate(content.split('\n'), 1):
|
||||||
|
if re.search(r'(TODO|FIXME|BUG|HACK|XXX)', line, re.IGNORECASE):
|
||||||
|
self.issues.append(EntropyIssue(
|
||||||
|
file=str(filepath),
|
||||||
|
line=i,
|
||||||
|
severity='low',
|
||||||
|
category='tech_debt',
|
||||||
|
description=line.strip()[:80],
|
||||||
|
detail=f'Technical debt marker'
|
||||||
|
))
|
||||||
|
|
||||||
|
def _check_nesting_depth(self, filepath: Path, lines: List[str]) -> None:
|
||||||
|
"""Check for deeply nested code blocks."""
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
if line and not line.strip().startswith('#'):
|
||||||
|
# Count leading spaces
|
||||||
|
stripped = line.lstrip()
|
||||||
|
indent = len(line) - len(stripped)
|
||||||
|
if indent > 20: # More than ~10 levels deep
|
||||||
|
self.issues.append(EntropyIssue(
|
||||||
|
file=str(filepath),
|
||||||
|
line=i,
|
||||||
|
severity='medium',
|
||||||
|
category='deep_nesting',
|
||||||
|
description=f'Line has {indent} spaces of indentation',
|
||||||
|
detail=line.strip()[:60]
|
||||||
|
))
|
||||||
|
|
||||||
|
def _check_duplicate_patterns(self, filepath: Path, lines: List[str]) -> None:
|
||||||
|
"""Check for consecutive duplicate non-blank lines."""
|
||||||
|
prev_line = None
|
||||||
|
dup_start = None
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped and not stripped.startswith('#') and stripped == prev_line:
|
||||||
|
if dup_start is None:
|
||||||
|
dup_start = i - 1
|
||||||
|
else:
|
||||||
|
if dup_start and i - dup_start > 2:
|
||||||
|
self.issues.append(EntropyIssue(
|
||||||
|
file=str(filepath),
|
||||||
|
line=dup_start,
|
||||||
|
severity='high',
|
||||||
|
category='duplicate_lines',
|
||||||
|
description=f'{i - dup_start} consecutive duplicate lines starting at {dup_start}',
|
||||||
|
detail=lines[dup_start-1].strip()[:60] if dup_start <= len(lines) else ''
|
||||||
|
))
|
||||||
|
dup_start = None
|
||||||
|
prev_line = stripped
|
||||||
|
|
||||||
|
def _check_magic_numbers(self, filepath: Path, lines: List[str]) -> None:
|
||||||
|
"""Check for magic numbers (unnamed constants)."""
|
||||||
|
magic_pattern = re.compile(r'(?<!\w)([0-9]{3,})(?!\w)') # Numbers with 3+ digits
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
if not line.strip().startswith('#'):
|
||||||
|
matches = magic_pattern.findall(line)
|
||||||
|
for m in matches:
|
||||||
|
self.issues.append(EntropyIssue(
|
||||||
|
file=str(filepath),
|
||||||
|
line=i,
|
||||||
|
severity='low',
|
||||||
|
category='magic_number',
|
||||||
|
description=f'Magic number: {m}',
|
||||||
|
detail=line.strip()[:60]
|
||||||
|
))
|
||||||
|
|
||||||
|
def run_audit(self) -> None:
|
||||||
|
"""Run audit on all Python files in src/."""
|
||||||
|
py_files = list(self.src_dir.glob("*.py"))
|
||||||
|
print(f"Auditing {len(py_files)} Python files in {self.src_dir}...")
|
||||||
|
|
||||||
|
for filepath in sorted(py_files):
|
||||||
|
if filepath.name == "__init__.py":
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
self.analyze_file(filepath)
|
||||||
|
self.files_analyzed += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error analyzing {filepath}: {e}")
|
||||||
|
|
||||||
|
def generate_report(self) -> str:
|
||||||
|
"""Generate a markdown report of findings."""
|
||||||
|
by_severity = {'high': [], 'medium': [], 'low': []}
|
||||||
|
by_category = {}
|
||||||
|
|
||||||
|
for issue in self.issues:
|
||||||
|
by_severity[issue.severity].append(issue)
|
||||||
|
if issue.category not in by_category:
|
||||||
|
by_category[issue.category] = []
|
||||||
|
by_category[issue.category].append(issue)
|
||||||
|
|
||||||
|
report = [
|
||||||
|
"# Entropy Audit Report: src/",
|
||||||
|
"",
|
||||||
|
f"**Files Analyzed:** {self.files_analyzed}",
|
||||||
|
f"**Total Lines:** {self.total_lines:,}",
|
||||||
|
f"**Issues Found:** {len(self.issues)}",
|
||||||
|
"",
|
||||||
|
"## Summary by Severity",
|
||||||
|
"",
|
||||||
|
f"- **High:** {len(by_severity['high'])}",
|
||||||
|
f"- **Medium:** {len(by_severity['medium'])}",
|
||||||
|
f"- **Low:** {len(by_severity['low'])}",
|
||||||
|
"",
|
||||||
|
"## Summary by Category",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
|
||||||
|
for cat, issues in sorted(by_category.items()):
|
||||||
|
report.append(f"- **{cat}:** {len(issues)}")
|
||||||
|
|
||||||
|
report.extend(["", "## High Severity Issues", ""])
|
||||||
|
for issue in sorted(by_severity['high'], key=lambda x: (x.file, x.line)):
|
||||||
|
report.append(f"### {issue.file}")
|
||||||
|
report.append(f"- **Line {issue.line}:** {issue.description}")
|
||||||
|
if issue.detail:
|
||||||
|
report.append(f" - Detail: `{issue.detail[:80]}`")
|
||||||
|
report.append("")
|
||||||
|
|
||||||
|
report.extend(["", "## Medium Severity Issues", ""])
|
||||||
|
for issue in sorted(by_severity['medium'], key=lambda x: (x.file, x.line))[:50]:
|
||||||
|
report.append(f"- **Line {issue.line}** ({issue.file}): {issue.description}")
|
||||||
|
|
||||||
|
if len(by_severity['medium']) > 50:
|
||||||
|
report.append(f"\n_... and {len(by_severity['medium']) - 50} more medium issues_")
|
||||||
|
|
||||||
|
return "\n".join(report)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
auditor = EntropyAuditor("src")
|
||||||
|
auditor.run_audit()
|
||||||
|
report = auditor.generate_report()
|
||||||
|
print(report)
|
||||||
|
|
||||||
|
# Also write to file
|
||||||
|
report_path = "conductor/tracks/data_oriented_optimization_20260312/entropy_audit_report.md"
|
||||||
|
with open(report_path, 'w') as f:
|
||||||
|
f.write(report)
|
||||||
|
print(f"\nReport written to {report_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Focused Entropy Audit for Manual Slop - Muratori Style
|
||||||
|
Focuses on ACTUAL issues, not style:
|
||||||
|
1. Duplicate logic (same thing done in multiple places)
|
||||||
|
2. State inconsistencies (parallel representations)
|
||||||
|
3. Logic errors / bugs
|
||||||
|
4. Performance concerns
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
from collections import defaultdict
|
||||||
|
from typing import List, Dict, Set, Tuple, Optional
|
||||||
|
|
||||||
|
def find_duplicate_logic_files():
|
||||||
|
"""Find files with similar patterns that might indicate duplicate logic."""
|
||||||
|
patterns = {
|
||||||
|
'calculate_track_progress': [],
|
||||||
|
'cascade_blocks': [],
|
||||||
|
'topological_sort': [],
|
||||||
|
'push_mma_state': [],
|
||||||
|
'active_tickets': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for f in Path('src').glob('*.py'):
|
||||||
|
content = f.read_text(encoding='utf-8', errors='ignore')
|
||||||
|
for pattern in patterns:
|
||||||
|
if re.search(pattern, content):
|
||||||
|
patterns[pattern].append(f.name)
|
||||||
|
|
||||||
|
return patterns
|
||||||
|
|
||||||
|
def check_ticket_state_management():
|
||||||
|
"""Check for state management issues in ticket handling."""
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
# Check if there are parallel ticket representations
|
||||||
|
gui_2_content = Path('src/gui_2.py').read_text(encoding='utf-8', errors='ignore')
|
||||||
|
app_ctrl_content = Path('src/app_controller.py').read_text(encoding='utf-8', errors='ignore')
|
||||||
|
dag_content = Path('src/dag_engine.py').read_text(encoding='utf-8', errors='ignore')
|
||||||
|
|
||||||
|
# gui_2 uses dict-based tickets
|
||||||
|
if 'active_tickets' in gui_2_content:
|
||||||
|
if 'ticket["status"]' in gui_2_content or "t['status']" in gui_2_content:
|
||||||
|
issues.append(("gui_2.py", "Dict-based ticket access pattern found"))
|
||||||
|
|
||||||
|
# Check for blocking logic duplication
|
||||||
|
gui_blocking = len(re.findall(r'_cb_block_ticket|_cb_unblock_ticket', gui_2_content))
|
||||||
|
dag_blocking = len(re.findall(r'cascade_blocks', dag_content))
|
||||||
|
|
||||||
|
if gui_blocking > 0 and dag_blocking > 0:
|
||||||
|
issues.append(("architecture", "GUI has manual block/unblock that could conflict with DAG cascade_blocks"))
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def check_import_issues():
|
||||||
|
"""Check for actual import problems - nested imports causing runtime issues."""
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
for f in Path('src').glob('*.py'):
|
||||||
|
try:
|
||||||
|
content = f.read_text(encoding='utf-8', errors='ignore')
|
||||||
|
tree = ast.parse(content, filename=str(f))
|
||||||
|
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.FunctionDef):
|
||||||
|
for child in ast.walk(node):
|
||||||
|
if isinstance(child, (ast.Import, ast.ImportFrom)):
|
||||||
|
line = child.lineno or 0
|
||||||
|
# Check if this import is inside a HOT PATH function
|
||||||
|
if node.name in ['_process_pending_gui_tasks', '_gui_func', 'run', 'tick']:
|
||||||
|
issues.append((f.name, f"Nested import `{ast.unparse(child).strip()[:50]}` in hot path `{node.name}` line {line}"))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def check_potential_bugs():
|
||||||
|
"""Check for potential bugs - undefined variables, etc."""
|
||||||
|
bugs = []
|
||||||
|
|
||||||
|
# Check for == None vs is None patterns
|
||||||
|
for f in Path('src').glob('*.py'):
|
||||||
|
content = f.read_text(encoding='utf-8', errors='ignore')
|
||||||
|
lines = content.split('\n')
|
||||||
|
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
# Skip comments
|
||||||
|
if line.strip().startswith('#'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check for mutable default arguments (common bug)
|
||||||
|
if re.search(r'def\s+\w+\([^)]*=\s*\[\s*\]', line):
|
||||||
|
bugs.append((f.name, i, "Mutable default argument", line.strip()[:60]))
|
||||||
|
if re.search(r'def\s+\w+\([^)]*=\s*\{\s*\}', line):
|
||||||
|
bugs.append((f.name, i, "Mutable default argument", line.strip()[:60]))
|
||||||
|
|
||||||
|
return bugs
|
||||||
|
|
||||||
|
def check_actual_duplicates():
|
||||||
|
"""Check for ACTUAL duplicate code - same logic copied."""
|
||||||
|
duplicates = []
|
||||||
|
seen_snippets = defaultdict(list)
|
||||||
|
|
||||||
|
# Look for duplicate patterns (3+ lines identical)
|
||||||
|
for f in sorted(Path('src').glob('*.py')):
|
||||||
|
try:
|
||||||
|
content = f.read_text(encoding='utf-8', errors='ignore')
|
||||||
|
lines = content.split('\n')
|
||||||
|
|
||||||
|
# Normalize and check consecutive duplicate lines
|
||||||
|
prev_normalized = None
|
||||||
|
dup_start = None
|
||||||
|
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
if line.strip().startswith('#'):
|
||||||
|
prev_normalized = None
|
||||||
|
dup_start = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
normalized = line.strip()
|
||||||
|
if not normalized:
|
||||||
|
prev_normalized = None
|
||||||
|
dup_start = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
if normalized == prev_normalized:
|
||||||
|
if dup_start is None:
|
||||||
|
dup_start = i - 1
|
||||||
|
else:
|
||||||
|
if dup_start and i - dup_start >= 3:
|
||||||
|
# Found 3+ consecutive duplicate lines
|
||||||
|
duplicates.append((f.name, dup_start, i - 1, lines[dup_start-1].strip()[:60]))
|
||||||
|
dup_start = None
|
||||||
|
prev_normalized = normalized
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return duplicates
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 70)
|
||||||
|
print("FOCUSED ENTROPY AUDIT - Muratori Style")
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("1. TICKET STATE MANAGEMENT ISSUES")
|
||||||
|
print("-" * 40)
|
||||||
|
issues = check_ticket_state_management()
|
||||||
|
if issues:
|
||||||
|
for issue in issues:
|
||||||
|
print(f" [{issue[0]}] {issue[1]}")
|
||||||
|
else:
|
||||||
|
print(" None found")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("2. NESTED IMPORTS IN HOT PATH FUNCTIONS")
|
||||||
|
print("-" * 40)
|
||||||
|
issues = check_import_issues()
|
||||||
|
if issues:
|
||||||
|
for fname, msg in issues[:10]:
|
||||||
|
print(f" [{fname}] {msg}")
|
||||||
|
else:
|
||||||
|
print(" None found")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("3. POTENTIAL BUGS (mutable defaults, etc)")
|
||||||
|
print("-" * 40)
|
||||||
|
issues = check_potential_bugs()
|
||||||
|
if issues:
|
||||||
|
for fname, line, bugtype, code in issues[:10]:
|
||||||
|
print(f" [{fname}:{line}] {bugtype}: {code}")
|
||||||
|
else:
|
||||||
|
print(" None found")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("4. ACTUAL DUPLICATE CODE (3+ consecutive lines)")
|
||||||
|
print("-" * 40)
|
||||||
|
duplicates = check_actual_duplicates()
|
||||||
|
if duplicates:
|
||||||
|
for fname, start, end, code in duplicates[:10]:
|
||||||
|
print(f" [{fname}:{start}-{end}] {code}")
|
||||||
|
else:
|
||||||
|
print(" None found")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("5. PATTERN USAGE ACROSS FILES")
|
||||||
|
print("-" * 40)
|
||||||
|
patterns = find_duplicate_logic_files()
|
||||||
|
for pattern, files in patterns.items():
|
||||||
|
if len(files) > 1:
|
||||||
|
print(f" {pattern}: {files}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -2,8 +2,10 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import copy
|
import copy
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import inspect
|
||||||
from typing import Any, List, Dict, Optional, Callable
|
from typing import Any, List, Dict, Optional, Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from src import workspace_manager
|
from src import workspace_manager
|
||||||
@@ -865,7 +867,6 @@ class AppController:
|
|||||||
elif item == "btn_mma_load_track":
|
elif item == "btn_mma_load_track":
|
||||||
self._cb_load_track(str(user_data or ""))
|
self._cb_load_track(str(user_data or ""))
|
||||||
elif item in self._clickable_actions:
|
elif item in self._clickable_actions:
|
||||||
import inspect
|
|
||||||
func = self._clickable_actions[item]
|
func = self._clickable_actions[item]
|
||||||
try:
|
try:
|
||||||
sig = inspect.signature(func)
|
sig = inspect.signature(func)
|
||||||
@@ -974,7 +975,6 @@ class AppController:
|
|||||||
self.mma_streams[stream_id] = ""
|
self.mma_streams[stream_id] = ""
|
||||||
self.mma_streams[stream_id] += f"[BEAD UPDATE] {bead_id} -> status: {status}\n"
|
self.mma_streams[stream_id] += f"[BEAD UPDATE] {bead_id} -> status: {status}\n"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
|
||||||
sys.stderr.write(f"[DEBUG] Error executing GUI task: {e}\n{traceback.format_exc()}\n")
|
sys.stderr.write(f"[DEBUG] Error executing GUI task: {e}\n{traceback.format_exc()}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
print(f"Error executing GUI task: {e}")
|
print(f"Error executing GUI task: {e}")
|
||||||
@@ -1642,7 +1642,6 @@ class AppController:
|
|||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
self.event_queue.put("response", {"text": e.ui_message(), "status": "error", "role": "Vendor API"})
|
self.event_queue.put("response", {"text": e.ui_message(), "status": "error", "role": "Vendor API"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
|
||||||
sys.stderr.write(f"[DEBUG] _handle_request_event ERROR: {e}\n{traceback.format_exc()}\n")
|
sys.stderr.write(f"[DEBUG] _handle_request_event ERROR: {e}\n{traceback.format_exc()}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
self.event_queue.put("response", {"text": f"ERROR: {e}", "status": "error", "role": "System"})
|
self.event_queue.put("response", {"text": f"ERROR: {e}", "status": "error", "role": "System"})
|
||||||
@@ -2642,7 +2641,6 @@ class AppController:
|
|||||||
sys.stderr.write("[DEBUG] Enqueued user_request event\n")
|
sys.stderr.write("[DEBUG] Enqueued user_request event\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
|
||||||
sys.stderr.write(f"[DEBUG] _do_generate ERROR: {e}\n{traceback.format_exc()}\n")
|
sys.stderr.write(f"[DEBUG] _do_generate ERROR: {e}\n{traceback.format_exc()}\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
self._set_status(f"generate error: {e}")
|
self._set_status(f"generate error: {e}")
|
||||||
|
|||||||
+1
-1
@@ -35,6 +35,7 @@ from src import thinking_parser
|
|||||||
import re
|
import re
|
||||||
import difflib
|
import difflib
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import traceback
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
import win32gui
|
import win32gui
|
||||||
import win32con
|
import win32con
|
||||||
@@ -1319,7 +1320,6 @@ class App:
|
|||||||
imgui.end_popup()
|
imgui.end_popup()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR in _gui_func: {e}")
|
print(f"ERROR in _gui_func: {e}")
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
if pushed_prior_tint:
|
if pushed_prior_tint:
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ class ConductorEngine:
|
|||||||
md_content: The full markdown context (history + files) for AI workers.
|
md_content: The full markdown context (history + files) for AI workers.
|
||||||
max_ticks: Optional limit on number of iterations (for testing).
|
max_ticks: Optional limit on number of iterations (for testing).
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
tick_count = 0
|
tick_count = 0
|
||||||
while True:
|
while True:
|
||||||
if self._pause_event.is_set():
|
if self._pause_event.is_set():
|
||||||
@@ -282,10 +281,7 @@ class ConductorEngine:
|
|||||||
else:
|
else:
|
||||||
# Check if ticket has a persona with preferred_models
|
# Check if ticket has a persona with preferred_models
|
||||||
if ticket.persona_id:
|
if ticket.persona_id:
|
||||||
# Try to load preferred_models from persona
|
|
||||||
try:
|
try:
|
||||||
from src.personas import PersonaManager
|
|
||||||
from src import paths
|
|
||||||
pm = PersonaManager(Path(paths.get_project_personas_path(Path.cwd())) if paths.get_project_personas_path(Path.cwd()).exists() else None)
|
pm = PersonaManager(Path(paths.get_project_personas_path(Path.cwd())) if paths.get_project_personas_path(Path.cwd()).exists() else None)
|
||||||
personas = pm.load_all()
|
personas = pm.load_all()
|
||||||
if ticket.persona_id in personas:
|
if ticket.persona_id in personas:
|
||||||
|
|||||||
Reference in New Issue
Block a user