orchestrata_ultimate.agents.volks.codedirector.review_checks
Reference › orchestrata_ultimate › agents › volks › codedirector › review_checks
review_checks holds deterministic AST and regex code-review checks that replace items of the LLM checklist. Each check_* function takes a file path, a parsed tree or the file lines, and returns a list of issues. The set covers import ordering and relative imports (check_imports), file completeness against the workspace file list, an entry-point guard, declared dependencies (check_dependency_declaration), function length (maximum 40 lines) and parameter count (maximum 5), placeholders (check_no_placeholder, check_todo_comments), error handling (check_error_handling, check_no_except_pass), type hints, mutable default values, docstrings, line length (default 88), security patterns such as eval, exec and hard-coded secrets (check_security), lint through ruff or flake8 (check_lint), and, for tests, mock usage and real-infrastructure use (check_no_mocks, check_real_infrastructure). _FIX_INSTRUCTIONS maps issue kinds to fix guidance. The QA volk (volk) is the importer, which runs them as its review gates.
At a glance
| Source | src/orchestrata_ultimate/agents/volks/codedirector/review_checks.py |
| Lines of code | 1681 |
| Classes | 0 |
| Functions | 48 |
| Imports (internal) | orchestrata_ultimate.core.base.traces |
| Imported by | orchestrata_ultimate.agents.volks.qa.volk |
| Tests that import it | tests/agents/volks/codedirector/test_review_checks.py, tests/agents/volks/codedirector/test_review_checks_extended.py, tests/generated_agents/test_volks_codedirector_review_checks_generated.py |
Class diagram
No classes; see Functions.
Dependency diagram
API
Constants
| Name | Type | Value |
|---|---|---|
_STDLIB_MODULES |
set[str] |
set(sys.stdlib_module_names) |
_FIX_INSTRUCTIONS |
dict[str, str] |
{'SYNTAX': 'FIX: The file has a SyntaxError. Open the file, find the invalid Py… |
Functions
| Function | Signature | Summary |
|---|---|---|
_parse_file |
_parse_file(filepath: str) -> ast.Module | None |
Parse a Python file into an AST. |
_read_lines |
_read_lines(filepath: str) -> list[str] |
Read file lines. |
_is_public |
_is_public(name: str) -> bool |
Check if a name is public (no leading underscore). |
_function_line_count |
_function_line_count(node: ast.FunctionDef | ast.AsyncFunctionDef) -> int |
Count logical lines in a function body (excludes docstring). |
_get_pyproject_deps |
_get_pyproject_deps(workspace: str) -> set[str] |
Extract dependency names from pyproject.toml. |
_collect_project_modules |
_collect_project_modules(workspace: str) -> set[str] |
Discover local package names under src/ and the workspace root. |
_collect_workspace_files |
_collect_workspace_files(workspace: str) -> set[str] |
Collect all workspace file paths (relative), skipping pseudo-dirs. |
_collect_imported_modules |
_collect_imported_modules(tree: ast.Module) -> list[tuple[str, int, str]] |
Extract all imported top-level module names with line numbers. |
check_imports traces |
check_imports(filepath: str, tree: ast.Module, project_modules: set[str]) -> list[Issue] |
Check 1: IMPORTS — ordering and relative imports. |
check_file_completeness |
check_file_completeness(filepath: str, tree: ast.Module, workspace_files: set[str], project_modules: set[str]) -> list[Issue] |
Check 4: FILE_COMPLETENESS — imported local modules exist. |
check_entry_point |
check_entry_point(filepath: str, lines: list[str], is_main_file: bool) -> list[Issue] |
Check 5: ENTRY_POINT — main guard present. |
check_dependency_declaration traces |
check_dependency_declaration(filepath: str, tree: ast.Module, declared_deps: set[str], project_modules: set[str]) -> list[Issue] |
Check 6: DEPENDENCY_DECLARATION — third-party imports declared. |
check_function_length |
check_function_length(filepath: str, tree: ast.Module) -> list[Issue] |
Check 10: FUNCTION_LENGTH — max 40 lines per function. |
check_function_args |
check_function_args(filepath: str, tree: ast.Module) -> list[Issue] |
Check 11: FUNCTION_ARGS — max 5 parameters. |
check_no_placeholder |
check_no_placeholder(filepath: str, tree: ast.Module) -> list[Issue] |
Check 12: NO_PLACEHOLDER — no pass/TODO/NotImplementedError. |
check_error_handling |
check_error_handling(filepath: str, tree: ast.Module) -> list[Issue] |
Check 13: ERROR_HANDLING — no bare except. |
_missing_param_hints |
_missing_param_hints(node: ast.FunctionDef | ast.AsyncFunctionDef, rel_name: str, start: int) -> list[Issue] |
Report params (from index start) lacking a type annotation. |
_check_function_type_hints |
_check_function_type_hints(node: ast.FunctionDef | ast.AsyncFunctionDef, rel_name: str) -> list[Issue] |
Check one function's return and parameter annotations. |
check_type_hints |
check_type_hints(filepath: str, tree: ast.Module) -> list[Issue] |
Check 17: TYPE_HINTS — public functions have annotations. |
check_mutable_defaults |
check_mutable_defaults(filepath: str, tree: ast.Module) -> list[Issue] |
Check 18: DEFAULT_VALUES — no mutable defaults. |
check_docstrings traces |
check_docstrings(filepath: str, tree: ast.Module) -> list[Issue] |
Check 19: DOCSTRINGS — public classes and functions. |
check_line_length |
check_line_length(filepath: str, lines: list[str], max_len: int = 88) -> list[Issue] |
Check 21: LINE_LENGTH — max characters per line. |
check_security |
check_security(filepath: str, tree: ast.Module, lines: list[str]) -> list[Issue] |
Check 30: SECURITY — eval/exec, hardcoded secrets. |
check_todo_comments |
check_todo_comments(filepath: str, lines: list[str]) -> list[Issue] |
Check 12b: No TODO/FIXME/HACK comments left behind. |
check_no_except_pass |
check_no_except_pass(filepath: str, tree: ast.Module) -> list[Issue] |
G224: Check for bare except: and except+pass patterns. |
check_no_mocks |
check_no_mocks(filepath: str, lines: list[str]) -> list[Issue] |
Check test files for mock/stub/fake usage. |
check_real_infrastructure traces |
check_real_infrastructure(filepath: str, lines: list[str]) -> list[Issue] |
G223: Verify test files use real infrastructure, not skips/mocks. |
check_lint traces |
check_lint(filepath: str) -> list[Issue] |
Run ruff or flake8 on a file for undefined names and unused imports. |
_is_validator_call |
_is_validator_call(child: ast.AST) -> bool |
True if child is a call to a validator-named function. |
_is_validation_raise |
_is_validation_raise(child: ast.AST) -> bool |
True if child raises a TypeError/ValueError-style exception. |
_has_validation_pattern |
_has_validation_pattern(node: ast.AST) -> bool |
True if a function body contains any recognized validation pattern. |
_validatable_params |
_validatable_params(node: ast.FunctionDef | ast.AsyncFunctionDef) -> list[ast.arg] |
Return the function's parameters excluding a leading self/cls. |
check_parameter_validation |
check_parameter_validation(filepath: str, tree: ast.Module) -> list[Issue] |
G230: Check that public methods validate their parameters. |
_has_traces_decorator |
_has_traces_decorator(node: ast.AST) -> bool |
Check if a node has a @traces (or @x.traces) decorator. |
_needs_traces |
_needs_traces(node: ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef) -> bool |
True if a public, non-exempt node is missing its @traces decorator. |
check_traceability traces |
check_traceability(filepath: str, tree: ast.Module) -> list[Issue] |
G226: Verify public classes and functions have @traces decorator. |
check_file_paths |
check_file_paths(workspace: str, files: list[str]) -> list[Issue] |
G226: Validate file paths for structure and naming. |
_is_test_path |
_is_test_path(rel_path: str) -> bool |
True if a relative path names a test file. |
_has_module_pseudo_segment |
_has_module_pseudo_segment(rel_path: str) -> bool |
True if any path segment is a scaffold Module: pseudo-segment. |
_discover_test_files |
_discover_test_files(workspace: str, known_tests: list[str]) -> list[str] |
Walk the workspace for test_*.py files not already listed. |
_common_file_checks |
_common_file_checks(filepath: str, tree: ast.Module, lines: list[str], project_modules: set[str]) -> list[Issue] |
Run the checks that apply to every file (source and test). |
_source_only_checks |
_source_only_checks(filepath: str, tree: ast.Module, lines: list[str], workspace_files: set[str], project_modules: set[str], declared_deps: set[str]) -> list[Issue] |
Run the checks that apply to source files only (not tests). |
run_deterministic_review traces |
run_deterministic_review(workspace: str, files: list[str]) -> list[Issue] |
Run all deterministic checks on source files. |
review_gate_syntax traces |
review_gate_syntax(workspace: str, files: list[str]) -> list[Issue] |
Gate 1: Syntax check. |
review_gate_lint traces |
review_gate_lint(workspace: str, files: list[str]) -> list[Issue] |
Gate 2: Lint and style. |
review_gate_checklist traces |
review_gate_checklist(workspace: str, files: list[str]) -> list[Issue] |
Gate 3: Full code review checklist. |
get_fix_instructions traces |
get_fix_instructions(issues: list[Issue]) -> str |
Generate clear fix instructions for each issue. |
format_review_report |
format_review_report(issues: list[Issue]) -> str |
Format issues into a human-readable review report. |
Source
"""Deterministic code review checks — no LLM needed.
Replaces checklist items 1, 4, 5, 6, 10, 11, 12, 13, 17, 18, 19, 21, 30
from the CODE_REVIEW_PROMPT with fast, reliable AST/regex analysis.
Item 2 (NO_MOCKING) is already handled by qa.py._validate_no_mocks.
Usage:
from orchestrata_ultimate.agents.volks.codedirector.review_checks import run_deterministic_review
issues = run_deterministic_review(workspace, files)
# issues: list of (check_id, filepath, description)
"""
import ast
import os
import re
import subprocess
import sys
from orchestrata_ultimate.core.base.traces import traces
# Each issue is (check_id, filepath, description)
Issue = tuple[str, str, str]
# Stdlib top-level module names (common subset for fast lookup)
_STDLIB_MODULES: set[str] = set(sys.stdlib_module_names)
# ── Helpers ──────────────────────────────────────────────────────
def _parse_file(filepath: str) -> ast.Module | None:
"""Parse a Python file into an AST. Returns None on failure."""
try:
with open(filepath, encoding="utf-8") as f:
source = f.read()
return ast.parse(source, filename=filepath)
except SyntaxError, OSError:
return None
def _read_lines(filepath: str) -> list[str]:
"""Read file lines. Returns empty list on failure."""
try:
with open(filepath, encoding="utf-8") as f:
return f.readlines()
except OSError:
return []
def _is_public(name: str) -> bool:
"""Check if a name is public (no leading underscore)."""
return not name.startswith("_")
def _function_line_count(
node: ast.FunctionDef | ast.AsyncFunctionDef,
) -> int:
"""Count logical lines in a function body (excludes docstring)."""
body = node.body
start = 0
# Skip docstring
if (
body
and isinstance(body[0], ast.Expr)
and isinstance(body[0].value, ast.Constant)
):
start = 1
if not body[start:]:
return 0
first = body[start].lineno
last = body[-1].end_lineno or body[-1].lineno
return last - first + 1
def _get_pyproject_deps(workspace: str) -> set[str]:
"""Extract dependency names from pyproject.toml."""
deps: set[str] = set()
toml_path = os.path.join(workspace, "pyproject.toml")
if not os.path.exists(toml_path):
return deps
try:
with open(toml_path, encoding="utf-8") as f:
content = f.read()
except OSError:
return deps
# Simple regex extraction — avoids tomli dependency
in_deps = False
for line in content.splitlines():
stripped = line.strip()
if stripped in ("dependencies = [", "dependencies=["):
in_deps = True
continue
if in_deps:
if stripped == "]":
break
# Match "flask>=3.0" or "redis" etc
m = re.match(r'["\']([a-zA-Z0-9_-]+)', stripped)
if m:
# Normalize: PyPI names are case-insensitive, dashes = underscores
deps.add(m.group(1).lower().replace("-", "_"))
return deps
# Directory-name prefixes for scaffold pseudo-dirs like "Module: src/".
_SKIP_DIR_PREFIXES = ("Module:", "Module ")
def _collect_project_modules(workspace: str) -> set[str]:
"""Discover local package names under ``src/`` and the workspace root.
A name is a project module if it is a directory under ``src/``, or a
workspace-root directory containing ``__init__.py``.
Args:
workspace: Path to project workspace root.
Returns:
Set of local package/module names.
"""
project_modules: set[str] = set()
src_dir = os.path.join(workspace, "src")
if os.path.isdir(src_dir):
for name in os.listdir(src_dir):
if os.path.isdir(os.path.join(src_dir, name)):
project_modules.add(name)
for name in os.listdir(workspace):
full = os.path.join(workspace, name)
if os.path.isdir(full) and os.path.exists(os.path.join(full, "__init__.py")):
project_modules.add(name)
return project_modules
def _collect_workspace_files(workspace: str) -> set[str]:
"""Collect all workspace file paths (relative), skipping pseudo-dirs.
Args:
workspace: Path to project workspace root.
Returns:
Set of workspace-relative file paths.
"""
workspace_files: set[str] = set()
for root, dirs, filenames in os.walk(workspace):
dirs[:] = [d for d in dirs if not d.startswith(_SKIP_DIR_PREFIXES)]
for fname in filenames:
rel = os.path.relpath(os.path.join(root, fname), workspace)
workspace_files.add(rel)
return workspace_files
def _collect_imported_modules(
tree: ast.Module,
) -> list[tuple[str, int, str]]:
"""Extract all imported top-level module names with line numbers.
Returns:
List of (top_level_module, line_number, full_import_string).
"""
imports = []
# Only check module-level imports (tree.body), not function-level
# inline imports. isort only reorders top-level imports, and
# function-level lazy imports (e.g. `import asyncio` inside main())
# are a valid pattern that shouldn't trigger ordering violations.
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
top = alias.name.split(".")[0]
imports.append((top, node.lineno, alias.name))
elif isinstance(node, ast.ImportFrom):
if node.module and node.level == 0:
top = node.module.split(".")[0]
imports.append((top, node.lineno, node.module))
return imports
# ── Individual Checks ────────────────────────────────────────────
@traces(spec="031", story="Deterministic Review", reqs=["REQ-031-026"])
def check_imports(
filepath: str,
tree: ast.Module,
project_modules: set[str],
) -> list[Issue]:
"""Check 1: IMPORTS — ordering and relative imports."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
# Detect relative imports
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.level > 0:
issues.append(
(
"IMPORTS",
rel_name,
f"L{node.lineno}: Relative import "
f"(level={node.level}). Use absolute imports.",
)
)
# Check import ordering: stdlib -> third-party -> local
imported = _collect_imported_modules(tree)
prev_group = 0 # 1=stdlib, 2=third-party, 3=local
for top, lineno, full in imported:
if top in _STDLIB_MODULES:
group = 1
elif top in project_modules:
group = 3
else:
group = 2
if group < prev_group:
issues.append(
(
"IMPORTS",
rel_name,
f"L{lineno}: Import '{full}' is out of order "
f"(stdlib < third-party < local).",
)
)
prev_group = group
return issues
def check_file_completeness(
filepath: str,
tree: ast.Module,
workspace_files: set[str],
project_modules: set[str],
) -> list[Issue]:
"""Check 4: FILE_COMPLETENESS — imported local modules exist."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
imported = _collect_imported_modules(tree)
for top, _lineno, full_path in imported:
if top in _STDLIB_MODULES:
continue
if top not in project_modules:
continue
# Verify the module file exists
candidates = [
f"{top}.py",
os.path.join(top, "__init__.py"),
os.path.join("src", top, "__init__.py"),
]
# Also check sub-modules for dotted imports
parts = full_path.split(".")
if len(parts) > 1:
candidates.append(os.path.join(*parts[:-1], f"{parts[-1]}.py"))
candidates.append(os.path.join("src", *parts[:-1], f"{parts[-1]}.py"))
candidates.append(os.path.join("src", *parts) + ".py")
candidates.append(os.path.join("src", *parts, "__init__.py"))
found = any(c in workspace_files for c in candidates)
if not found:
issues.append(
(
"FILE_COMPLETE",
rel_name,
f"L{_lineno}: Import '{full_path}' — "
f"module file not found in workspace.",
)
)
return issues
def check_entry_point(
filepath: str,
lines: list[str],
is_main_file: bool,
) -> list[Issue]:
"""Check 5: ENTRY_POINT — main guard present."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
if not is_main_file:
return issues
has_main_guard = any("__name__" in line and "__main__" in line for line in lines)
if not has_main_guard:
issues.append(
(
"ENTRY_POINT",
rel_name,
"Main file missing `if __name__ == '__main__':` guard.",
)
)
return issues
@traces(spec="029", story="Deterministic Review", reqs=["REQ-029-068"])
def check_dependency_declaration(
filepath: str,
tree: ast.Module,
declared_deps: set[str],
project_modules: set[str],
) -> list[Issue]:
"""Check 6: DEPENDENCY_DECLARATION — third-party imports declared."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
if not declared_deps:
return issues # No pyproject.toml, skip check
imported = _collect_imported_modules(tree)
for top, lineno, _ in imported:
if top in _STDLIB_MODULES:
continue
if top in project_modules:
continue
normalized = top.lower().replace("-", "_")
# Common aliases: PIL->pillow, cv2->opencv-python, etc.
aliases = {
"pil": "pillow",
"cv2": "opencv_python",
"sklearn": "scikit_learn",
"yaml": "pyyaml",
"attr": "attrs",
"bs4": "beautifulsoup4",
}
check_name = aliases.get(normalized, normalized)
if check_name not in declared_deps:
issues.append(
(
"DEPS",
rel_name,
f"L{lineno}: '{top}' imported but not in "
f"pyproject.toml dependencies.",
)
)
return issues
def check_function_length(filepath: str, tree: ast.Module) -> list[Issue]:
"""Check 10: FUNCTION_LENGTH — max 40 lines per function."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
length = _function_line_count(node)
if length > 40:
issues.append(
(
"FUNC_LENGTH",
rel_name,
f"L{node.lineno}: `{node.name}()` is {length} lines (max 40).",
)
)
return issues
def check_function_args(filepath: str, tree: ast.Module) -> list[Issue]:
"""Check 11: FUNCTION_ARGS — max 5 parameters."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
args = node.args
count = len(args.args) + len(args.kwonlyargs)
# Subtract 'self'/'cls'
if args.args and args.args[0].arg in ("self", "cls"):
count -= 1
if count > 5:
issues.append(
(
"FUNC_ARGS",
rel_name,
f"L{node.lineno}: `{node.name}()` has {count} params (max 5).",
)
)
return issues
def check_no_placeholder(filepath: str, tree: ast.Module) -> list[Issue]:
"""Check 12: NO_PLACEHOLDER — no pass/TODO/NotImplementedError."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
for child in ast.walk(node):
# Bare `pass` in a function with only pass + docstring
if isinstance(child, ast.Pass):
# Allow pass in exception handlers and abstract
parent_body = node.body
non_doc = [
n
for n in parent_body
if not (
isinstance(n, ast.Expr)
and isinstance(n.value, ast.Constant)
)
]
if len(non_doc) == 1 and isinstance(non_doc[0], ast.Pass):
issues.append(
(
"PLACEHOLDER",
rel_name,
f"L{node.lineno}: `{node.name}()` "
f"has empty body (only pass).",
)
)
break
# raise NotImplementedError
if (
isinstance(child, ast.Raise)
and child.exc
and isinstance(child.exc, ast.Call)
and isinstance(child.exc.func, ast.Name)
and child.exc.func.id == "NotImplementedError"
):
issues.append(
(
"PLACEHOLDER",
rel_name,
f"L{child.lineno}: NotImplementedError in `{node.name}()`.",
)
)
return issues
def check_error_handling(filepath: str, tree: ast.Module) -> list[Issue]:
"""Check 13: ERROR_HANDLING — no bare except."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
for node in ast.walk(tree):
if isinstance(node, ast.ExceptHandler):
if node.type is None:
issues.append(
(
"ERROR_HANDLING",
rel_name,
f"L{node.lineno}: Bare `except:` — "
f"use specific exception types.",
)
)
return issues
def _missing_param_hints(
node: ast.FunctionDef | ast.AsyncFunctionDef,
rel_name: str,
start: int,
) -> list[Issue]:
"""Report params (from index ``start``) lacking a type annotation."""
issues: list[Issue] = []
for arg in node.args.args[start:]:
if arg.annotation is None:
issues.append(
(
"TYPE_HINTS",
rel_name,
f"L{node.lineno}: `{node.name}()` "
f"param '{arg.arg}' has no type annotation.",
)
)
return issues
def _check_function_type_hints(
node: ast.FunctionDef | ast.AsyncFunctionDef,
rel_name: str,
) -> list[Issue]:
"""Check one function's return and parameter annotations."""
# __init__: check param annotations only (no return hint required).
if node.name == "__init__":
return _missing_param_hints(node, rel_name, 1) # skip self
if not _is_public(node.name):
return []
issues: list[Issue] = []
if node.returns is None:
issues.append(
(
"TYPE_HINTS",
rel_name,
f"L{node.lineno}: `{node.name}()` " f"missing return type annotation.",
)
)
params = node.args.args
start = 1 if params and params[0].arg in ("self", "cls") else 0
issues.extend(_missing_param_hints(node, rel_name, start))
return issues
def check_type_hints(filepath: str, tree: ast.Module) -> list[Issue]:
"""Check 17: TYPE_HINTS — public functions have annotations."""
rel_name = os.path.basename(filepath)
issues: list[Issue] = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
issues.extend(_check_function_type_hints(node, rel_name))
return issues
def check_mutable_defaults(filepath: str, tree: ast.Module) -> list[Issue]:
"""Check 18: DEFAULT_VALUES — no mutable defaults."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
mutable_types = (ast.List, ast.Dict, ast.Set)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
for default in node.args.defaults + node.args.kw_defaults:
if default is not None and isinstance(default, mutable_types):
issues.append(
(
"MUTABLE_DEFAULT",
rel_name,
f"L{node.lineno}: `{node.name}()` has "
f"mutable default argument. Use None instead.",
)
)
return issues
@traces(spec="031", story="Deterministic Review", reqs=["REQ-031-026"])
def check_docstrings(filepath: str, tree: ast.Module) -> list[Issue]:
"""Check 19: DOCSTRINGS — public classes and functions."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
if not _is_public(node.name):
continue
if not ast.get_docstring(node):
issues.append(
(
"DOCSTRING",
rel_name,
f"L{node.lineno}: `{node.name}()` missing docstring.",
)
)
elif isinstance(node, ast.ClassDef):
if not _is_public(node.name):
continue
if not ast.get_docstring(node):
issues.append(
(
"DOCSTRING",
rel_name,
f"L{node.lineno}: `{node.name}` class missing docstring.",
)
)
return issues
def check_line_length(
filepath: str, lines: list[str], max_len: int = 88
) -> list[Issue]:
"""Check 21: LINE_LENGTH — max characters per line."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
violations = 0
for i, line in enumerate(lines, 1):
stripped = line.rstrip("\n\r")
if len(stripped) > max_len:
# Allow long URLs and comments with URLs
if "http://" in stripped or "https://" in stripped:
continue
violations += 1
if violations <= 3: # Cap reported instances
issues.append(
(
"LINE_LENGTH",
rel_name,
f"L{i}: {len(stripped)} chars (max {max_len}).",
)
)
if violations > 3:
issues.append(
(
"LINE_LENGTH",
rel_name,
f"...and {violations - 3} more lines over {max_len} chars.",
)
)
return issues
def check_security(
filepath: str,
tree: ast.Module,
lines: list[str],
) -> list[Issue]:
"""Check 30: SECURITY — eval/exec, hardcoded secrets."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
# AST: detect eval/exec calls
for node in ast.walk(tree):
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
if node.func.id in ("eval", "exec"):
issues.append(
(
"SECURITY",
rel_name,
f"L{node.lineno}: `{node.func.id}()` call — "
f"potential code injection risk.",
)
)
# Regex: hardcoded secrets patterns
secret_patterns = [
(
r"(?:password|passwd|secret|api_key|apikey|token|auth)"
r'\s*=\s*["\'][^"\']{8,}["\']',
"Possible hardcoded secret",
),
(
r"(?:sk-|pk_live_|sk_live_|ghp_|gho_|AKIA)[A-Za-z0-9]{10,}",
"Possible API key/token",
),
]
for i, line in enumerate(lines, 1):
for pattern, desc in secret_patterns:
if re.search(pattern, line, re.IGNORECASE):
# Skip if it's a comment or os.getenv
stripped = line.strip()
if stripped.startswith("#"):
continue
if "os.getenv" in line or "os.environ" in line:
continue
issues.append(
(
"SECURITY",
rel_name,
f"L{i}: {desc}.",
)
)
break
return issues
def check_todo_comments(filepath: str, lines: list[str]) -> list[Issue]:
"""Check 12b: No TODO/FIXME/HACK comments left behind."""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
for i, line in enumerate(lines, 1):
stripped = line.strip()
if stripped.startswith("#"):
upper = stripped.upper()
if any(tag in upper for tag in ("TODO", "FIXME", "HACK", "XXX")):
issues.append(
(
"TODO",
rel_name,
f"L{i}: {stripped[:60]}",
)
)
return issues
_MOCK_PATTERNS = [
(r"from\s+unittest\.mock\s+import", "unittest.mock import"),
(r"import\s+unittest\.mock", "unittest.mock import"),
(r"from\s+unittest\s+import\s+mock", "unittest import mock"),
(r"Mock\s*\(", "Mock() instantiation"),
(r"MagicMock\s*\(", "MagicMock() instantiation"),
(r"AsyncMock\s*\(", "AsyncMock() instantiation"),
(r"@\s*(mock\.)?patch\s*\(", "@patch decorator"),
(r"mocker\.(patch|spy|stub|mock)\s*\(", "pytest-mock usage"),
(r"create_autospec\s*\(", "create_autospec()"),
(r"patch\.object\s*\(", "patch.object()"),
(r"patch\.dict\s*\(", "patch.dict()"),
(r"patch\.multiple\s*\(", "patch.multiple()"),
(
r"class\s+(Mock|Fake|Stub|Dummy)\w*\s*[\(:]",
"custom mock/stub/fake class",
),
(r"monkeypatch\.\w+\s*\(", "monkeypatch usage"),
]
def check_no_except_pass(filepath: str, tree: ast.Module) -> list[Issue]:
"""G224: Check for bare except: and except+pass patterns.
Args:
filepath: Path to the Python file.
tree: Parsed AST.
Returns:
List of error-swallowing violation issues.
"""
issues: list[Issue] = []
for node in ast.walk(tree):
if not isinstance(node, ast.ExceptHandler):
continue
if node.type is None:
issues.append(
(
"EXCEPT_PASS",
filepath,
f"L{node.lineno}: bare `except:` — must specify exception type",
)
)
if node.body and len(node.body) == 1 and isinstance(node.body[0], ast.Pass):
issues.append(
(
"EXCEPT_PASS",
filepath,
f"L{node.lineno}: `except: pass` swallows errors — "
"log the exception at minimum",
)
)
return issues
def check_no_mocks(filepath: str, lines: list[str]) -> list[Issue]:
"""Check test files for mock/stub/fake usage.
Args:
filepath: Path to the test file.
lines: File lines.
Returns:
List of mock violation issues.
"""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
for i, line in enumerate(lines, 1):
raw = line.strip()
if not raw or raw.startswith("#"):
continue
for pattern, desc in _MOCK_PATTERNS:
if re.search(pattern, raw):
issues.append(
(
"MOCK",
rel_name,
f"L{i}: {desc} — `{raw[:60]}`",
)
)
break # One violation per line
return issues
@traces(spec="031", story="Deterministic Review", reqs=["REQ-031-050"])
def check_real_infrastructure(
filepath: str,
lines: list[str],
) -> list[Issue]:
"""G223: Verify test files use real infrastructure, not skips/mocks.
Checks:
- No pytest.mark.skipif that skips on infra unavailability
- No MagicMock/patch (redundant with check_no_mocks but explicit)
- Tests importing redis/neo4j must actually connect (have host/URI)
- No 'localhost' when REDIS_HOST/KG_ROUTING_URI env vars exist
Args:
filepath: Path to the test file.
lines: Lines of the file.
Returns:
List of issues found.
"""
issues: list[Issue] = []
basename = os.path.basename(filepath)
# Only check test files
if not basename.startswith("test_"):
return issues
content = "\n".join(lines)
# Check for skip decorators that dodge infrastructure
skip_patterns = [
(
r"pytest\.mark\.skipif.*(?:redis|neo4j|unavailable|REDIS|NEO4J)",
"Test skips when infrastructure is unavailable — "
"tests must use real infrastructure",
),
(
r"pytest\.mark\.skip\b(?!if).*(?:redis|neo4j|infra)",
"Test unconditionally skips infrastructure tests",
),
]
for pattern, desc in skip_patterns:
for i, line in enumerate(lines, 1):
if re.search(pattern, line, re.IGNORECASE):
issues.append(("REAL_INFRA", filepath, f"L{i}: {desc}"))
# Check that tests importing redis actually set REDIS_HOST
imports_redis = "import redis" in content
imports_neo4j = "import neo4j" in content or "from neo4j" in content
has_redis_host = (
"REDIS_HOST" in content
or 'os.environ.get("REDIS_HOST"' in content
or "tests.infrastructure" in content
)
# KG_ROUTING_URI/KG_URI are the canonical graph-endpoint keys since the
# 2026-07-27 Neo4j decommission; NEO4J_URI is still RECOGNISED here (so
# pre-cutover files under review still match) but is never ADVISED --
# the remediation text below names only keys that still exist.
has_graph_uri = (
"KG_ROUTING_URI" in content
or "KG_URI" in content
or "NEO4J_URI" in content
or "resolve_uri" in content
or "tests.infrastructure" in content
)
if imports_redis and not has_redis_host:
issues.append(
(
"REAL_INFRA",
filepath,
"Imports redis but no REDIS_HOST configured — "
"must connect to real Redis (set REDIS_HOST in .env)",
)
)
if imports_neo4j and not has_graph_uri:
issues.append(
(
"REAL_INFRA",
filepath,
"Imports the neo4j Bolt driver but no graph endpoint is "
"configured — must connect to the real graph (set "
"KG_ROUTING_URI or KG_URI)",
)
)
return issues
@traces(spec="031", story="Deterministic Review", reqs=["REQ-031-021"])
def check_lint(filepath: str) -> list[Issue]:
"""Run ruff or flake8 on a file for undefined names and unused imports.
Args:
filepath: Absolute path to the Python file.
Returns:
List of lint issues found.
"""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
# G195: Skip F401 on __init__.py — re-exports are intentional
is_init = rel_name == "__init__.py"
ruff_select = "F821,F811" if is_init else "F821,F811,F401"
flake8_select = "E999,F821,F811" if is_init else "E999,F821,F811,F401"
# Try linters: system PATH first, then orchestrata venv fallback
linter_cmds = [
("ruff check", ruff_select),
("flake8", flake8_select),
("/opt/orchestrata/.venv/bin/ruff check", ruff_select),
("/opt/orchestrata/.venv/bin/flake8", flake8_select),
]
for linter_cmd, select in linter_cmds:
cmd = linter_cmd.split() + [f"--select={select}", filepath]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=10,
)
if result.returncode != 0 and result.stdout.strip():
for line in result.stdout.strip().splitlines()[:5]:
# Trim absolute path to just filename:line:col: msg
msg = line.strip()
if filepath in msg:
msg = msg.replace(filepath, rel_name)
issues.append(("LINT", rel_name, msg))
return issues # Linter found — stop trying others
except FileNotFoundError, PermissionError:
continue # Linter not installed or not executable, try next
except subprocess.TimeoutExpired:
return issues
return issues
# ── G230: Input Validation Enforcement ────────────────────────────
_VALIDATOR_PREFIXES = ("isinstance", "validate", "check", "assert", "verify")
_VALIDATION_ERROR_TYPES = {"TypeError", "ValueError", "ValidationError"}
def _is_validator_call(child: ast.AST) -> bool:
"""True if ``child`` is a call to a validator-named function."""
if not isinstance(child, ast.Call):
return False
func = child.func
name = ""
if isinstance(func, ast.Name):
name = func.id
elif isinstance(func, ast.Attribute):
name = func.attr
return any(name.startswith(p) for p in _VALIDATOR_PREFIXES)
def _is_validation_raise(child: ast.AST) -> bool:
"""True if ``child`` raises a TypeError/ValueError-style exception."""
if not (isinstance(child, ast.Raise) and child.exc):
return False
exc = child.exc
exc_name = ""
if isinstance(exc, ast.Call) and isinstance(exc.func, ast.Name):
exc_name = exc.func.id
elif isinstance(exc, ast.Name):
exc_name = exc.id
return exc_name in _VALIDATION_ERROR_TYPES
def _has_validation_pattern(node: ast.AST) -> bool:
"""True if a function body contains any recognized validation pattern.
Patterns: a validator-named call, a raise of a validation error type,
an assert statement, or an ``if ...: raise`` guard clause.
"""
for child in ast.walk(node):
if _is_validator_call(child):
return True
if _is_validation_raise(child):
return True
if isinstance(child, ast.Assert):
return True
if isinstance(child, ast.If) and any(
isinstance(s, ast.Raise) for s in child.body
):
return True
return False
def _validatable_params(
node: ast.FunctionDef | ast.AsyncFunctionDef,
) -> list[ast.arg]:
"""Return the function's parameters excluding a leading self/cls."""
params = node.args.args
start = 1 if params and params[0].arg in ("self", "cls") else 0
return params[start:]
def check_parameter_validation(
filepath: str,
tree: ast.Module,
) -> list[Issue]:
"""G230: Check that public methods validate their parameters.
Every public method that accepts parameters (beyond self/cls)
must contain at least one validation pattern:
- isinstance() check
- if param is None
- if not param
- raise TypeError/ValueError
- assert statement
- validator function call
Args:
filepath: Absolute path to the file.
tree: Parsed AST.
Returns:
List of validation issues.
"""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
# Skip test files and __init__.py
if rel_name.startswith("test_") or rel_name == "conftest.py":
return issues
if rel_name == "__init__.py":
return issues
for node in ast.walk(tree):
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
continue
if node.name.startswith("_"):
continue
real_params = _validatable_params(node)
if not real_params:
continue
if not _has_validation_pattern(node):
issues.append(
(
"INPUT_VALIDATION",
rel_name,
f"L{node.lineno}: `{node.name}("
f"{', '.join(p.arg for p in real_params)})` "
"— no input validation. Public methods must "
"validate parameters (isinstance, None check, "
"raise TypeError/ValueError).",
)
)
return issues
# ── G226: Traceability Enforcement ───────────────────────────────
# Names exempt from traceability (they ARE the traceability infrastructure).
_TRACEABILITY_EXEMPT = {"traces", "main"}
def _has_traces_decorator(node: ast.AST) -> bool:
"""Check if a node has a @traces (or @x.traces) decorator."""
for dec in getattr(node, "decorator_list", []):
if isinstance(dec, ast.Call):
func = dec.func
if isinstance(func, ast.Name) and func.id == "traces":
return True
if isinstance(func, ast.Attribute) and func.attr == "traces":
return True
elif isinstance(dec, ast.Name) and dec.id == "traces":
return True
return False
def _needs_traces(
node: ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef,
) -> bool:
"""True if a public, non-exempt node is missing its @traces decorator."""
return (
not node.name.startswith("_")
and node.name not in _TRACEABILITY_EXEMPT
and not _has_traces_decorator(node)
)
@traces(spec="031", story="Deterministic Review", reqs=["REQ-031-026"])
def check_traceability(
filepath: str,
tree: ast.Module,
) -> list[Issue]:
"""G226: Verify public classes and functions have @traces decorator.
Every public class and public function (not starting with _)
must have a @traces(spec=, story=, reqs=[]) decorator for
requirement traceability.
Args:
filepath: Absolute path to the file.
tree: Parsed AST.
Returns:
List of traceability issues.
"""
issues: list[Issue] = []
rel_name = os.path.basename(filepath)
# Skip test files — tests don't need @traces
if rel_name.startswith("test_") or rel_name == "conftest.py":
return issues
# Skip __init__.py — re-exports, no logic
if rel_name == "__init__.py":
return issues
for node in ast.iter_child_nodes(tree):
if isinstance(node, ast.ClassDef) and _needs_traces(node):
issues.append(
(
"TRACEABILITY",
rel_name,
f"L{node.lineno}: class `{node.name}` "
"missing @traces(spec=, story=, reqs=[]) decorator.",
)
)
elif isinstance(
node, (ast.FunctionDef, ast.AsyncFunctionDef)
) and _needs_traces(node):
issues.append(
(
"TRACEABILITY",
rel_name,
f"L{node.lineno}: function `{node.name}()` "
"missing @traces(spec=, story=, reqs=[]) decorator.",
)
)
return issues
# ── G226: File Path Validation ───────────────────────────────────
def check_file_paths(
workspace: str,
files: list[str],
) -> list[Issue]:
"""G226: Validate file paths for structure and naming.
Checks:
- Python file names are valid identifiers (no spaces, hyphens).
- Package dirs contain __init__.py.
- No stray .py files outside src/ or tests/.
- No path traversal (.. segments).
Args:
workspace: Project workspace root.
files: Relative file paths to check.
Returns:
List of issues found.
"""
issues: list[Issue] = []
for rel_path in files:
if not rel_path.endswith(".py"):
continue
# Path traversal check
if ".." in rel_path.split(os.sep):
issues.append(
(
"FILE_PATH",
rel_path,
"Path contains '..' traversal — not allowed.",
)
)
continue
# Valid Python filename check
basename = os.path.basename(rel_path)
stem = basename.removesuffix(".py")
if stem != "__init__" and not stem.isidentifier():
issues.append(
(
"FILE_PATH",
rel_path,
f"'{basename}' is not a valid Python module name. "
"Use snake_case with no hyphens or spaces.",
)
)
# Package __init__.py check — every directory containing
# .py files under src/ must have __init__.py
dirpath = os.path.dirname(rel_path)
if dirpath and dirpath.startswith("src"):
parts = dirpath.split(os.sep)
for i in range(1, len(parts) + 1):
pkg_dir = os.sep.join(parts[:i])
init_path = os.path.join(workspace, pkg_dir, "__init__.py")
if not os.path.exists(init_path):
issues.append(
(
"FILE_PATH",
os.path.join(pkg_dir, "__init__.py"),
f"Missing __init__.py — package '{pkg_dir}' "
"is not importable.",
)
)
return issues
# ── Orchestrator ─────────────────────────────────────────────────
# app.py excluded — Flask factory pattern uses cli.py/__main__.py as entry.
_MAIN_FILES = {"cli.py", "__main__.py", "main.py"}
def _is_test_path(rel_path: str) -> bool:
"""True if a relative path names a test file."""
return os.path.basename(rel_path).startswith("test_") or rel_path.startswith(
"tests/"
)
def _has_module_pseudo_segment(rel_path: str) -> bool:
"""True if any path segment is a scaffold ``Module:`` pseudo-segment."""
return any(part.startswith(_SKIP_DIR_PREFIXES) for part in rel_path.split(os.sep))
def _discover_test_files(workspace: str, known_tests: list[str]) -> list[str]:
"""Walk the workspace for ``test_*.py`` files not already listed.
Args:
workspace: Path to project workspace root.
known_tests: Test paths already collected from the files list.
Returns:
``known_tests`` extended in place with any newly found test files.
"""
skip_dirs = {".venv", "__pycache__", ".git", "site-packages", "node_modules"}
for root, dirs, fnames in os.walk(workspace):
dirs[:] = [
d
for d in dirs
if d not in skip_dirs and not d.startswith(_SKIP_DIR_PREFIXES)
]
for fname in fnames:
if fname.startswith("test_") and fname.endswith(".py"):
rel = os.path.relpath(os.path.join(root, fname), workspace)
if rel not in known_tests:
known_tests.append(rel)
return known_tests
def _common_file_checks(
filepath: str,
tree: ast.Module,
lines: list[str],
project_modules: set[str],
) -> list[Issue]:
"""Run the checks that apply to every file (source and test)."""
issues: list[Issue] = []
issues.extend(check_imports(filepath, tree, project_modules))
issues.extend(check_function_length(filepath, tree))
issues.extend(check_function_args(filepath, tree))
issues.extend(check_error_handling(filepath, tree))
issues.extend(check_mutable_defaults(filepath, tree))
# G128-2: Skip LINE_LENGTH — black auto-format runs before review
# and fixes everything it can. Remaining long lines are unfixable
# (long strings, URLs, identifiers) and waste review cycles.
issues.extend(check_no_mocks(filepath, lines))
issues.extend(check_no_except_pass(filepath, tree))
issues.extend(check_real_infrastructure(filepath, lines))
issues.extend(check_security(filepath, tree, lines))
issues.extend(check_lint(filepath))
# G226: Traceability + G230: Input validation enforcement
issues.extend(check_traceability(filepath, tree))
issues.extend(check_parameter_validation(filepath, tree))
return issues
def _source_only_checks(
filepath: str,
tree: ast.Module,
lines: list[str],
workspace_files: set[str],
project_modules: set[str],
declared_deps: set[str],
) -> list[Issue]:
"""Run the checks that apply to source files only (not tests)."""
is_main = os.path.basename(filepath) in _MAIN_FILES
issues: list[Issue] = []
issues.extend(
check_file_completeness(filepath, tree, workspace_files, project_modules)
)
issues.extend(check_entry_point(filepath, lines, is_main))
issues.extend(
check_dependency_declaration(filepath, tree, declared_deps, project_modules)
)
issues.extend(check_no_placeholder(filepath, tree))
issues.extend(check_type_hints(filepath, tree))
issues.extend(check_docstrings(filepath, tree))
issues.extend(check_todo_comments(filepath, lines))
return issues
@traces(
spec="029",
story="Deterministic Review",
reqs=["REQ-029-067", "REQ-029-068"],
)
def run_deterministic_review(
workspace: str,
files: list[str],
) -> list[Issue]:
"""Run all deterministic checks on source files.
Args:
workspace: Path to project workspace root.
files: List of relative file paths to review.
Returns:
List of (check_id, filepath, description) tuples.
"""
workspace_files = _collect_workspace_files(workspace)
project_modules = _collect_project_modules(workspace)
declared_deps = _get_pyproject_deps(workspace)
# G226: File path validation — runs before any code checks
all_issues: list[Issue] = list(check_file_paths(workspace, files))
# G-193-01: drop pseudo-files with a "Module:" segment, then split.
real_files = [f for f in files if not _has_module_pseudo_segment(f)]
py_source = [f for f in real_files if f.endswith(".py") and not _is_test_path(f)]
py_tests = [f for f in real_files if f.endswith(".py") and _is_test_path(f)]
_discover_test_files(workspace, py_tests)
for rel_path in py_source + py_tests:
filepath = os.path.join(workspace, rel_path)
if not os.path.exists(filepath):
continue
tree = _parse_file(filepath)
if tree is None:
all_issues.append(
("SYNTAX", rel_path, "File has syntax errors — cannot parse.")
)
continue
lines = _read_lines(filepath)
all_issues.extend(_common_file_checks(filepath, tree, lines, project_modules))
if not _is_test_path(rel_path):
all_issues.extend(
_source_only_checks(
filepath,
tree,
lines,
workspace_files,
project_modules,
declared_deps,
)
)
return all_issues
# ── G227: Gated Review Stages ────────────────────────────────────
# Three strict gates. Each must pass before the next runs.
# Gate 1: Syntax — can the files even parse?
# Gate 2: Lint & Style — are they clean?
# Gate 3: Full checklist — architecture, traceability, quality
@traces(spec="031", story="Gated Review Stages", reqs=["REQ-031-017", "REQ-031-018"])
def review_gate_syntax(
workspace: str,
files: list[str],
) -> list[Issue]:
"""Gate 1: Syntax check. Can every file parse?
If ANY file has a SyntaxError, stop here. Do not proceed
to lint or checklist — fix syntax first.
Args:
workspace: Project workspace root.
files: Relative file paths.
Returns:
List of SYNTAX issues. Empty means gate passed.
"""
issues: list[Issue] = []
# File path validation first
issues.extend(check_file_paths(workspace, files))
if issues:
return issues
py_files = [f for f in files if f.endswith(".py")]
for rel_path in py_files:
filepath = os.path.join(workspace, rel_path)
if not os.path.exists(filepath):
continue
tree = _parse_file(filepath)
if tree is None:
issues.append(
(
"SYNTAX",
rel_path,
"SyntaxError — file cannot be parsed as Python.",
)
)
return issues
@traces(spec="031", story="Gated Review Stages", reqs=["REQ-031-021"])
def review_gate_lint(
workspace: str,
files: list[str],
) -> list[Issue]:
"""Gate 2: Lint and style. Are files clean?
Only runs AFTER Gate 1 (syntax) passes. Checks:
- Linter errors (flake8/ruff)
- Import issues (absolute only, correct order)
- Mutable defaults
- No mocks, no except+pass
- No bare except
- Security issues
Args:
workspace: Project workspace root.
files: Relative file paths.
Returns:
List of lint/style issues. Empty means gate passed.
"""
issues: list[Issue] = []
# Determine project modules for import validation
project_modules = _collect_project_modules(workspace)
py_files = [f for f in files if f.endswith(".py")]
for rel_path in py_files:
filepath = os.path.join(workspace, rel_path)
if not os.path.exists(filepath):
continue
tree = _parse_file(filepath)
if tree is None:
continue # Gate 1 would have caught this
lines = _read_lines(filepath)
issues.extend(check_lint(filepath))
issues.extend(check_imports(filepath, tree, project_modules))
issues.extend(check_mutable_defaults(filepath, tree))
issues.extend(check_no_mocks(filepath, lines))
issues.extend(check_no_except_pass(filepath, tree))
issues.extend(check_security(filepath, tree, lines))
issues.extend(check_real_infrastructure(filepath, lines))
return issues
@traces(spec="031", story="Gated Review Stages", reqs=["REQ-031-025", "REQ-031-026"])
def review_gate_checklist(
workspace: str,
files: list[str],
) -> list[Issue]:
"""Gate 3: Full code review checklist.
Only runs AFTER Gate 1 (syntax) and Gate 2 (lint) pass.
Checks architecture, traceability, completeness, quality:
- Function length (≤40 lines)
- Function args (≤5 params)
- Error handling
- Traceability (@traces decorators)
- File completeness
- Entry points
- Dependency declarations
- No placeholders (NotImplementedError/pass/TODO)
- Type hints
- Docstrings
Args:
workspace: Project workspace root.
files: Relative file paths.
Returns:
List of checklist issues. Empty means gate passed.
"""
issues: list[Issue] = []
workspace_files = _collect_workspace_files(workspace)
project_modules = _collect_project_modules(workspace)
declared_deps = _get_pyproject_deps(workspace)
py_files = [f for f in files if f.endswith(".py")]
for rel_path in py_files:
filepath = os.path.join(workspace, rel_path)
if not os.path.exists(filepath):
continue
tree = _parse_file(filepath)
if tree is None:
continue
lines = _read_lines(filepath)
# Quality checks — all files
issues.extend(check_function_length(filepath, tree))
issues.extend(check_function_args(filepath, tree))
issues.extend(check_error_handling(filepath, tree))
issues.extend(check_traceability(filepath, tree))
issues.extend(check_parameter_validation(filepath, tree))
# Source-only checks
if not _is_test_path(rel_path):
issues.extend(
_source_only_checks(
filepath,
tree,
lines,
workspace_files,
project_modules,
declared_deps,
)
)
return issues
# ── G226: Fix Instruction Generator ──────────────────────────────
# Every check type maps to a clear, actionable fix instruction
# that tells the LLM exactly what to do. No ambiguity.
_FIX_INSTRUCTIONS: dict[str, str] = {
"SYNTAX": (
"FIX: The file has a SyntaxError. Open the file, find the "
"invalid Python, and rewrite it as valid Python code. "
"Common causes: prose text instead of code, unclosed "
"brackets, invalid indentation."
),
"LINT": (
"FIX: Run the linter message through the code. Fix the "
"exact line and column referenced. Common fixes: remove "
"unused imports, fix undefined names, add missing colons."
),
"IMPORTS": (
"FIX: Fix the import statement. Use absolute imports only "
"(no relative imports). Ensure the module exists. Order: "
"stdlib, third-party, local — separated by blank lines."
),
"FUNC_LENGTH": (
"FIX: Split the function into smaller helpers. Extract "
"logical blocks into private functions with clear names. "
"Each function must be ≤40 lines."
),
"FUNC_ARGS": (
"FIX: Reduce parameter count to ≤5. Group related params "
"into a dataclass or TypedDict. Use **kwargs only if the "
"function is a wrapper."
),
"ERROR_HANDLING": (
"FIX: Add try/except with specific exception types around "
"the operation. Never use bare except:. Always log the "
"exception: except SomeError as exc: logger.error(exc)"
),
"MUTABLE_DEFAULT": (
"FIX: Replace mutable default (list, dict, set) with None "
"and initialize inside the function body: "
"if param is None: param = []"
),
"NO_MOCKS": (
"FIX: Remove all unittest.mock usage. Test real behavior "
"with real infrastructure. No MagicMock, patch, or AsyncMock."
),
"EXCEPT_PASS": (
"FIX: Replace 'except: pass' with specific exception type "
"and logging: except SpecificError as exc: "
"logger.debug('%s', exc)"
),
"REAL_INFRA": (
"FIX: Remove pytest.skip/skipif. Tests must use real "
"infrastructure (Redis, the graph). Connect to actual services."
),
"SECURITY": (
"FIX: Remove the security vulnerability. No eval/exec with "
"user input, no hardcoded secrets, no SQL injection via "
"string concatenation. Use parameterized queries."
),
"FILE_COMPLETE": (
"FIX: The file is missing required content. Check that all "
"classes and functions referenced by other files are defined."
),
"ENTRY_POINT": (
"FIX: Add 'if __name__ == \"__main__\":' guard to the main entry point file."
),
"DEPENDENCY": (
"FIX: Add the missing dependency to pyproject.toml under "
"[project.dependencies]. Use the correct package name."
),
"PLACEHOLDER": (
"FIX: Replace NotImplementedError/pass/TODO with real "
"implementation code. Every function must have a working body."
),
"TYPE_HINTS": (
"FIX: Add type hints to ALL public function parameters and "
"return types. Python 3.14 syntax — use modern generics:\n"
" def func(name: str, items: list[int] = None) -> dict[str, Any]:\n"
" Use list[], dict[], set[], tuple[] (NOT List, Dict, Set, Tuple)\n"
" Use X | None (NOT Optional[X])\n"
" Use X | Y (NOT Union[X, Y])\n"
" __init__ needs param hints but no -> return hint."
),
"DOCSTRING": (
"FIX: Add a Google-style docstring. Format:\n"
' """One-line summary.\n\n'
" Args:\n param: Description.\n\n"
" Returns:\n Description.\n"
' """'
),
"TODO": (
"FIX: Remove the TODO/FIXME/HACK comment and implement "
"the functionality now. No deferred work."
),
"TRACEABILITY": (
"FIX: Add @traces decorator to the class/function. Format:\n"
" @traces(spec='001', story='US-001', reqs=['REQ-001'])\n"
"If orchestrata.core.traceability is not available, define:\n"
" def traces(**kw): return lambda f: f"
),
"FILE_PATH": (
"FIX: Rename the file to use a valid Python module name "
"(snake_case, no hyphens, no spaces). Ensure all package "
"directories have __init__.py."
),
"INPUT_VALIDATION": (
"FIX: Add input validation at the start of the method.\n"
" For each parameter, add one or more of:\n"
" - Type check: if not isinstance(param, expected_type): "
"raise TypeError(...)\n"
" - None check: if param is None: raise ValueError(...)\n"
" - Range check: if param < 0: raise ValueError(...)\n"
" - Call a validator: validate_param(param)\n"
" Every public method must validate every parameter."
),
"IMPORT_ERROR": (
"FIX: The import fails at runtime. Check that the module "
"exists, is installed, and the import path is correct."
),
}
@traces(spec="031", story="Gated Review Stages", reqs=["REQ-031-028"])
def get_fix_instructions(issues: list[Issue]) -> str:
"""Generate clear fix instructions for each issue.
Args:
issues: List of (check_id, filepath, description) tuples.
Returns:
Formatted fix instructions string for the LLM.
"""
if not issues:
return ""
lines = ["FIX INSTRUCTIONS (mandatory — resolve ALL before handoff):\n"]
seen_checks: set[str] = set()
for check_id, filepath, desc in issues:
instruction = _FIX_INSTRUCTIONS.get(
check_id,
f"FIX: Resolve the {check_id} issue in {filepath}.",
)
lines.append(f" [{check_id}] {filepath}: {desc}")
if check_id not in seen_checks:
lines.append(f" → {instruction}")
seen_checks.add(check_id)
lines.append("")
lines.append(
"CRITICAL: You MUST fix ALL issues above. Do NOT hand off "
"or escalate until every single issue is resolved. "
"Partial fixes are rejected."
)
return "\n".join(lines)
def format_review_report(issues: list[Issue]) -> str:
"""Format issues into a human-readable review report.
Args:
issues: List of (check_id, filepath, description) tuples.
Returns:
Formatted review string.
"""
if not issues:
return "DETERMINISTIC_REVIEW: PASS (0 issues)"
# Group by check_id
by_check: dict[str, list[tuple[str, str]]] = {}
for check_id, filepath, desc in issues:
by_check.setdefault(check_id, []).append((filepath, desc))
lines = [
f"DETERMINISTIC_REVIEW: FAIL ({len(issues)} issues)",
"",
]
for check_id, file_issues in sorted(by_check.items()):
lines.append(f"[{check_id}] ({len(file_issues)} issues)")
for filepath, desc in file_issues[:5]:
lines.append(f" - {desc}")
if len(file_issues) > 5:
lines.append(f" ...and {len(file_issues) - 5} more")
lines.append("")
return "\n".join(lines)