Private
Public Access
docs: scrub gemini_cli references from 12 docs (provider count 8->7)
Cleaned: docs/guide_ai_client.md, docs/guide_architecture.md, docs/guide_models.md, docs/guide_simulations.md, docs/guide_context_aggregation.md, docs/guide_tools.md, docs/Readme.md, conductor/tech-stack.md, conductor/product.md, conductor/product-guidelines.md, conductor/workflow.md, conductor/code_styleguides/error_handling.md. Provider list citations updated to 7 (gemini, anthropic, deepseek, minimax, qwen, grok, llama). guide_meta_boundary.md intentionally retained (its gemini_cli references are the meta-tooling GEMINI_CLI_HOOK_CONTEXT env var, NOT the provider; per spec GAP-A12).
This commit is contained in:
+66
-66
@@ -11,9 +11,9 @@ The AI's ability to interact with the filesystem is mediated by a three-layer se
|
||||
### Global State
|
||||
|
||||
```python
|
||||
_allowed_paths: set[Path] = set() # Explicit file allowlist (resolved absolutes)
|
||||
_base_dirs: set[Path] = set() # Directory roots for containment checks
|
||||
_primary_base_dir: Path | None = None # Used for resolving relative paths
|
||||
_allowed_paths: set[Path] = set() # Explicit file allowlist (resolved absolutes)
|
||||
_base_dirs: set[Path] = set() # Directory roots for containment checks
|
||||
_primary_base_dir: Path | None = None # Used for resolving relative paths
|
||||
perf_monitor_callback: Optional[Callable[[], dict[str, Any]]] = None
|
||||
```
|
||||
|
||||
@@ -61,7 +61,7 @@ The `dispatch` function (`mcp_client.py:1322`) is a flat if/elif chain mapping 4
|
||||
| Tool | Parameters | Description |
|
||||
|---|---|---|
|
||||
| `read_file` | `path` | UTF-8 file content extraction |
|
||||
| `list_directory` | `path` | Compact table: `[file/dir] name size`. Applies blacklist filter to entries. |
|
||||
| `list_directory` | `path` | Compact table: `[file/dir] name size`. Applies blacklist filter to entries. |
|
||||
| `search_files` | `path`, `pattern` | Glob pattern matching within an allowed directory. Applies blacklist filter. |
|
||||
| `get_file_slice` | `path`, `start_line`, `end_line` | Returns specific line range (1-based, inclusive) |
|
||||
| `set_file_slice` | `path`, `start_line`, `end_line`, `new_content` | Replaces a line range with new content (surgical edit) |
|
||||
@@ -166,28 +166,28 @@ See [guide_beads.md](guide_beads.md) (placeholder; written in Task 10) for the f
|
||||
**AST-based read tools** follow this pattern:
|
||||
```python
|
||||
def py_get_skeleton(path: str) -> str:
|
||||
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() or p.suffix != ".py": return f"ERROR: not a python file: {path}"
|
||||
from file_cache import ASTParser
|
||||
code = p.read_text(encoding="utf-8")
|
||||
parser = ASTParser("python")
|
||||
return parser.get_skeleton(code)
|
||||
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() or p.suffix != ".py": return f"ERROR: not a python file: {path}"
|
||||
from file_cache import ASTParser
|
||||
code = p.read_text(encoding="utf-8")
|
||||
parser = ASTParser("python")
|
||||
return parser.get_skeleton(code)
|
||||
```
|
||||
|
||||
**AST-based write tools** use stdlib `ast` (not tree-sitter) to locate symbols, then delegate to `set_file_slice`:
|
||||
```python
|
||||
def py_update_definition(path: str, name: str, new_content: str) -> str:
|
||||
p, err = _resolve_and_check(path)
|
||||
if err: return err
|
||||
code = p.read_text(encoding="utf-8").lstrip(chr(0xFEFF)) # Strip BOM
|
||||
tree = ast.parse(code)
|
||||
node = _get_symbol_node(tree, name) # Walks AST for matching node
|
||||
if not node: return f"ERROR: could not find definition '{name}'"
|
||||
start = getattr(node, "lineno")
|
||||
end = getattr(node, "end_lineno")
|
||||
return set_file_slice(path, start, end, new_content)
|
||||
p, err = _resolve_and_check(path)
|
||||
if err: return err
|
||||
code = p.read_text(encoding="utf-8").lstrip(chr(0xFEFF)) # Strip BOM
|
||||
tree = ast.parse(code)
|
||||
node = _get_symbol_node(tree, name) # Walks AST for matching node
|
||||
if not node: return f"ERROR: could not find definition '{name}'"
|
||||
start = getattr(node, "lineno")
|
||||
end = getattr(node, "end_lineno")
|
||||
return set_file_slice(path, start, end, new_content)
|
||||
```
|
||||
|
||||
The `_get_symbol_node` helper supports dot notation (`ClassName.method_name`) by first finding the class, then searching its body for the method.
|
||||
@@ -200,19 +200,19 @@ Tools can be executed concurrently via `async_dispatch`:
|
||||
|
||||
```python
|
||||
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||
"""Dispatch an MCP tool call asynchronously."""
|
||||
return await asyncio.to_thread(dispatch, tool_name, tool_input)
|
||||
"""Dispatch an MCP tool call asynchronously."""
|
||||
return await asyncio.to_thread(dispatch, tool_name, tool_input)
|
||||
```
|
||||
|
||||
In `ai_client.py`, multiple tool calls within a single AI turn are executed in parallel:
|
||||
|
||||
```python
|
||||
async def _execute_tool_calls_concurrently(calls, base_dir, ...):
|
||||
tasks = []
|
||||
for fc in calls:
|
||||
tasks.append(_execute_single_tool_call_async(name, args, ...))
|
||||
results = await asyncio.gather(*tasks)
|
||||
return results
|
||||
tasks = []
|
||||
for fc in calls:
|
||||
tasks.append(_execute_single_tool_call_async(name, args, ...))
|
||||
results = await asyncio.gather(*tasks)
|
||||
return results
|
||||
```
|
||||
|
||||
This significantly reduces latency when the AI makes multiple independent file reads in a single turn.
|
||||
@@ -229,16 +229,16 @@ Manual Slop exposes a REST-based IPC interface on `127.0.0.1:8999` using Python'
|
||||
|
||||
```python
|
||||
class HookServerInstance(ThreadingHTTPServer):
|
||||
app: Any # Reference to main App instance
|
||||
app: Any # Reference to main App instance
|
||||
|
||||
class HookHandler(BaseHTTPRequestHandler):
|
||||
# Accesses self.server.app for all state
|
||||
# Accesses self.server.app for all state
|
||||
|
||||
class HookServer:
|
||||
app: Any
|
||||
port: int = 8999
|
||||
server: HookServerInstance | None
|
||||
thread: threading.Thread | None
|
||||
app: Any
|
||||
port: int = 8999
|
||||
server: HookServerInstance | None
|
||||
thread: threading.Thread | None
|
||||
```
|
||||
|
||||
**Start conditions**: Only starts if `app.test_hooks_enabled == True` OR current provider is `'gemini_cli'`. Otherwise `start()` silently returns.
|
||||
@@ -274,20 +274,20 @@ This ensures all state reads happen on the GUI main thread during `_process_pend
|
||||
|
||||
```python
|
||||
{
|
||||
"mma_status": str, # "idle" | "planning" | "executing" | "done"
|
||||
"ai_status": str, # "idle" | "sending..." | etc.
|
||||
"active_tier": str | None,
|
||||
"active_track": str, # Track ID or raw value
|
||||
"active_tickets": list, # Serialized ticket dicts
|
||||
"mma_step_mode": bool,
|
||||
"pending_tool_approval": bool, # _pending_ask_dialog
|
||||
"pending_mma_step_approval": bool, # _pending_mma_approval is not None
|
||||
"pending_mma_spawn_approval": bool, # _pending_mma_spawn is not None
|
||||
"pending_approval": bool, # Backward compat: step OR tool
|
||||
"pending_spawn": bool, # Alias for spawn approval
|
||||
"tracks": list,
|
||||
"proposed_tracks": list,
|
||||
"mma_streams": dict, # {stream_id: output_text}
|
||||
"mma_status": str, # "idle" | "planning" | "executing" | "done"
|
||||
"ai_status": str, # "idle" | "sending..." | etc.
|
||||
"active_tier": str | None,
|
||||
"active_track": str, # Track ID or raw value
|
||||
"active_tickets": list, # Serialized ticket dicts
|
||||
"mma_step_mode": bool,
|
||||
"pending_tool_approval": bool, # _pending_ask_dialog
|
||||
"pending_mma_step_approval": bool, # _pending_mma_approval is not None
|
||||
"pending_mma_spawn_approval": bool, # _pending_mma_spawn is not None
|
||||
"pending_approval": bool, # Backward compat: step OR tool
|
||||
"pending_spawn": bool, # Alias for spawn approval
|
||||
"tracks": list,
|
||||
"proposed_tracks": list,
|
||||
"mma_streams": dict, # {stream_id: output_text}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -295,9 +295,9 @@ This ensures all state reads happen on the GUI main thread during `_process_pend
|
||||
|
||||
```python
|
||||
{
|
||||
"thinking": bool, # ai_status in ["sending...", "running powershell..."]
|
||||
"live": bool, # ai_status in ["running powershell...", "fetching url...", ...]
|
||||
"prior": bool, # app.is_viewing_prior_session
|
||||
"thinking": bool, # ai_status in ["sending...", "running powershell..."]
|
||||
"live": bool, # ai_status in ["running powershell...", "fetching url...", ...]
|
||||
"prior": bool, # app.is_viewing_prior_session
|
||||
}
|
||||
```
|
||||
|
||||
@@ -340,7 +340,7 @@ The counterpart `/api/ask/respond`:
|
||||
|
||||
```python
|
||||
class ApiHookClient:
|
||||
def __init__(self, base_url="http://127.0.0.1:8999", max_retries=5, retry_delay=0.2)
|
||||
def __init__(self, base_url="http://127.0.0.1:8999", max_retries=5, retry_delay=0.2)
|
||||
```
|
||||
|
||||
### Connection Methods
|
||||
@@ -400,21 +400,21 @@ Tool calls are executed concurrently within a single AI turn using `asyncio.gath
|
||||
|
||||
```python
|
||||
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||
"""
|
||||
Dispatch an MCP tool call by name asynchronously.
|
||||
Returns the result as a string.
|
||||
"""
|
||||
# Run blocking I/O bound tools in a thread to allow parallel execution
|
||||
return await asyncio.to_thread(dispatch, tool_name, tool_input)
|
||||
"""
|
||||
Dispatch an MCP tool call by name asynchronously.
|
||||
Returns the result as a string.
|
||||
"""
|
||||
# Run blocking I/O bound tools in a thread to allow parallel execution
|
||||
return await asyncio.to_thread(dispatch, tool_name, tool_input)
|
||||
```
|
||||
|
||||
All tools are wrapped in `asyncio.to_thread()` to prevent blocking the event loop. This enables `ai_client.py` to execute multiple tools via `asyncio.gather()`:
|
||||
|
||||
```python
|
||||
results = await asyncio.gather(
|
||||
async_dispatch("read_file", {"path": "src/module_a.py"}),
|
||||
async_dispatch("read_file", {"path": "src/module_b.py"}),
|
||||
async_dispatch("get_file_summary", {"path": "src/module_c.py"}),
|
||||
async_dispatch("read_file", {"path": "src/module_a.py"}),
|
||||
async_dispatch("read_file", {"path": "src/module_b.py"}),
|
||||
async_dispatch("get_file_summary", {"path": "src/module_c.py"}),
|
||||
)
|
||||
```
|
||||
|
||||
@@ -453,13 +453,13 @@ Summary:
|
||||
|
||||
```
|
||||
logs/sessions/<session_id>/
|
||||
comms.log # JSON-L: every API interaction (direction, kind, payload)
|
||||
toolcalls.log # Markdown: sequential tool invocation records
|
||||
apihooks.log # API hook invocations
|
||||
clicalls.log # JSON-L: CLI subprocess details (command, stdin, stdout, stderr, latency)
|
||||
comms.log # JSON-L: every API interaction (direction, kind, payload)
|
||||
toolcalls.log # Markdown: sequential tool invocation records
|
||||
apihooks.log # API hook invocations
|
||||
clicalls.log # JSON-L: CLI subprocess details (command, stdin, stdout, stderr, latency)
|
||||
|
||||
scripts/generated/
|
||||
<ts>_<seq:04d>.ps1 # Each AI-generated PowerShell script, preserved in order
|
||||
<ts>_<seq:04d>.ps1 # Each AI-generated PowerShell script, preserved in order
|
||||
```
|
||||
|
||||
### Logging Functions
|
||||
|
||||
Reference in New Issue
Block a user