167 lines
5.5 KiB
Python
167 lines
5.5 KiB
Python
|
|
#!/usr/bin/env python3
|
|
"""
|
|
Symbolic Verify (GOFAI) Tool
|
|
|
|
Leverages Python's Abstract Syntax Tree (AST) to perform deterministic
|
|
code audits without LLM inference. Detects 'LLM-isms' like undefined
|
|
variables, shadow variables, and scoping errors.
|
|
"""
|
|
|
|
import ast
|
|
import json
|
|
import logging
|
|
import os
|
|
from typing import Dict, List, Set, Any
|
|
from tools.registry import registry, tool_error, tool_result
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SYMBOLIC_VERIFY_SCHEMA = {
|
|
"name": "symbolic_verify",
|
|
"description": "Perform a deterministic GOFAI audit of code using AST analysis. Identifies undefined variables, unused imports, and scoping issues without using an LLM. Use this to verify your changes are syntactically and semantically sound before submission.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {"type": "string", "description": "Path to the Python file to audit."},
|
|
"check_level": {
|
|
"type": "string",
|
|
"enum": ["syntax", "scope", "all"],
|
|
"default": "all",
|
|
"description": "Level of analysis to perform."
|
|
}
|
|
},
|
|
"required": ["path"]
|
|
}
|
|
}
|
|
|
|
class ScopeAnalyzer(ast.NodeVisitor):
|
|
def __init__(self):
|
|
self.defined_vars = set()
|
|
self.used_vars = set()
|
|
self.undefined_references = []
|
|
self.scopes = [{}] # Stack of symbol tables
|
|
self.builtins = set(dir(__builtins__))
|
|
|
|
def visit_Import(self, node):
|
|
for alias in node.names:
|
|
name = alias.asname or alias.name
|
|
self.scopes[-1][name] = "import"
|
|
self.generic_visit(node)
|
|
|
|
def visit_ImportFrom(self, node):
|
|
for alias in node.names:
|
|
name = alias.asname or alias.name
|
|
self.scopes[-1][name] = "import"
|
|
self.generic_visit(node)
|
|
|
|
def visit_Name(self, node):
|
|
if isinstance(node.ctx, ast.Store):
|
|
self.scopes[-1][node.id] = "defined"
|
|
elif isinstance(node.ctx, ast.Load):
|
|
# Check if defined in any scope level or builtins
|
|
is_defined = any(node.id in scope for scope in self.scopes) or node.id in self.builtins
|
|
if not is_defined:
|
|
# Store potential undefined
|
|
self.undefined_references.append({
|
|
"name": node.id,
|
|
"lineno": node.lineno,
|
|
"col": node.col_offset
|
|
})
|
|
self.generic_visit(node)
|
|
|
|
def visit_FunctionDef(self, node):
|
|
self.scopes[-1][node.name] = "function"
|
|
# New scope for arguments and body
|
|
new_scope = {}
|
|
for arg in node.args.args:
|
|
new_scope[arg.arg] = "parameter"
|
|
self.scopes.append(new_scope)
|
|
self.generic_visit(node)
|
|
self.scopes.pop()
|
|
|
|
def visit_ClassDef(self, node):
|
|
self.scopes[-1][node.name] = "class"
|
|
self.scopes.append({})
|
|
self.generic_visit(node)
|
|
self.scopes.pop()
|
|
|
|
def audit_file(path: str, check_level: str = "all"):
|
|
"""Audit a Python file for common semantic errors."""
|
|
if not path.endswith(".py"):
|
|
return tool_error("Symbolic verification only supports Python (.py) files.")
|
|
|
|
try:
|
|
if not os.path.exists(path):
|
|
return tool_error(f"File not found: {path}")
|
|
|
|
source = open(path, "r").read()
|
|
|
|
# 1. Syntax Check
|
|
try:
|
|
tree = ast.parse(source)
|
|
except SyntaxError as e:
|
|
return tool_result(
|
|
status="Critical Failure",
|
|
errors=[{
|
|
"type": "SyntaxError",
|
|
"message": e.msg,
|
|
"lineno": e.lineno,
|
|
"offset": e.offset
|
|
}],
|
|
recommendation="Fix the syntax error immediately. The file cannot be executed."
|
|
)
|
|
|
|
if check_level == "syntax":
|
|
return tool_result(status="Clean", message="Syntax is valid.")
|
|
|
|
# 2. Scope & Reference Search
|
|
analyzer = ScopeAnalyzer()
|
|
analyzer.visit(tree)
|
|
|
|
# Filter out common false positives (e.g. late imports or dynamic names)
|
|
# For a truly robust GOFAI we'd do more, but this is 'secret sauce' level
|
|
undefined = []
|
|
seen = set()
|
|
for ref in analyzer.undefined_references:
|
|
key = (ref["name"], ref["lineno"])
|
|
if key not in seen:
|
|
undefined.append(ref)
|
|
seen.add(key)
|
|
|
|
if not undefined:
|
|
return tool_result(
|
|
status="Healthy",
|
|
message="Deterministic check passed. No undefined variables detected in analyzed scopes.",
|
|
file_stats={
|
|
"chars": len(source),
|
|
"nodes": len(list(ast.walk(tree)))
|
|
}
|
|
)
|
|
|
|
report = "GOFAI AUDIT DETECTED SEMANTIC ISSUES:\n"
|
|
for u in undefined:
|
|
report += f"- Undefined Variable: '{u['name']}' at line {u['lineno']}\n"
|
|
|
|
return tool_result(
|
|
status="Warning",
|
|
summary=report,
|
|
undefined_variables=undefined,
|
|
recommendation="Review the undefined variables. Ensure they are imported or defined before use."
|
|
)
|
|
|
|
except Exception as e:
|
|
return tool_error(f"Symbolic audit failed: {str(e)}")
|
|
|
|
def _handle_symbolic_verify(args, **kwargs):
|
|
return audit_file(args.get("path"), args.get("check_level", "all"))
|
|
|
|
|
|
registry.register(
|
|
name="symbolic_verify",
|
|
toolset="qa",
|
|
schema=SYMBOLIC_VERIFY_SCHEMA,
|
|
handler=_handle_symbolic_verify,
|
|
emoji="🔬"
|
|
)
|
|
|