checkpoint
This commit is contained in:
@@ -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
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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": [
|
||||||
"*"
|
"*"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
94
outline_tool.py
Normal 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."
|
||||||
@@ -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
47
scripts/tool_call.py
Normal 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()
|
||||||
Reference in New Issue
Block a user