checkpoint

This commit is contained in:
2026-02-27 20:41:30 -05:00
parent 6c887e498d
commit 138e31374b
9 changed files with 199 additions and 21 deletions

View File

@@ -5,11 +5,10 @@ model: gemini-3.1-pro-preview
tools: tools:
- read_file - read_file
- list_directory - list_directory
- glob - discovered_tool_search_files
- grep_search - grep_search
- google_web_search - discovered_tool_web_search
- web_fetch - discovered_tool_fetch_url
- codebase_investigator
- activate_skill - activate_skill
- discovered_tool_run_powershell - discovered_tool_run_powershell
--- ---

View File

@@ -7,11 +7,10 @@ tools:
- write_file - write_file
- replace - replace
- list_directory - list_directory
- glob - discovered_tool_search_files
- grep_search - grep_search
- google_web_search - discovered_tool_web_search
- web_fetch - discovered_tool_fetch_url
- codebase_investigator
- activate_skill - activate_skill
- discovered_tool_run_powershell - discovered_tool_run_powershell
--- ---

View File

@@ -7,11 +7,10 @@ tools:
- write_file - write_file
- replace - replace
- list_directory - list_directory
- glob - discovered_tool_search_files
- grep_search - grep_search
- google_web_search - discovered_tool_web_search
- web_fetch - discovered_tool_fetch_url
- codebase_investigator
- activate_skill - activate_skill
- discovered_tool_run_powershell - discovered_tool_run_powershell
--- ---

View File

@@ -5,11 +5,10 @@ model: gemini-2.5-flash-lite
tools: tools:
- read_file - read_file
- list_directory - list_directory
- glob - discovered_tool_search_files
- grep_search - grep_search
- google_web_search - discovered_tool_web_search
- web_fetch - discovered_tool_fetch_url
- codebase_investigator
- activate_skill - activate_skill
- discovered_tool_run_powershell - discovered_tool_run_powershell
--- ---

View File

@@ -3,7 +3,8 @@
"enableAgents": true "enableAgents": true
}, },
"tools": { "tools": {
"discoveryCommand": "python C:/projects/manual_slop/scripts/tool_discovery.py", "discoveryCommand": "uv run python C:/projects/manual_slop/scripts/tool_discovery.py",
"callCommand": "uv run python C:/projects/manual_slop/scripts/tool_call.py",
"whitelist": [ "whitelist": [
"*" "*"
] ]

View File

@@ -31,6 +31,7 @@ so the AI doesn't wander outside the project workspace.
from pathlib import Path from pathlib import Path
import summarize import summarize
import outline_tool
import urllib.request import urllib.request
import urllib.parse import urllib.parse
from html.parser import HTMLParser from html.parser import HTMLParser
@@ -261,6 +262,25 @@ def get_python_skeleton(path: str) -> str:
return f"ERROR generating skeleton for '{path}': {e}" return f"ERROR generating skeleton for '{path}': {e}"
def get_code_outline(path: str) -> str:
"""
Returns a hierarchical outline of a code file (classes, functions, methods with line ranges).
"""
p, err = _resolve_and_check(path)
if err:
return err
if not p.exists():
return f"ERROR: file not found: {path}"
if not p.is_file():
return f"ERROR: not a file: {path}"
try:
code = p.read_text(encoding="utf-8")
return outline_tool.get_outline(p, code)
except Exception as e:
return f"ERROR generating outline for '{path}': {e}"
# ------------------------------------------------------------------ web tools # ------------------------------------------------------------------ web tools
@@ -387,7 +407,7 @@ def get_ui_performance() -> str:
# ------------------------------------------------------------------ tool dispatch # ------------------------------------------------------------------ tool dispatch
TOOL_NAMES = {"read_file", "list_directory", "search_files", "get_file_summary", "get_python_skeleton", "web_search", "fetch_url", "get_ui_performance"} TOOL_NAMES = {"read_file", "list_directory", "search_files", "get_file_summary", "get_python_skeleton", "get_code_outline", "web_search", "fetch_url", "get_ui_performance"}
def dispatch(tool_name: str, tool_input: dict) -> str: def dispatch(tool_name: str, tool_input: dict) -> str:
@@ -404,6 +424,8 @@ def dispatch(tool_name: str, tool_input: dict) -> str:
return get_file_summary(tool_input.get("path", "")) return get_file_summary(tool_input.get("path", ""))
if tool_name == "get_python_skeleton": if tool_name == "get_python_skeleton":
return get_python_skeleton(tool_input.get("path", "")) return get_python_skeleton(tool_input.get("path", ""))
if tool_name == "get_code_outline":
return get_code_outline(tool_input.get("path", ""))
if tool_name == "web_search": if tool_name == "web_search":
return web_search(tool_input.get("query", "")) return web_search(tool_input.get("query", ""))
if tool_name == "fetch_url": if tool_name == "fetch_url":
@@ -511,6 +533,24 @@ MCP_TOOL_SPECS = [
"required": ["path"], "required": ["path"],
}, },
}, },
{
"name": "get_code_outline",
"description": (
"Get a hierarchical outline of a code file. "
"This returns classes, functions, and methods with their line ranges and brief docstrings. "
"Use this to quickly map out a file's structure before reading specific sections."
),
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the code file (currently supports .py).",
}
},
"required": ["path"],
},
},
{ {
"name": "web_search", "name": "web_search",
"description": "Search the web using DuckDuckGo. Returns the top 5 search results with titles, URLs, and snippets. Chain this with fetch_url to read specific pages.", "description": "Search the web using DuckDuckGo. Returns the top 5 search results with titles, URLs, and snippets. Chain this with fetch_url to read specific pages.",

94
outline_tool.py Normal file
View File

@@ -0,0 +1,94 @@
import tree_sitter
import tree_sitter_python
from pathlib import Path
class CodeOutliner:
def __init__(self):
self.language = tree_sitter.Language(tree_sitter_python.language())
self.parser = tree_sitter.Parser(self.language)
def outline(self, code: str) -> str:
tree = self.parser.parse(bytes(code, "utf8"))
lines = code.splitlines()
output = []
def get_docstring(node):
# In Python, docstring is usually the first expression statement in a block
body = node.child_by_field_name("body")
if body and body.type == "block":
for child in body.children:
if child.type == "comment":
continue
if child.type == "expression_statement":
expr = child.children[0]
if expr.type == "string":
doc = code[expr.start_byte:expr.end_byte].strip()
# Strip quotes
if doc.startswith(('"""', "'''")):
doc = doc[3:-3]
elif doc.startswith(('"', "'")):
doc = doc[1:-1]
return doc.splitlines()[0] if doc else ""
break
return None
def walk(node, indent=0):
node_type = node.type
name = None
if node_type == "class_definition":
name_node = node.child_by_field_name("name")
if name_node:
name = code[name_node.start_byte:name_node.end_byte]
start_line = node.start_point.row + 1
end_line = node.end_point.row + 1
output.append(f"{' ' * indent}[Class] {name} (Lines {start_line}-{end_line})")
doc = get_docstring(node)
if doc:
output.append(f"{' ' * (indent + 1)}\"\"\"{doc}\"\"\"")
elif node_type in ("function_definition", "async_function_definition"):
name_node = node.child_by_field_name("name")
if name_node:
name = code[name_node.start_byte:name_node.end_byte]
start_line = node.start_point.row + 1
end_line = node.end_point.row + 1
prefix = "[Async Func]" if node_type == "async_function_definition" else "[Func]"
# Check if it's a method (parent is a class body)
parent = node.parent
while parent and parent.type != "class_definition":
if parent.type == "module":
break
parent = parent.parent
if parent and parent.type == "class_definition":
prefix = "[Method]"
output.append(f"{' ' * indent}{prefix} {name} (Lines {start_line}-{end_line})")
doc = get_docstring(node)
if doc:
output.append(f"{' ' * (indent + 1)}\"\"\"{doc}\"\"\"")
for child in node.children:
# Don't recurse into function bodies for outlining functions,
# but we DO want to recurse into classes to find methods.
if node_type == "class_definition":
if child.type == "block":
walk(child, indent + 1)
elif node_type == "module":
walk(child, indent)
elif node_type == "block":
walk(child, indent)
walk(tree.root_node)
return "\n".join(output)
def get_outline(path: Path, code: str) -> str:
suffix = path.suffix.lower()
if suffix == ".py":
outliner = CodeOutliner()
return outliner.outline(code)
else:
return f"Outlining not supported for {suffix} files yet."

View File

@@ -177,14 +177,14 @@ def execute_agent(role: str, prompt: str, docs: list[str]) -> str:
system_directive = "STRICT SYSTEM DIRECTIVE: You are a stateless Tier 3 Worker (Contributor). " \ system_directive = "STRICT SYSTEM DIRECTIVE: You are a stateless Tier 3 Worker (Contributor). " \
"Your goal is to implement specific code changes or tests based on the provided task. " \ "Your goal is to implement specific code changes or tests based on the provided task. " \
"You have access to tools for reading and writing files (e.g., read_file, write_file, replace), " \ "You have access to tools for reading and writing files (e.g., read_file, write_file, replace), " \
"codebase investigation (codebase_investigator), and web tools (google_web_search, web_fetch). " \ "codebase investigation (codebase_investigator), and web tools (discovered_tool_web_search, discovered_tool_fetch_url). " \
"You CAN execute PowerShell scripts via discovered_tool_run_powershell for verification and testing. " \ "You CAN execute PowerShell scripts via discovered_tool_run_powershell for verification and testing. " \
"Follow TDD and return success status or code changes. No pleasantries, no conversational filler." "Follow TDD and return success status or code changes. No pleasantries, no conversational filler."
elif role in ['tier4', 'tier4-qa']: elif role in ['tier4', 'tier4-qa']:
system_directive = "STRICT SYSTEM DIRECTIVE: You are a stateless Tier 4 QA Agent. " \ system_directive = "STRICT SYSTEM DIRECTIVE: You are a stateless Tier 4 QA Agent. " \
"Your goal is to analyze errors, summarize logs, or verify tests. " \ "Your goal is to analyze errors, summarize logs, or verify tests. " \
"You have access to tools for reading files, exploring the codebase (codebase_investigator), " \ "You have access to tools for reading files, exploring the codebase (codebase_investigator), " \
"and web tools (google_web_search, web_fetch). " \ "and web tools (discovered_tool_web_search, discovered_tool_fetch_url). " \
"You CAN execute PowerShell scripts via discovered_tool_run_powershell for diagnostics. " \ "You CAN execute PowerShell scripts via discovered_tool_run_powershell for diagnostics. " \
"ONLY output the requested analysis. No pleasantries." "ONLY output the requested analysis. No pleasantries."
else: else:
@@ -209,7 +209,7 @@ def execute_agent(role: str, prompt: str, docs: list[str]) -> str:
# We use -p 'mma_task' to ensure non-interactive (headless) mode and valid parsing. # We use -p 'mma_task' to ensure non-interactive (headless) mode and valid parsing.
# Whitelist tools to ensure they are available to the model in headless mode. # Whitelist tools to ensure they are available to the model in headless mode.
# Using 'discovered_tool_run_powershell' as it's the confirmed name for shell access. # Using 'discovered_tool_run_powershell' as it's the confirmed name for shell access.
allowed_tools = "read_file,write_file,replace,list_directory,glob,grep_search,search_files,get_file_summary,discovered_tool_run_powershell,activate_skill,codebase_investigator,google_web_search,web_fetch" allowed_tools = "read_file,write_file,replace,list_directory,glob,grep_search,discovered_tool_search_files,discovered_tool_get_file_summary,discovered_tool_run_powershell,activate_skill,codebase_investigator,discovered_tool_web_search,discovered_tool_fetch_url"
ps_command = ( ps_command = (
f"if (Test-Path 'C:\\projects\\misc\\setup_gemini.ps1') {{ . 'C:\\projects\\misc\\setup_gemini.ps1' }}; " f"if (Test-Path 'C:\\projects\\misc\\setup_gemini.ps1') {{ . 'C:\\projects\\misc\\setup_gemini.ps1' }}; "
f"gemini -p 'mma_task' --allowed-tools {allowed_tools} --output-format json --model {model}" f"gemini -p 'mma_task' --allowed-tools {allowed_tools} --output-format json --model {model}"

47
scripts/tool_call.py Normal file
View File

@@ -0,0 +1,47 @@
import sys
import json
import os
# Add project root to sys.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
try:
import mcp_client
except ImportError:
print(json.dumps({"error": "Failed to import mcp_client"}))
sys.exit(1)
def main():
if len(sys.argv) < 2:
print(json.dumps({"error": "No tool name provided"}))
sys.exit(1)
tool_name = sys.argv[1]
# Read arguments from stdin
try:
input_data = sys.stdin.read()
if input_data:
tool_input = json.loads(input_data)
else:
tool_input = {}
except json.JSONDecodeError:
print(json.dumps({"error": "Invalid JSON input"}))
sys.exit(1)
try:
# Note: mcp_client.configure() is usually called by the GUI before each session,
# but for direct CLI calls, we might need a basic configuration.
# However, mcp_client tools generally resolve paths relative to CWD if not configured.
result = mcp_client.dispatch(tool_name, tool_input)
# We wrap the result in a JSON object for consistency if needed,
# but the CLI often expects just the string result from stdout.
# Actually, let's just print the raw result string as that's what mcp_client returns.
print(result)
except Exception as e:
print(f"ERROR executing tool {tool_name}: {e}")
sys.exit(1)
if __name__ == "__main__":
main()