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:
@@ -167,7 +167,7 @@ Need to represent "missing or failed"?
|
|||||||
| +-- Use raise (only for programmer errors like KeyError on a known dict)
|
| +-- Use raise (only for programmer errors like KeyError on a known dict)
|
||||||
|
|
|
|
||||||
+-- Does the SDK raise an exception you can't avoid?
|
+-- Does the SDK raise an exception you can't avoid?
|
||||||
+-- Catch at the boundary; convert to ErrorInfo inside a Result
|
+-- Catch at the boundary; convert to ErrorInfo inside a Result
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -177,17 +177,17 @@ Need to represent "missing or failed"?
|
|||||||
**DON'T do these things:**
|
**DON'T do these things:**
|
||||||
|
|
||||||
1. **DON'T** use `Optional[X]` for "this might fail at runtime". Use
|
1. **DON'T** use `Optional[X]` for "this might fail at runtime". Use
|
||||||
`Result[X]` instead.
|
`Result[X]` instead.
|
||||||
2. **DON'T** use `None` as a sentinel for "no result". Use a nil-sentinel
|
2. **DON'T** use `None` as a sentinel for "no result". Use a nil-sentinel
|
||||||
dataclass.
|
dataclass.
|
||||||
3. **DON'T** raise a custom exception class for runtime failures. Catch SDK
|
3. **DON'T** raise a custom exception class for runtime failures. Catch SDK
|
||||||
exceptions and return `ErrorInfo`.
|
exceptions and return `ErrorInfo`.
|
||||||
4. **DON'T** use `Union[T, E]` (sum type). Use a struct with parallel fields
|
4. **DON'T** use `Union[T, E]` (sum type). Use a struct with parallel fields
|
||||||
(AND over OR).
|
(AND over OR).
|
||||||
5. **DON'T** have `if x is None: handle; else: use_x` patterns in production
|
5. **DON'T** have `if x is None: handle; else: use_x` patterns in production
|
||||||
code. The nil-sentinel makes them unnecessary.
|
code. The nil-sentinel makes them unnecessary.
|
||||||
6. **DON'T** catch `except Exception` and silently swallow. Convert to
|
6. **DON'T** catch `except Exception` and silently swallow. Convert to
|
||||||
`ErrorInfo` and return in the `Result`.
|
`ErrorInfo` and return in the `Result`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -196,16 +196,16 @@ Need to represent "missing or failed"?
|
|||||||
The 3 refactored subsystems demonstrate each pattern in context:
|
The 3 refactored subsystems demonstrate each pattern in context:
|
||||||
|
|
||||||
- **`src/mcp_client.py:205-294`** — `read_file`, `list_directory`,
|
- **`src/mcp_client.py:205-294`** — `read_file`, `list_directory`,
|
||||||
`search_files` return `Result[str]`; `(p, err)` tuples become
|
`search_files` return `Result[str]`; `(p, err)` tuples become
|
||||||
`Result[Path]`; the 30+ `assert p is not None` chain (lines 304-794) is
|
`Result[Path]`; the 30+ `assert p is not None` chain (lines 304-794) is
|
||||||
removed.
|
removed.
|
||||||
- **`src/ai_client.py`** — `_send_<vendor>_result()` returns `Result[str]`
|
- **`src/ai_client.py`** — `_send_<vendor>_result()` returns `Result[str]`
|
||||||
(8 vendors: gemini, anthropic, deepseek, minimax, gemini_cli, qwen, llama,
|
(8 vendors: gemini, anthropic, deepseek, minimax, qwen, llama,
|
||||||
grok); `send(...) -> Result[str, ErrorInfo]` is the public API.
|
grok); `send(...) -> Result[str, ErrorInfo]` is the public API.
|
||||||
- **`src/rag_engine.py:100-180`** — `_init_vector_store_result`,
|
- **`src/rag_engine.py:100-180`** — `_init_vector_store_result`,
|
||||||
`_validate_collection_dim_result`, `is_empty_result`, `add_documents_result`
|
`_validate_collection_dim_result`, `is_empty_result`, `add_documents_result`
|
||||||
return `Result[None]` or `Result[T]`; broad `except Exception` blocks
|
return `Result[None]` or `Result[T]`; broad `except Exception` blocks
|
||||||
become `ErrorInfo` entries.
|
become `ErrorInfo` entries.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -219,23 +219,23 @@ from `_in_3_files.py` per the contradictions report) covers all
|
|||||||
remaining work to bring the 14 migration-target files into compliance.
|
remaining work to bring the 14 migration-target files into compliance.
|
||||||
|
|
||||||
- **`Optional[T]` return types are FORBIDDEN** in all `src/*.py`. Use
|
- **`Optional[T]` return types are FORBIDDEN** in all `src/*.py`. Use
|
||||||
`Result[T]` (with `NIL_T` singleton if needed) instead. Rationale:
|
`Result[T]` (with `NIL_T` singleton if needed) instead. Rationale:
|
||||||
`Optional[T]` is the sum type `Union[T, None]` that Fleury's framework
|
`Optional[T]` is the sum type `Union[T, None]` that Fleury's framework
|
||||||
replaces. Mixing the two patterns reintroduces the bifurcation the
|
replaces. Mixing the two patterns reintroduces the bifurcation the
|
||||||
convention is designed to remove.
|
convention is designed to remove.
|
||||||
- Argument types that may be `None` (e.g., `rag_engine: Optional[Any] = None`)
|
- Argument types that may be `None` (e.g., `rag_engine: Optional[Any] = None`)
|
||||||
remain allowed; they describe a caller choice, not a runtime failure
|
remain allowed; they describe a caller choice, not a runtime failure
|
||||||
of this function. Only `Optional[T]` *return* types are banned.
|
of this function. Only `Optional[T]` *return* types are banned.
|
||||||
- **Function return types must be `Result[T]` for any function that can fail
|
- **Function return types must be `Result[T]` for any function that can fail
|
||||||
at runtime.** A function that can't fail (e.g., `get_name() -> str`)
|
at runtime.** A function that can't fail (e.g., `get_name() -> str`)
|
||||||
doesn't need a `Result`. The classification is "can this return a different
|
doesn't need a `Result`. The classification is "can this return a different
|
||||||
value under different runtime conditions?" If yes, `Result`. If no, plain
|
value under different runtime conditions?" If yes, `Result`. If no, plain
|
||||||
return type.
|
return type.
|
||||||
- **Catch SDK exceptions at the boundary only.** Inside the 3 refactored
|
- **Catch SDK exceptions at the boundary only.** Inside the 3 refactored
|
||||||
files, the only place an exception is caught is at the SDK call site
|
files, the only place an exception is caught is at the SDK call site
|
||||||
(e.g., `_send_<vendor>_result()` wrapping the SDK call). Internal
|
(e.g., `_send_<vendor>_result()` wrapping the SDK call). Internal
|
||||||
`try/except` is reserved for converting `OSError`, `PermissionError`, and
|
`try/except` is reserved for converting `OSError`, `PermissionError`, and
|
||||||
similar I/O exceptions to `ErrorInfo` at the mcp_client tool boundary.
|
similar I/O exceptions to `ErrorInfo` at the mcp_client tool boundary.
|
||||||
|
|
||||||
The verification script `scripts/audit_optional_returns.py` enforces the
|
The verification script `scripts/audit_optional_returns.py` enforces the
|
||||||
`Optional[X]` rule by failing CI if any new `Optional[X]` return type
|
`Optional[X]` rule by failing CI if any new `Optional[X]` return type
|
||||||
@@ -266,18 +266,18 @@ warnings use `warnings.warn(..., stacklevel=2)` which is thread-safe.
|
|||||||
**Use it for:**
|
**Use it for:**
|
||||||
|
|
||||||
- New public APIs (any function that can fail at runtime and the caller
|
- New public APIs (any function that can fail at runtime and the caller
|
||||||
might care).
|
might care).
|
||||||
- New internal functions where the caller benefits from knowing the failure
|
- New internal functions where the caller benefits from knowing the failure
|
||||||
(vs. just propagating `None`).
|
(vs. just propagating `None`).
|
||||||
|
|
||||||
**Don't use it for:**
|
**Don't use it for:**
|
||||||
|
|
||||||
- Constructors (`__init__`) that fail with programmer errors (use `assert` or
|
- Constructors (`__init__`) that fail with programmer errors (use `assert` or
|
||||||
`raise` for these). See "Constructors Can Raise" below for the full rule.
|
`raise` for these). See "Constructors Can Raise" below for the full rule.
|
||||||
- Trivial getters that can't fail (`get_name() -> str` doesn't need a
|
- Trivial getters that can't fail (`get_name() -> str` doesn't need a
|
||||||
`Result`).
|
`Result`).
|
||||||
- Performance-critical hot paths where the overhead of the dataclass
|
- Performance-critical hot paths where the overhead of the dataclass
|
||||||
allocation is measurable (rare; benchmark first).
|
allocation is measurable (rare; benchmark first).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -333,16 +333,16 @@ into internal code; it's the framework contract.
|
|||||||
# Compliant: FastAPI boundary in _api_* handler
|
# Compliant: FastAPI boundary in _api_* handler
|
||||||
async def _api_get_key(controller, header_key: str) -> str:
|
async def _api_get_key(controller, header_key: str) -> str:
|
||||||
if not _is_valid_key(header_key):
|
if not _is_valid_key(header_key):
|
||||||
raise HTTPException(status_code=403, detail="Could not validate API Key")
|
raise HTTPException(status_code=403, detail="Could not validate API Key")
|
||||||
return header_key
|
return header_key
|
||||||
|
|
||||||
# Compliant: broad catch + HTTPException at the FastAPI boundary
|
# Compliant: broad catch + HTTPException at the FastAPI boundary
|
||||||
async def _api_generate(controller, payload):
|
async def _api_generate(controller, payload):
|
||||||
try:
|
try:
|
||||||
result = ai_client.send(...)
|
result = ai_client.send(...)
|
||||||
return result.data
|
return result.data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"AI call failed: {e}")
|
raise HTTPException(status_code=500, detail=f"AI call failed: {e}")
|
||||||
```
|
```
|
||||||
|
|
||||||
The catch-all `except Exception` is acceptable here **because the
|
The catch-all `except Exception` is acceptable here **because the
|
||||||
@@ -353,13 +353,13 @@ HTTP status code is the framework contract.
|
|||||||
### What is NOT a boundary
|
### What is NOT a boundary
|
||||||
|
|
||||||
- Internal business logic: `try/except` around a `for` loop in a
|
- Internal business logic: `try/except` around a `for` loop in a
|
||||||
controller method is internal, not boundary.
|
controller method is internal, not boundary.
|
||||||
- Cross-method calls within `src/`: calling a method in
|
- Cross-method calls within `src/`: calling a method in
|
||||||
`app_controller.py` from a method in `app_controller.py` is internal,
|
`app_controller.py` from a method in `app_controller.py` is internal,
|
||||||
not boundary.
|
not boundary.
|
||||||
- stdlib I/O that the user controls directly: opening a file the user
|
- stdlib I/O that the user controls directly: opening a file the user
|
||||||
passed via `--config` is internal; converting the failure should be
|
passed via `--config` is internal; converting the failure should be
|
||||||
Result-based, not exception-based.
|
Result-based, not exception-based.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -391,10 +391,10 @@ where the caller of the drain point does NOT need to receive a
|
|||||||
```python
|
```python
|
||||||
# COMPLIANT: drain point. The HTTP status code IS the error response.
|
# COMPLIANT: drain point. The HTTP status code IS the error response.
|
||||||
async def _api_get_track(controller, track_id: str) -> dict:
|
async def _api_get_track(controller, track_id: str) -> dict:
|
||||||
result = controller.get_track_result(track_id)
|
result = controller.get_track_result(track_id)
|
||||||
if not result.ok:
|
if not result.ok:
|
||||||
raise HTTPException(status_code=404, detail=result.errors[0].ui_message())
|
raise HTTPException(status_code=404, detail=result.errors[0].ui_message())
|
||||||
return {"track": result.data}
|
return {"track": result.data}
|
||||||
```
|
```
|
||||||
|
|
||||||
The caller (the HTTP client) receives an HTTP 4xx/5xx response. The
|
The caller (the HTTP client) receives an HTTP 4xx/5xx response. The
|
||||||
@@ -407,10 +407,10 @@ the error.
|
|||||||
```python
|
```python
|
||||||
# COMPLIANT: drain point. The user sees the error in the modal.
|
# COMPLIANT: drain point. The user sees the error in the modal.
|
||||||
def _show_track_load_failure(controller, track_id: str) -> None:
|
def _show_track_load_failure(controller, track_id: str) -> None:
|
||||||
result = controller.get_track_result(track_id)
|
result = controller.get_track_result(track_id)
|
||||||
if not result.ok:
|
if not result.ok:
|
||||||
imgui.open_popup("Track Load Error")
|
imgui.open_popup("Track Load Error")
|
||||||
# popup body reads result.errors[0].ui_message() and displays it
|
# popup body reads result.errors[0].ui_message() and displays it
|
||||||
```
|
```
|
||||||
|
|
||||||
The user sees the error. The caller (`_show_track_load_failure`)
|
The user sees the error. The caller (`_show_track_load_failure`)
|
||||||
@@ -421,10 +421,10 @@ returns `None` — it is the end of the propagation chain.
|
|||||||
```python
|
```python
|
||||||
# COMPLIANT: drain point. The app shuts down intentionally.
|
# COMPLIANT: drain point. The app shuts down intentionally.
|
||||||
def _shutdown_on_critical_failure(controller) -> None:
|
def _shutdown_on_critical_failure(controller) -> None:
|
||||||
result = controller._init_session_db_result()
|
result = controller._init_session_db_result()
|
||||||
if not result.ok:
|
if not result.ok:
|
||||||
sys.stderr.write(f"FATAL: {result.errors[0].ui_message()}\n")
|
sys.stderr.write(f"FATAL: {result.errors[0].ui_message()}\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
```
|
```
|
||||||
|
|
||||||
The error is propagated to the OS via `sys.exit(1)`. The drain point
|
The error is propagated to the OS via `sys.exit(1)`. The drain point
|
||||||
@@ -435,12 +435,12 @@ is the process termination itself.
|
|||||||
```python
|
```python
|
||||||
# COMPLIANT: drain point. The error is sent to monitoring.
|
# COMPLIANT: drain point. The error is sent to monitoring.
|
||||||
def _report_failure_to_telemetry(controller, op_name: str, result: Result[T]) -> None:
|
def _report_failure_to_telemetry(controller, op_name: str, result: Result[T]) -> None:
|
||||||
if not result.ok:
|
if not result.ok:
|
||||||
telemetry.emit_error(
|
telemetry.emit_error(
|
||||||
operation=op_name,
|
operation=op_name,
|
||||||
kind=result.errors[0].kind.value,
|
kind=result.errors[0].kind.value,
|
||||||
message=result.errors[0].message,
|
message=result.errors[0].message,
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
The error reaches the telemetry system. The caller of the drain point
|
The error reaches the telemetry system. The caller of the drain point
|
||||||
@@ -452,12 +452,12 @@ receives `None`.
|
|||||||
# COMPLIANT: drain point. The retry is bounded and the final failure
|
# COMPLIANT: drain point. The retry is bounded and the final failure
|
||||||
# is reported back to the user (which is itself a drain point).
|
# is reported back to the user (which is itself a drain point).
|
||||||
def _load_track_with_retry(controller, track_id: str) -> Track | None:
|
def _load_track_with_retry(controller, track_id: str) -> Track | None:
|
||||||
for attempt in range(MAX_RETRIES):
|
for attempt in range(MAX_RETRIES):
|
||||||
result = controller.get_track_result(track_id)
|
result = controller.get_track_result(track_id)
|
||||||
if result.ok:
|
if result.ok:
|
||||||
return result.data
|
return result.data
|
||||||
time.sleep(BACKOFF_SECONDS * (attempt + 1))
|
time.sleep(BACKOFF_SECONDS * (attempt + 1))
|
||||||
return None # Caller will display "failed after N attempts"
|
return None # Caller will display "failed after N attempts"
|
||||||
```
|
```
|
||||||
|
|
||||||
The retry loop is a drain point: the function returns `Track | None`
|
The retry loop is a drain point: the function returns `Track | None`
|
||||||
@@ -471,15 +471,15 @@ The following are **NOT** drain points. They are silent-fallback
|
|||||||
violations that lose data:
|
violations that lose data:
|
||||||
|
|
||||||
- **`sys.stderr.write(...)` alone** (without visible user feedback or
|
- **`sys.stderr.write(...)` alone** (without visible user feedback or
|
||||||
app-level decision): the data is lost; the user sees nothing.
|
app-level decision): the data is lost; the user sees nothing.
|
||||||
Logging is NOT a drain.
|
Logging is NOT a drain.
|
||||||
- **`logging.error(...)` / `logger.exception(...)` alone**: same as
|
- **`logging.error(...)` / `logger.exception(...)` alone**: same as
|
||||||
above. The log is recorded, but the error is invisible to the user.
|
above. The log is recorded, but the error is invisible to the user.
|
||||||
- **`return default_value`** after a `try/except`: the original error
|
- **`return default_value`** after a `try/except`: the original error
|
||||||
context is lost; the caller cannot distinguish success from failure.
|
context is lost; the caller cannot distinguish success from failure.
|
||||||
- **`pass`**: silent. The data is lost.
|
- **`pass`**: silent. The data is lost.
|
||||||
- **`traceback.print_exc(...)` alone**: similar to logging — visible in
|
- **`traceback.print_exc(...)` alone**: similar to logging — visible in
|
||||||
the console but invisible to the user.
|
the console but invisible to the user.
|
||||||
|
|
||||||
**The key distinction:** a drain point **terminates the propagation**
|
**The key distinction:** a drain point **terminates the propagation**
|
||||||
with a visible, intentional action. A log call or silent fallback
|
with a visible, intentional action. A log call or silent fallback
|
||||||
@@ -490,13 +490,13 @@ with a visible, intentional action. A log call or silent fallback
|
|||||||
The two concepts are complementary:
|
The two concepts are complementary:
|
||||||
|
|
||||||
- **Boundary types** (Section: "Boundary Types") describe WHERE
|
- **Boundary types** (Section: "Boundary Types") describe WHERE
|
||||||
exceptions originate or are converted (third-party SDK calls, stdlib
|
exceptions originate or are converted (third-party SDK calls, stdlib
|
||||||
I/O, FastAPI handlers). The catch site at a boundary converts the
|
I/O, FastAPI handlers). The catch site at a boundary converts the
|
||||||
exception to `ErrorInfo` and returns it in `Result`.
|
exception to `ErrorInfo` and returns it in `Result`.
|
||||||
- **Drain points** describe WHERE the `Result[T]` propagation
|
- **Drain points** describe WHERE the `Result[T]` propagation
|
||||||
terminates (HTTP error response, GUI display, app termination,
|
terminates (HTTP error response, GUI display, app termination,
|
||||||
telemetry, bounded retry). The function at a drain point returns
|
telemetry, bounded retry). The function at a drain point returns
|
||||||
`None` or raises into a framework; it does NOT return `Result[T]`.
|
`None` or raises into a framework; it does NOT return `Result[T]`.
|
||||||
|
|
||||||
A function can be BOTH a boundary AND a drain point. The
|
A function can be BOTH a boundary AND a drain point. The
|
||||||
`_api_*` FastAPI handler is a boundary (catches SDK exceptions) and a
|
`_api_*` FastAPI handler is a boundary (catches SDK exceptions) and a
|
||||||
@@ -510,12 +510,12 @@ Heuristic D that recognizes drain-point patterns as `INTERNAL_COMPLIANT`.
|
|||||||
The patterns are:
|
The patterns are:
|
||||||
|
|
||||||
1. `except (SomeError): self.send_response(status); ...` (HTTP
|
1. `except (SomeError): self.send_response(status); ...` (HTTP
|
||||||
response in a `BaseHTTPRequestHandler` subclass)
|
response in a `BaseHTTPRequestHandler` subclass)
|
||||||
2. `except (SomeError): imgui.open_popup(...)` (GUI error display)
|
2. `except (SomeError): imgui.open_popup(...)` (GUI error display)
|
||||||
3. `except (SomeError): sys.exit(...)` (intentional termination)
|
3. `except (SomeError): sys.exit(...)` (intentional termination)
|
||||||
4. `except (SomeError): telemetry.emit_*(...)` (telemetry)
|
4. `except (SomeError): telemetry.emit_*(...)` (telemetry)
|
||||||
5. `except (SomeError): for attempt in range(N): ...; return None`
|
5. `except (SomeError): for attempt in range(N): ...; return None`
|
||||||
(bounded retry; followed by `return None` or similar end-of-propagation)
|
(bounded retry; followed by `return None` or similar end-of-propagation)
|
||||||
|
|
||||||
A site matching any of these is classified `INTERNAL_COMPLIANT`, with a
|
A site matching any of these is classified `INTERNAL_COMPLIANT`, with a
|
||||||
note that the pattern is a drain point.
|
note that the pattern is a drain point.
|
||||||
@@ -554,18 +554,18 @@ calls):
|
|||||||
```python
|
```python
|
||||||
def _validate_collection_dim_result(self) -> Result[None]:
|
def _validate_collection_dim_result(self) -> Result[None]:
|
||||||
if self.collection is None or self.collection == "mock":
|
if self.collection is None or self.collection == "mock":
|
||||||
return Result(data=None)
|
return Result(data=None)
|
||||||
try:
|
try:
|
||||||
res = self.collection.get(limit=1, include=["embeddings"])
|
res = self.collection.get(limit=1, include=["embeddings"])
|
||||||
# ... validation logic ...
|
# ... validation logic ...
|
||||||
return Result(data=None)
|
return Result(data=None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Result(data=None, errors=[
|
return Result(data=None, errors=[
|
||||||
ErrorInfo(kind=ErrorKind.INTERNAL,
|
ErrorInfo(kind=ErrorKind.INTERNAL,
|
||||||
message=f"Failed to validate collection dim: {e}",
|
message=f"Failed to validate collection dim: {e}",
|
||||||
source="rag._validate_collection_dim",
|
source="rag._validate_collection_dim",
|
||||||
original=e)
|
original=e)
|
||||||
])
|
])
|
||||||
```
|
```
|
||||||
|
|
||||||
This `except Exception` is **compliant** because the catch + ErrorInfo
|
This `except Exception` is **compliant** because the catch + ErrorInfo
|
||||||
@@ -602,11 +602,11 @@ elaborates.
|
|||||||
```python
|
```python
|
||||||
class MyClass:
|
class MyClass:
|
||||||
def __init__(self, config: Config):
|
def __init__(self, config: Config):
|
||||||
if config is None:
|
if config is None:
|
||||||
raise ValueError("MyClass requires a non-None Config")
|
raise ValueError("MyClass requires a non-None Config")
|
||||||
if not config.api_key:
|
if not config.api_key:
|
||||||
raise ValueError("MyClass requires a non-empty api_key")
|
raise ValueError("MyClass requires a non-empty api_key")
|
||||||
self._config = config
|
self._config = config
|
||||||
```
|
```
|
||||||
|
|
||||||
**Compliant assert (for impossible states):**
|
**Compliant assert (for impossible states):**
|
||||||
@@ -674,7 +674,7 @@ try:
|
|||||||
do_something(resource)
|
do_something(resource)
|
||||||
finally:
|
finally:
|
||||||
release(resource) # `finally` is cleaner; `except+raise` is for when
|
release(resource) # `finally` is cleaner; `except+raise` is for when
|
||||||
# you also need to log or convert
|
# you also need to log or convert
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `try/finally` for the pure cleanup case (no logging/conversion).
|
Use `try/finally` for the pure cleanup case (no logging/conversion).
|
||||||
@@ -759,15 +759,15 @@ script's output):
|
|||||||
Files scanned: 65
|
Files scanned: 65
|
||||||
Files with findings: 42
|
Files with findings: 42
|
||||||
Total sites: 348
|
Total sites: 348
|
||||||
Compliant sites: 80
|
Compliant sites: 80
|
||||||
Suspicious sites: 25
|
Suspicious sites: 25
|
||||||
Violation sites: 211
|
Violation sites: 211
|
||||||
Unclear (review): 32
|
Unclear (review): 32
|
||||||
|
|
||||||
--- Baseline (refactored files: mcp_client, ai_client, rag_engine) ---
|
--- Baseline (refactored files: mcp_client, ai_client, rag_engine) ---
|
||||||
Sites: 112, violations: 77
|
Sites: 112, violations: 77
|
||||||
--- Migration target (all other src/ files) ---
|
--- Migration target (all other src/ files) ---
|
||||||
Sites: 236, violations: 134
|
Sites: 236, violations: 134
|
||||||
```
|
```
|
||||||
|
|
||||||
The **baseline** is the 3 fully-refactored files (the convention reference).
|
The **baseline** is the 3 fully-refactored files (the convention reference).
|
||||||
@@ -788,15 +788,15 @@ When converting existing code:
|
|||||||
|
|
||||||
1. Identify the `Optional[X]` return type or the `raise` statement.
|
1. Identify the `Optional[X]` return type or the `raise` statement.
|
||||||
2. Define a `Result` dataclass (or use the existing one) with `data: X` and
|
2. Define a `Result` dataclass (or use the existing one) with `data: X` and
|
||||||
`errors: list[ErrorInfo]`.
|
`errors: list[ErrorInfo]`.
|
||||||
3. Replace `None` returns with `Result(data=NIL_X, errors=[...])` or
|
3. Replace `None` returns with `Result(data=NIL_X, errors=[...])` or
|
||||||
`Result(data=zero_value, errors=[...])`.
|
`Result(data=zero_value, errors=[...])`.
|
||||||
4. Replace `raise X` with
|
4. Replace `raise X` with
|
||||||
`return Result(data=zero_value, errors=[ErrorInfo(kind=..., message=...)])`.
|
`return Result(data=zero_value, errors=[ErrorInfo(kind=..., message=...)])`.
|
||||||
5. Update the caller to check `result.errors` instead of `is None` /
|
5. Update the caller to check `result.errors` instead of `is None` /
|
||||||
`try/except`.
|
`try/except`.
|
||||||
6. Add a test that verifies both the success and failure paths return the
|
6. Add a test that verifies both the success and failure paths return the
|
||||||
right `Result`.
|
right `Result`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -816,13 +816,13 @@ When a function is migrated from `Optional[T]` / `raise` to `Result[T]`:
|
|||||||
```python
|
```python
|
||||||
# BEFORE (the legacy):
|
# BEFORE (the legacy):
|
||||||
def do_thing() -> Optional[str]:
|
def do_thing() -> Optional[str]:
|
||||||
result = do_thing_result()
|
result = do_thing_result()
|
||||||
if not result.ok: return None
|
if not result.ok: return None
|
||||||
return result.data
|
return result.data
|
||||||
|
|
||||||
# AFTER (the new):
|
# AFTER (the new):
|
||||||
def do_thing_result() -> Result[str]:
|
def do_thing_result() -> Result[str]:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
The `do_thing` function must be **deleted**, not kept as a wrapper. Keep only one entry point: `do_thing_result()`.
|
The `do_thing` function must be **deleted**, not kept as a wrapper. Keep only one entry point: `do_thing_result()`.
|
||||||
@@ -832,7 +832,7 @@ The `do_thing` function must be **deleted**, not kept as a wrapper. Keep only on
|
|||||||
```python
|
```python
|
||||||
# After OBLITERATE: only do_thing_result exists
|
# After OBLITERATE: only do_thing_result exists
|
||||||
def do_thing_result() -> Result[str]:
|
def do_thing_result() -> Result[str]:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
Callers are rewritten:
|
Callers are rewritten:
|
||||||
@@ -881,18 +881,18 @@ checklist before claiming a task is done.**
|
|||||||
**Before writing or modifying ANY `try/except` code, you MUST:**
|
**Before writing or modifying ANY `try/except` code, you MUST:**
|
||||||
|
|
||||||
1. **READ `conductor/code_styleguides/error_handling.md` end-to-end.**
|
1. **READ `conductor/code_styleguides/error_handling.md` end-to-end.**
|
||||||
The 7 sections are: (1) The 5 Patterns, (2) Decision Tree,
|
The 7 sections are: (1) The 5 Patterns, (2) Decision Tree,
|
||||||
(3) Anti-Patterns, (4) Hard Rules, (5) Boundary Types, (6) The
|
(3) Anti-Patterns, (4) Hard Rules, (5) Boundary Types, (6) The
|
||||||
Broad-Except Distinction, (7) AI Agent Checklist (this section).
|
Broad-Except Distinction, (7) AI Agent Checklist (this section).
|
||||||
|
|
||||||
2. **Acknowledge the read in the commit message.** Format: "TIER-2
|
2. **Acknowledge the read in the commit message.** Format: "TIER-2
|
||||||
READ conductor/code_styleguides/error_handling.md before
|
READ conductor/code_styleguides/error_handling.md before
|
||||||
<phase/task>."
|
<phase/task>."
|
||||||
|
|
||||||
3. **The styleguide is the source of truth.** Your training data is
|
3. **The styleguide is the source of truth.** Your training data is
|
||||||
the OPPOSITE of this convention. Idiomatic Python (`try/except` +
|
the OPPOSITE of this convention. Idiomatic Python (`try/except` +
|
||||||
`Optional[T]` + `raise Exception`) is what the convention is
|
`Optional[T]` + `raise Exception`) is what the convention is
|
||||||
designed to REPLACE.
|
designed to REPLACE.
|
||||||
|
|
||||||
**Why:** the previous round (Phase 10) added 5 LAUNDERING HEURISTICS to
|
**Why:** the previous round (Phase 10) added 5 LAUNDERING HEURISTICS to
|
||||||
the audit script that classified narrowing as compliant, which is the
|
the audit script that classified narrowing as compliant, which is the
|
||||||
@@ -906,75 +906,75 @@ re-introducing laundering heuristics.**
|
|||||||
When writing NEW code, you MUST:
|
When writing NEW code, you MUST:
|
||||||
|
|
||||||
1. **Use `Result[T]` for any function that can fail at runtime.** A
|
1. **Use `Result[T]` for any function that can fail at runtime.** A
|
||||||
function that returns a different value under different runtime
|
function that returns a different value under different runtime
|
||||||
conditions (success vs. failure) returns `Result[T]`, not
|
conditions (success vs. failure) returns `Result[T]`, not
|
||||||
`Optional[T]`, not `T | None`, not a custom exception class. Use the
|
`Optional[T]`, not `T | None`, not a custom exception class. Use the
|
||||||
`Result` dataclass from `src/result_types.py`; populate
|
`Result` dataclass from `src/result_types.py`; populate
|
||||||
`errors: list[ErrorInfo]` on failure.
|
`errors: list[ErrorInfo]` on failure.
|
||||||
|
|
||||||
2. **Catch SDK exceptions at the boundary, convert to `ErrorInfo`.** If
|
2. **Catch SDK exceptions at the boundary, convert to `ErrorInfo`.** If
|
||||||
your code calls `anthropic`, `google.genai`, `openai`, `chromadb`,
|
your code calls `anthropic`, `google.genai`, `openai`, `chromadb`,
|
||||||
`requests`, or any other third-party SDK, the catch site
|
`requests`, or any other third-party SDK, the catch site
|
||||||
converts the exception to `ErrorInfo(kind=..., message=...)` and
|
converts the exception to `ErrorInfo(kind=..., message=...)` and
|
||||||
returns it in `Result.errors`. Do NOT re-raise; do NOT swallow;
|
returns it in `Result.errors`. Do NOT re-raise; do NOT swallow;
|
||||||
do NOT let the exception propagate into internal code.
|
do NOT let the exception propagate into internal code.
|
||||||
|
|
||||||
3. **Use nil-sentinel dataclasses for "no result".** If a function
|
3. **Use nil-sentinel dataclasses for "no result".** If a function
|
||||||
would return `None` in idiomatic Python, return a frozen
|
would return `None` in idiomatic Python, return a frozen
|
||||||
`NilPath` / `NilRAGState` / etc. singleton from
|
`NilPath` / `NilRAGState` / etc. singleton from
|
||||||
`src/result_types.py` instead. Callers don't need `if x is None:`
|
`src/result_types.py` instead. Callers don't need `if x is None:`
|
||||||
checks; they can call `x.read_text` and get `""` on the nil path.
|
checks; they can call `x.read_text` and get `""` on the nil path.
|
||||||
|
|
||||||
4. **Use `try/finally` (no except) for cleanup.** Bare
|
4. **Use `try/finally` (no except) for cleanup.** Bare
|
||||||
`try: ...; finally: cleanup()` is the canonical `goto defer`
|
`try: ...; finally: cleanup()` is the canonical `goto defer`
|
||||||
pattern. Use it for resource cleanup, lock release, file handle
|
pattern. Use it for resource cleanup, lock release, file handle
|
||||||
close. Do NOT use `try/except` + pass for cleanup; the cleanup
|
close. Do NOT use `try/except` + pass for cleanup; the cleanup
|
||||||
should run whether or not an exception occurred.
|
should run whether or not an exception occurred.
|
||||||
|
|
||||||
5. **`raise` is reserved for programmer errors.** `assert` for
|
5. **`raise` is reserved for programmer errors.** `assert` for
|
||||||
"this should never happen" invariants. `raise ValueError`,
|
"this should never happen" invariants. `raise ValueError`,
|
||||||
`raise NotImplementedError`, `raise KeyError` in `__init__` for
|
`raise NotImplementedError`, `raise KeyError` in `__init__` for
|
||||||
"this object needs X." Do NOT use `raise` for runtime failures
|
"this object needs X." Do NOT use `raise` for runtime failures
|
||||||
(the network is down, the file doesn't exist, the API rate-limited);
|
(the network is down, the file doesn't exist, the API rate-limited);
|
||||||
those are `Result` cases.
|
those are `Result` cases.
|
||||||
|
|
||||||
### The 7 MUST-NOT-DO rules
|
### The 7 MUST-NOT-DO rules
|
||||||
|
|
||||||
When writing NEW code, you MUST NOT:
|
When writing NEW code, you MUST NOT:
|
||||||
|
|
||||||
1. **DO NOT use `Optional[T]` as a return type** (in any file in
|
1. **DO NOT use `Optional[T]` as a return type** (in any file in
|
||||||
`src/`). Use `Result[T]` instead. CI fails if you add a new
|
`src/`). Use `Result[T]` instead. CI fails if you add a new
|
||||||
`Optional[T]` return type to any `src/*.py` (enforced by
|
`Optional[T]` return type to any `src/*.py` (enforced by
|
||||||
`scripts/audit_optional_in_baseline_files.py --strict`,
|
`scripts/audit_optional_in_baseline_files.py --strict`,
|
||||||
which scans all `src/*.py` as of 2026-06-27).
|
which scans all `src/*.py` as of 2026-06-27).
|
||||||
|
|
||||||
2. **DO NOT use `Optional[T]` as a return type** (anywhere else in
|
2. **DO NOT use `Optional[T]` as a return type** (anywhere else in
|
||||||
`src/`). The convention is migrating to `Result[T]`; new code
|
`src/`). The convention is migrating to `Result[T]`; new code
|
||||||
should set the pattern, not perpetuate the old one. Argument
|
should set the pattern, not perpetuate the old one. Argument
|
||||||
types that may be `None` (caller choice) are still OK.
|
types that may be `None` (caller choice) are still OK.
|
||||||
|
|
||||||
3. **DO NOT use `None` as a sentinel for "no result".** Use a
|
3. **DO NOT use `None` as a sentinel for "no result".** Use a
|
||||||
nil-sentinel dataclass. The data is zero-initialized; the caller
|
nil-sentinel dataclass. The data is zero-initialized; the caller
|
||||||
doesn't need a None check.
|
doesn't need a None check.
|
||||||
|
|
||||||
4. **DO NOT raise a custom exception class for runtime failures.**
|
4. **DO NOT raise a custom exception class for runtime failures.**
|
||||||
SDK exceptions caught and converted to `ErrorInfo` is the only
|
SDK exceptions caught and converted to `ErrorInfo` is the only
|
||||||
legitimate exception path. Internal code uses `Result`.
|
legitimate exception path. Internal code uses `Result`.
|
||||||
|
|
||||||
5. **DO NOT use `Union[T, E]` (sum type).** Use `Result[T]` with
|
5. **DO NOT use `Union[T, E]` (sum type).** Use `Result[T]` with
|
||||||
side-channel `errors: list[ErrorInfo]`. The result is the data
|
side-channel `errors: list[ErrorInfo]`. The result is the data
|
||||||
AND the errors, not a tagged sum.
|
AND the errors, not a tagged sum.
|
||||||
|
|
||||||
6. **DO NOT catch `except Exception` and silently swallow.** Either
|
6. **DO NOT catch `except Exception` and silently swallow.** Either
|
||||||
narrow the exception type, convert to `ErrorInfo` in a `Result`,
|
narrow the exception type, convert to `ErrorInfo` in a `Result`,
|
||||||
or document the intentional swallow with a comment-free `assert`
|
or document the intentional swallow with a comment-free `assert`
|
||||||
for the precondition. The audit script flags this as
|
for the precondition. The audit script flags this as
|
||||||
`INTERNAL_SILENT_SWALLOW`.
|
`INTERNAL_SILENT_SWALLOW`.
|
||||||
|
|
||||||
7. **DO NOT catch `except Exception` in non-`*_result` code without
|
7. **DO NOT catch `except Exception` in non-`*_result` code without
|
||||||
conversion to `ErrorInfo`.** If you must catch, convert:
|
conversion to `ErrorInfo`.** If you must catch, convert:
|
||||||
`except SomeError as e: return Result(data=NIL_T, errors=[ErrorInfo(kind=INTERNAL, message=..., original=e)])`.
|
`except SomeError as e: return Result(data=NIL_T, errors=[ErrorInfo(kind=INTERNAL, message=..., original=e)])`.
|
||||||
The audit script flags this as `INTERNAL_BROAD_CATCH`.
|
The audit script flags this as `INTERNAL_BROAD_CATCH`.
|
||||||
|
|
||||||
### The 3 boundary patterns (where `try/except` IS the right answer)
|
### The 3 boundary patterns (where `try/except` IS the right answer)
|
||||||
|
|
||||||
@@ -982,20 +982,20 @@ These are the 3 categories where `try/except` is legitimate. See the
|
|||||||
"Boundary Types" section above for the full discussion.
|
"Boundary Types" section above for the full discussion.
|
||||||
|
|
||||||
1. **Third-party SDK calls.** Wrapping `anthropic.Anthropic().messages.create(...)`
|
1. **Third-party SDK calls.** Wrapping `anthropic.Anthropic().messages.create(...)`
|
||||||
in `try/except anthropic.APIError` is the canonical pattern.
|
in `try/except anthropic.APIError` is the canonical pattern.
|
||||||
Convert to `ErrorInfo`; return in `Result`.
|
Convert to `ErrorInfo`; return in `Result`.
|
||||||
|
|
||||||
2. **Stdlib I/O that can raise.** `open()`, `os.path.*`,
|
2. **Stdlib I/O that can raise.** `open()`, `os.path.*`,
|
||||||
`json.loads()`, `subprocess.run()`, `socket.*`, `sqlite3.*`,
|
`json.loads()`, `subprocess.run()`, `socket.*`, `sqlite3.*`,
|
||||||
`chromadb.PersistentClient()` can all raise. Catch the specific
|
`chromadb.PersistentClient()` can all raise. Catch the specific
|
||||||
exception (`OSError`, `FileNotFoundError`, `json.JSONDecodeError`,
|
exception (`OSError`, `FileNotFoundError`, `json.JSONDecodeError`,
|
||||||
`subprocess.CalledProcessError`, etc.); convert to `ErrorInfo`.
|
`subprocess.CalledProcessError`, etc.); convert to `ErrorInfo`.
|
||||||
|
|
||||||
3. **FastAPI `HTTPException` in `_api_*` handlers.** `raise
|
3. **FastAPI `HTTPException` in `_api_*` handlers.** `raise
|
||||||
HTTPException(status_code=..., detail=...)` in a function named
|
HTTPException(status_code=..., detail=...)` in a function named
|
||||||
`_api_*` is the FastAPI-idiomatic way to signal HTTP errors.
|
`_api_*` is the FastAPI-idiomatic way to signal HTTP errors.
|
||||||
FastAPI converts it to a JSON response at the framework level.
|
FastAPI converts it to a JSON response at the framework level.
|
||||||
This is NOT an exception leak; it's the framework contract.
|
This is NOT an exception leak; it's the framework contract.
|
||||||
|
|
||||||
### The pre-commit gate
|
### The pre-commit gate
|
||||||
|
|
||||||
@@ -1038,14 +1038,14 @@ automated check; the checklist is the manual one.
|
|||||||
---
|
---
|
||||||
|
|
||||||
- `conductor/tracks/data_oriented_error_handling_20260606/spec.md` — the spec
|
- `conductor/tracks/data_oriented_error_handling_20260606/spec.md` — the spec
|
||||||
that established this convention.
|
that established this convention.
|
||||||
- `docs/guide_ai_client.md` "Data-Oriented Error Handling (Fleury Pattern)"
|
- `docs/guide_ai_client.md` "Data-Oriented Error Handling (Fleury Pattern)"
|
||||||
— the in-context guide for the provider layer.
|
— the in-context guide for the provider layer.
|
||||||
- `docs/guide_mcp_client.md` "Data-Oriented Error Handling (Fleury Pattern)"
|
- `docs/guide_mcp_client.md` "Data-Oriented Error Handling (Fleury Pattern)"
|
||||||
— the in-context guide for the MCP tool layer.
|
— the in-context guide for the MCP tool layer.
|
||||||
- `conductor/code_styleguides/data_oriented_design.md` (added 2026-06-12) — the canonical Data-Oriented Design (DOD) reference; this track is the canonical application of DOD to error handling ("errors are data, not control flow").
|
- `conductor/code_styleguides/data_oriented_design.md` (added 2026-06-12) — the canonical Data-Oriented Design (DOD) reference; this track is the canonical application of DOD to error handling ("errors are data, not control flow").
|
||||||
- `conductor/code_styleguides/agent_memory_dimensions.md` (added 2026-06-12) — the 4-dim memory model; the knowledge harvest TDD protocol in `workflow.md` uses this track's `Result` pattern.
|
- `conductor/code_styleguides/agent_memory_dimensions.md` (added 2026-06-12) — the 4-dim memory model; the knowledge harvest TDD protocol in `workflow.md` uses this track's `Result` pattern.
|
||||||
- `docs/guide_rag.md` "Data-Oriented Error Handling (Fleury Pattern)" — the
|
- `docs/guide_rag.md` "Data-Oriented Error Handling (Fleury Pattern)" — the
|
||||||
in-context guide for the RAG engine.
|
in-context guide for the RAG engine.
|
||||||
- Ryan Fleury's [original article](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors)
|
- Ryan Fleury's [original article](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors)
|
||||||
— the philosophical foundation.
|
— the philosophical foundation.
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ The canonical mandate is in `conductor/code_styleguides/data_oriented_design.md`
|
|||||||
## Code Standards & Architecture
|
## Code Standards & Architecture
|
||||||
|
|
||||||
- **Data-Oriented & Immediate Mode Heuristics:** Align with the architectural values of engineers like Casey Muratori and Mike Acton.
|
- **Data-Oriented & Immediate Mode Heuristics:** Align with the architectural values of engineers like Casey Muratori and Mike Acton.
|
||||||
- **The "Less Python Does, the Better" Rule:** Python should act primarily as a procedural semantic definer (similar to how ImGui defines a UI DAG), delegating heavy lifting to efficient data structures, vectorized operations, or lower-level primitives.
|
- **The "Less Python Does, the Better" Rule:** Python should act primarily as a procedural semantic definer (similar to how ImGui defines a UI DAG), delegating heavy lifting to efficient data structures, vectorized operations, or lower-level primitives.
|
||||||
- Minimize Python JIT overhead by favoring bulk data processing over fine-grained object-oriented manipulation.
|
- Minimize Python JIT overhead by favoring bulk data processing over fine-grained object-oriented manipulation.
|
||||||
- The GUI (`gui_2.py`) must remain a pure visualization of application state. It should not *own* complex business logic or orchestrator hooks (strive to decouple the 'Application' controller from the 'View').
|
- The GUI (`gui_2.py`) must remain a pure visualization of application state. It should not *own* complex business logic or orchestrator hooks (strive to decouple the 'Application' controller from the 'View').
|
||||||
- Treat the UI as an immediate mode frame-by-frame projection of underlying data structures.
|
- Treat the UI as an immediate mode frame-by-frame projection of underlying data structures.
|
||||||
- Optimize for zero lag and never block the main render loop with heavy Python JIT work.
|
- Optimize for zero lag and never block the main render loop with heavy Python JIT work.
|
||||||
- Utilize proper asynchronous batching and queue-based pipelines for background AI work, ensuring a data-oriented flow rather than tangled object-oriented state graphs.
|
- Utilize proper asynchronous batching and queue-based pipelines for background AI work, ensuring a data-oriented flow rather than tangled object-oriented state graphs.
|
||||||
- **Strict State Management:** There must be a rigorous separation between the Main GUI rendering thread and daemon execution threads. The UI should *never* hang during AI communication or script execution. Use lock-protected queues and events for synchronization.
|
- **Strict State Management:** There must be a rigorous separation between the Main GUI rendering thread and daemon execution threads. The UI should *never* hang during AI communication or script execution. Use lock-protected queues and events for synchronization.
|
||||||
- **Comprehensive Logging:** Aggressively log all actions, API payloads, tool calls, and executed scripts. Maintain timestamped JSON-L and markdown logs to ensure total transparency and debuggability.
|
- **Comprehensive Logging:** Aggressively log all actions, API payloads, tool calls, and executed scripts. Maintain timestamped JSON-L and markdown logs to ensure total transparency and debuggability.
|
||||||
- **Mandatory ImGui Verification:** All changes to the GUI (`gui_2.py`) MUST be verified using the custom AST linter (`scripts/check_imgui_scopes.py`) to ensure all ImGui scopes (begin/end, push/pop) are properly matched. Developers should prioritize the use of `src/imgui_scopes.py` context managers (`imscope`) over manual push/pop calls.
|
- **Mandatory ImGui Verification:** All changes to the GUI (`gui_2.py`) MUST be verified using the custom AST linter (`scripts/check_imgui_scopes.py`) to ensure all ImGui scopes (begin/end, push/pop) are properly matched. Developers should prioritize the use of `src/imgui_scopes.py` context managers (`imscope`) over manual push/pop calls.
|
||||||
@@ -57,8 +57,8 @@ For the **Indentation** and **Newlines** rules (1-space indent, blank-line rules
|
|||||||
- **Region Blocks:** Use `#region: Name` and `#endregion: Name` to logically organize massive files that cannot be easily broken apart without increasing context load.
|
- **Region Blocks:** Use `#region: Name` and `#endregion: Name` to logically organize massive files that cannot be easily broken apart without increasing context load.
|
||||||
- **Type Hinting:** Mandatory, strict type hints for all parameters, return types, and global variables to ensure high-signal context for AI agents.
|
- **Type Hinting:** Mandatory, strict type hints for all parameters, return types, and global variables to ensure high-signal context for AI agents.
|
||||||
- **Structural Dependency Mapping (SDM):** All major state variables, methods, and functions MUST include terse dependency tags at the end of their docstrings for AI-assisted impact analysis.
|
- **Structural Dependency Mapping (SDM):** All major state variables, methods, and functions MUST include terse dependency tags at the end of their docstrings for AI-assisted impact analysis.
|
||||||
- **Functions/Methods:** `[C: Caller1, Caller2]` (Primary callers).
|
- **Functions/Methods:** `[C: Caller1, Caller2]` (Primary callers).
|
||||||
- **State Variables:** `[M: File:Line, Method]` (Mutation points) and `[U: File]` (Major use paths).
|
- **State Variables:** `[M: File:Line, Method]` (Mutation points) and `[U: File]` (Major use paths).
|
||||||
|
|
||||||
## Data-Oriented Error Handling
|
## Data-Oriented Error Handling
|
||||||
|
|
||||||
@@ -89,23 +89,23 @@ are trained on idiomatic Python and will revert to it without explicit
|
|||||||
guidance. The project enforces the convention through 4 mechanisms:
|
guidance. The project enforces the convention through 4 mechanisms:
|
||||||
|
|
||||||
1. **`conductor/code_styleguides/error_handling.md`** — the canonical
|
1. **`conductor/code_styleguides/error_handling.md`** — the canonical
|
||||||
styleguide. Has 5 patterns, 3 boundary types, 1 broad-except
|
styleguide. Has 5 patterns, 3 boundary types, 1 broad-except
|
||||||
distinction rule, 1 constructor-raise rule, 1 re-raise rule, and
|
distinction rule, 1 constructor-raise rule, 1 re-raise rule, and
|
||||||
the audit script reference. Read this before writing any code that
|
the audit script reference. Read this before writing any code that
|
||||||
can fail at runtime.
|
can fail at runtime.
|
||||||
|
|
||||||
2. **`conductor/code_styleguides/error_handling.md` "AI Agent Checklist"** —
|
2. **`conductor/code_styleguides/error_handling.md` "AI Agent Checklist"** —
|
||||||
the explicit cheatsheet of 5 MUST-DO rules, 7 MUST-NOT-DO rules, and
|
the explicit cheatsheet of 5 MUST-DO rules, 7 MUST-NOT-DO rules, and
|
||||||
3 boundary patterns. Run this checklist before claiming a task is
|
3 boundary patterns. Run this checklist before claiming a task is
|
||||||
done.
|
done.
|
||||||
|
|
||||||
3. **`scripts/audit_exception_handling.py`** — the static analyzer
|
3. **`scripts/audit_exception_handling.py`** — the static analyzer
|
||||||
that catches violations before commit. The script classifies
|
that catches violations before commit. The script classifies
|
||||||
`try/except/finally/raise` sites against 10 categories. Use it
|
`try/except/finally/raise` sites against 10 categories. Use it
|
||||||
pre-commit.
|
pre-commit.
|
||||||
|
|
||||||
4. **`scripts/audit_exception_handling.py --strict`** — the CI gate.
|
4. **`scripts/audit_exception_handling.py --strict`** — the CI gate.
|
||||||
Exits 1 on any violation. Wire this into pre-commit hooks and CI.
|
Exits 1 on any violation. Wire this into pre-commit hooks and CI.
|
||||||
|
|
||||||
**The 4 enforcement audit scripts (the project-level enforcement set):**
|
**The 4 enforcement audit scripts (the project-level enforcement set):**
|
||||||
|
|
||||||
|
|||||||
+53
-53
@@ -9,7 +9,7 @@ To serve as an expert-level utility for personal developer use on small projects
|
|||||||
For deep implementation details when planning or implementing tracks, consult `docs/` (last refreshed: 2026-06-02 via the comprehensive documentation refresh track):
|
For deep implementation details when planning or implementing tracks, consult `docs/` (last refreshed: 2026-06-02 via the comprehensive documentation refresh track):
|
||||||
|
|
||||||
**Core architecture:**
|
**Core architecture:**
|
||||||
- **[docs/guide_architecture.md](../docs/guide_architecture.md):** Threading model, event system, AI client multi-provider (Gemini, Anthropic, DeepSeek, Gemini CLI, MiniMax), HITL mechanism, comms logging
|
- **[docs/guide_architecture.md](../docs/guide_architecture.md):** Threading model, event system, AI client multi-provider (Gemini, Anthropic, DeepSeek, MiniMax), HITL mechanism, comms logging
|
||||||
- **[docs/guide_meta_boundary.md](../docs/guide_meta_boundary.md):** The critical distinction between the Application's Strict-HITL environment and the Meta-Tooling environment used to build it
|
- **[docs/guide_meta_boundary.md](../docs/guide_meta_boundary.md):** The critical distinction between the Application's Strict-HITL environment and the Meta-Tooling environment used to build it
|
||||||
- **[docs/guide_tools.md](../docs/guide_tools.md):** MCP Bridge, 45-tool inventory, Hook API, ApiHookClient, shell runner
|
- **[docs/guide_tools.md](../docs/guide_tools.md):** MCP Bridge, 45-tool inventory, Hook API, ApiHookClient, shell runner
|
||||||
- **[docs/guide_mma.md](../docs/guide_mma.md):** 4-tier orchestration, DAG engine, worker lifecycle, persona application
|
- **[docs/guide_mma.md](../docs/guide_mma.md):** 4-tier orchestration, DAG engine, worker lifecycle, persona application
|
||||||
@@ -17,7 +17,7 @@ For deep implementation details when planning or implementing tracks, consult `d
|
|||||||
|
|
||||||
**Per-source-file references (NEW):**
|
**Per-source-file references (NEW):**
|
||||||
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md):** `src/gui_2.py` (~437KB): App class lifecycle, ~90 module-level render functions, Multi-Viewport docks, panel registry, ImGuiScope context managers, hot reload support
|
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md):** `src/gui_2.py` (~437KB): App class lifecycle, ~90 module-level render functions, Multi-Viewport docks, panel registry, ImGuiScope context managers, hot reload support
|
||||||
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md):** `src/ai_client.py` (~166KB): multi-provider LLM singleton (8 providers: gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), async dispatch via `asyncio.gather`, threading.local source tier tagging, Anthropic ephemeral + Gemini explicit caching, Tier 4 QA error interception, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`)
|
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md):** `src/ai_client.py` (~166KB): multi-provider LLM singleton (7 providers: gemini, anthropic, deepseek, minimax, qwen, grok, llama), async dispatch via `asyncio.gather`, threading.local source tier tagging, Anthropic ephemeral + Gemini explicit caching, Tier 4 QA error interception, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`)
|
||||||
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md):** `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB): HookServer on `127.0.0.1:8999`, ApiHookClient wrapper, 8+ endpoints, Remote Confirmation Protocol via `/api/ask`
|
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md):** `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB): HookServer on `127.0.0.1:8999`, ApiHookClient wrapper, 8+ endpoints, Remote Confirmation Protocol via `/api/ask`
|
||||||
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md):** `src/mcp_client.py` (~92KB, 45 tools): 3-layer security (Allowlist → Validate → Resolve), all native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), ExternalMCPManager (Stdio + SSE), JSON-RPC 2.0 engine. Tool specs now live in `src/mcp_tool_specs.py` (typed `ToolSpec` dataclass + `_REGISTRY`); `mcp_client.py` re-exports `TOOL_NAMES` for backward compat.
|
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md):** `src/mcp_client.py` (~92KB, 45 tools): 3-layer security (Allowlist → Validate → Resolve), all native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), ExternalMCPManager (Stdio + SSE), JSON-RPC 2.0 engine. Tool specs now live in `src/mcp_tool_specs.py` (typed `ToolSpec` dataclass + `_REGISTRY`); `mcp_client.py` re-exports `TOOL_NAMES` for backward compat.
|
||||||
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md):** `src/app_controller.py` (~240KB): headless orchestrator, AppState dataclass, all subsystem managers, `_predefined_callbacks`/`_gettable_fields` Hook API registries, SyncEventQueue, headless mode
|
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md):** `src/app_controller.py` (~240KB): headless orchestrator, AppState dataclass, all subsystem managers, `_predefined_callbacks`/`_gettable_fields` Hook API registries, SyncEventQueue, headless mode
|
||||||
@@ -49,57 +49,57 @@ For deep implementation details when planning or implementing tracks, consult `d
|
|||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
- **Multi-Provider Integration:** Supports Gemini, Anthropic, DeepSeek, Gemini CLI (headless), MiniMax, Qwen, Grok, and Llama (Ollama) with seamless switching.
|
- **Multi-Provider Integration:** Supports Gemini, Anthropic, DeepSeek, (headless), MiniMax, Qwen, Grok, and Llama (Ollama) with seamless switching.
|
||||||
- **4-Tier Hierarchical Multi-Model Architecture:** Orchestrates an intelligent cascade of specialized models to isolate cognitive loads and minimize token burn.
|
- **4-Tier Hierarchical Multi-Model Architecture:** Orchestrates an intelligent cascade of specialized models to isolate cognitive loads and minimize token burn.
|
||||||
- **Tier 1 (Orchestrator):** Strategic product alignment, setup (`/conductor:setup`), and track initialization (`/conductor:newTrack`) using `gemini-3.1-pro-preview`.
|
- **Tier 1 (Orchestrator):** Strategic product alignment, setup (`/conductor:setup`), and track initialization (`/conductor:newTrack`) using `gemini-3.1-pro-preview`.
|
||||||
- **Tier 2 (Tech Lead):** Technical oversight and track execution (`/conductor:implement`) using `gemini-3-flash-preview`. Maintains persistent context throughout implementation.
|
- **Tier 2 (Tech Lead):** Technical oversight and track execution (`/conductor:implement`) using `gemini-3-flash-preview`. Maintains persistent context throughout implementation.
|
||||||
- **Tier 3 (Worker):** Surgical code implementation and TDD using `gemini-2.5-flash-lite`. Operates statelessly with tool access and dependency skeletons.
|
- **Tier 3 (Worker):** Surgical code implementation and TDD using `gemini-2.5-flash-lite`. Operates statelessly with tool access and dependency skeletons.
|
||||||
- **Tier 4 (QA):** Error analysis and diagnostics using `gemini-2.5-flash-lite`. Operates statelessly with tool access.
|
- **Tier 4 (QA):** Error analysis and diagnostics using `gemini-2.5-flash-lite`. Operates statelessly with tool access.
|
||||||
- **MMA Delegation Engine:** Route tasks, ensuring role-scoped context and detailed observability via timestamped sub-agent logs. Supports dynamic ticket creation and dependency resolution via an automated Dispatcher Loop.
|
- **MMA Delegation Engine:** Route tasks, ensuring role-scoped context and detailed observability via timestamped sub-agent logs. Supports dynamic ticket creation and dependency resolution via an automated Dispatcher Loop.
|
||||||
- **MMA Observability Dashboard:** A high-density control center within the GUI for monitoring and managing the 4-Tier architecture.
|
- **MMA Observability Dashboard:** A high-density control center within the GUI for monitoring and managing the 4-Tier architecture.
|
||||||
- **Track Browser:** Real-time visualization of all implementation tracks with status indicators and progress bars. Includes a dedicated **Active Track Summary** featuring a color-coded progress bar, precise ticket status breakdown (Completed, In Progress, Blocked, Todo), and dynamic **ETA estimation** based on historical completion times.
|
- **Track Browser:** Real-time visualization of all implementation tracks with status indicators and progress bars. Includes a dedicated **Active Track Summary** featuring a color-coded progress bar, precise ticket status breakdown (Completed, In Progress, Blocked, Todo), and dynamic **ETA estimation** based on historical completion times.
|
||||||
- **Visual Task DAG:** An interactive, node-based visualizer for the active track's task dependencies using `imgui-node-editor`. Features color-coded state tracking (Ready, Running, Blocked, Done), drag-and-drop dependency creation, and right-click deletion.
|
- **Visual Task DAG:** An interactive, node-based visualizer for the active track's task dependencies using `imgui-node-editor`. Features color-coded state tracking (Ready, Running, Blocked, Done), drag-and-drop dependency creation, and right-click deletion.
|
||||||
- **Strategy Visualization:** Dedicated real-time output streams for Tier 1 (Strategic Planning) and Tier 2/3 (Execution) agents, allowing the user to follow the agent's reasoning chains alongside the task DAG.
|
- **Strategy Visualization:** Dedicated real-time output streams for Tier 1 (Strategic Planning) and Tier 2/3 (Execution) agents, allowing the user to follow the agent's reasoning chains alongside the task DAG.
|
||||||
- **Agent-Focused Filtering:** Allows the user to focus the entire GUI (Session Hub, Discussion Hub, Comms) on a specific agent's activities and scoped context.
|
- **Agent-Focused Filtering:** Allows the user to focus the entire GUI (Session Hub, Discussion Hub, Comms) on a specific agent's activities and scoped context.
|
||||||
- **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files. Supports **Project-Specific Conductor Directories**, defaulting to `./conductor` relative to each project's TOML file. Projects can define their own conductor path override in `manual_slop.toml` (`[conductor].dir`) via the Projects tab for isolated track management. This prevents global context pollution and ensures the Tech Lead session is isolated to the specific track's objective.
|
- **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files. Supports **Project-Specific Conductor Directories**, defaulting to `./conductor` relative to each project's TOML file. Projects can define their own conductor path override in `manual_slop.toml` (`[conductor].dir`) via the Projects tab for isolated track management. This prevents global context pollution and ensures the Tech Lead session is isolated to the specific track's objective.
|
||||||
**Native DAG Execution Engine:** Employs a Python-based Directed Acyclic Graph (DAG) engine to manage complex task dependencies. Supports automated topological sorting, robust cycle detection, and **transitive blocking propagation** (cascading `blocked` status to downstream dependents to prevent execution stalls).
|
**Native DAG Execution Engine:** Employs a Python-based Directed Acyclic Graph (DAG) engine to manage complex task dependencies. Supports automated topological sorting, robust cycle detection, and **transitive blocking propagation** (cascading `blocked` status to downstream dependents to prevent execution stalls).
|
||||||
|
|
||||||
- **Programmable Execution State machine:** Governing the transition between "Auto-Queue" (autonomous worker spawning) and "Step Mode" (explicit manual approval for each task transition).
|
- **Programmable Execution State machine:** Governing the transition between "Auto-Queue" (autonomous worker spawning) and "Step Mode" (explicit manual approval for each task transition).
|
||||||
- **Role-Scoped Documentation:** Automated mapping of foundational documents to specific tiers to prevent token bloat and maintain high-signal context.
|
- **Role-Scoped Documentation:** Automated mapping of foundational documents to specific tiers to prevent token bloat and maintain high-signal context.
|
||||||
- **Tiered Context Scoping:** Employs optimized context subsets for each tier. Tiers 1 & 2 receive strategic documents and full history, while Tier 3/4 workers receive task-specific "Focus Files" and automated AST dependency skeletons.
|
- **Tiered Context Scoping:** Employs optimized context subsets for each tier. Tiers 1 & 2 receive strategic documents and full history, while Tier 3/4 workers receive task-specific "Focus Files" and automated AST dependency skeletons.
|
||||||
- **Worker Spawn Interceptor:** A mandatory security gate that intercepts every sub-agent launch. Provides a GUI modal allowing the user to review, modify, or reject the worker's prompt and file context before it is sent to the API.
|
- **Worker Spawn Interceptor:** A mandatory security gate that intercepts every sub-agent launch. Provides a GUI modal allowing the user to review, modify, or reject the worker's prompt and file context before it is sent to the API.
|
||||||
- **Strict Memory Siloing:** Employs tree-sitter AST-based interface extraction (Skeleton View, Curated View, and Targeted View) and "Context Amnesia" to provide workers only with the absolute minimum context required. Supports **Python, C, and C++** languages for structural extraction. Features an intelligent context aggregation engine utilizing **Hash-Based Caching (SHA256)** and LRU eviction to eliminate redundant processing. Employs **Tier-Level Aggregation Strategies** (`full`, `summarize`, `skeleton`) configured directly via Agent Personas, integrating high-tier AI sub-agents during the aggregation pass to generate succinct, high-signal summaries for both code and text files. Includes **Manual Skeleton Context Injection**, allowing developers to preview and manually inject file skeletons or full content into discussions via a dedicated GUI modal. Features multi-level dependency traversal and AST caching to minimize re-parsing overhead and token burn.
|
- **Strict Memory Siloing:** Employs tree-sitter AST-based interface extraction (Skeleton View, Curated View, and Targeted View) and "Context Amnesia" to provide workers only with the absolute minimum context required. Supports **Python, C, and C++** languages for structural extraction. Features an intelligent context aggregation engine utilizing **Hash-Based Caching (SHA256)** and LRU eviction to eliminate redundant processing. Employs **Tier-Level Aggregation Strategies** (`full`, `summarize`, `skeleton`) configured directly via Agent Personas, integrating high-tier AI sub-agents during the aggregation pass to generate succinct, high-signal summaries for both code and text files. Includes **Manual Skeleton Context Injection**, allowing developers to preview and manually inject file skeletons or full content into discussions via a dedicated GUI modal. Features multi-level dependency traversal and AST caching to minimize re-parsing overhead and token burn.
|
||||||
- **Explicit Execution Control:** All AI-generated PowerShell scripts require explicit human confirmation via interactive UI dialogs before execution, supported by a global "Linear Execution Clutch" for deterministic debugging.
|
- **Explicit Execution Control:** All AI-generated PowerShell scripts require explicit human confirmation via interactive UI dialogs before execution, supported by a global "Linear Execution Clutch" for deterministic debugging.
|
||||||
- **Parallel Multi-Agent Execution:** Executes multiple AI workers in parallel using a non-blocking execution engine and a dedicated `WorkerPool`. Features configurable concurrency limits (defaulting to 4) to optimize resource usage and prevent API rate limiting.
|
- **Parallel Multi-Agent Execution:** Executes multiple AI workers in parallel using a non-blocking execution engine and a dedicated `WorkerPool`. Features configurable concurrency limits (defaulting to 4) to optimize resource usage and prevent API rate limiting.
|
||||||
- **Beads Mode Integration:** Supports [Beads](https://github.com/steveyegge/beads) as a first-class, project-specific alternative to markdown-based tracking.
|
- **Beads Mode Integration:** Supports [Beads](https://github.com/steveyegge/beads) as a first-class, project-specific alternative to markdown-based tracking.
|
||||||
- **Git-Backed Issue Tracking:** Uses a local `.beads` repository (backed by Dolt) to store the task graph, allowing tracks and tickets to be versioned alongside the code.
|
- **Git-Backed Issue Tracking:** Uses a local `.beads` repository (backed by Dolt) to store the task graph, allowing tracks and tickets to be versioned alongside the code.
|
||||||
- **Beads Toolset:** Provides a suite of MCP tools (`bd_create`, `bd_update`, `bd_ready`, `bd_list`) for agents to manage the issue graph autonomously.
|
- **Beads Toolset:** Provides a suite of MCP tools (`bd_create`, `bd_update`, `bd_ready`, `bd_list`) for agents to manage the issue graph autonomously.
|
||||||
- **Context Compaction:** Automatically summarizes completed beads to preserve context window space for the active task.
|
- **Context Compaction:** Automatically summarizes completed beads to preserve context window space for the active task.
|
||||||
- **Augmented Visualizations:** Integrates with the Visual DAG and MMA Dashboard to provide real-time visibility into the Dolt-backed issue graph.
|
- **Augmented Visualizations:** Integrates with the Visual DAG and MMA Dashboard to provide real-time visibility into the Dolt-backed issue graph.
|
||||||
- **Parallel Tool Execution:** Executes independent tool calls (e.g., parallel file reads) concurrently within a single agent turn using an asynchronous execution engine, significantly reducing end-to-end latency.
|
- **Parallel Tool Execution:** Executes independent tool calls (e.g., parallel file reads) concurrently within a single agent turn using an asynchronous execution engine, significantly reducing end-to-end latency.
|
||||||
- **Automated Tier 4 QA:** Integrates real-time error interception in the shell runner, automatically forwarding technical failures to cheap sub-agents for 20-word diagnostic summaries injected back into the worker history.
|
- **Automated Tier 4 QA:** Integrates real-time error interception in the shell runner, automatically forwarding technical failures to cheap sub-agents for 20-word diagnostic summaries injected back into the worker history.
|
||||||
- **External MCP Server Support:** Adds support for integrating external Model Context Protocol (MCP) servers, expanding the agent's toolset with the broader MCP ecosystem.
|
- **External MCP Server Support:** Adds support for integrating external Model Context Protocol (MCP) servers, expanding the agent's toolset with the broader MCP ecosystem.
|
||||||
- **Multi-Server Lifecycle Management:** Orchestrates multiple concurrent MCP server sessions (Stdio for local subprocesses and SSE for remote servers).
|
- **Multi-Server Lifecycle Management:** Orchestrates multiple concurrent MCP server sessions (Stdio for local subprocesses and SSE for remote servers).
|
||||||
- **Flexible Configuration:** Supports global (`config.toml`) and project-specific (`manual_slop.toml`) paths for `mcp_config.json` (standard MCP configuration format).
|
- **Flexible Configuration:** Supports global (`config.toml`) and project-specific (`manual_slop.toml`) paths for `mcp_config.json` (standard MCP configuration format).
|
||||||
- **Auto-Start & Discovery:** Automatically initializes configured servers on project load and dynamically aggregates their tools into the agent's capability declarations.
|
- **Auto-Start & Discovery:** Automatically initializes configured servers on project load and dynamically aggregates their tools into the agent's capability declarations.
|
||||||
- **Dedicated Operations UI:** Features a new **External Tools** section within the Operations Hub for monitoring server status (idle, starting, running, error) and browsing discovered tool schemas. Supports **Pop-Out Panel functionality**, allowing the External Tools interface to be detached into a standalone window for optimized multi-monitor workflows.
|
- **Dedicated Operations UI:** Features a new **External Tools** section within the Operations Hub for monitoring server status (idle, starting, running, error) and browsing discovered tool schemas. Supports **Pop-Out Panel functionality**, allowing the External Tools interface to be detached into a standalone window for optimized multi-monitor workflows.
|
||||||
- **Strict HITL Safety:** All external tool calls are intercepted and require explicit human-in-the-loop approval via the standard confirmation dialog before execution.
|
- **Strict HITL Safety:** All external tool calls are intercepted and require explicit human-in-the-loop approval via the standard confirmation dialog before execution.
|
||||||
- **Retrieval-Augmented Generation (RAG) Support:** Introduces advanced retrieval capabilities to overcome context window limitations and reduce hallucination.
|
- **Retrieval-Augmented Generation (RAG) Support:** Introduces advanced retrieval capabilities to overcome context window limitations and reduce hallucination.
|
||||||
- **Multi-Source Retrieval:** Supports local vector stores (ChromaDB) and an **External RAG Bridge** via the Model Context Protocol (MCP) for connecting to third-party retrieval services.
|
- **Multi-Source Retrieval:** Supports local vector stores (ChromaDB) and an **External RAG Bridge** via the Model Context Protocol (MCP) for connecting to third-party retrieval services.
|
||||||
- **High-Performance Indexing:** Employs a parallelized indexing pipeline using `ThreadPoolExecutor` and incremental updates based on file `mtime` to handle large codebases efficiently.
|
- **High-Performance Indexing:** Employs a parallelized indexing pipeline using `ThreadPoolExecutor` and incremental updates based on file `mtime` to handle large codebases efficiently.
|
||||||
- **Deep Discussion Integration:** Retrieved context fragments are automatically prepended to agent prompts and captured in the discussion history, featuring a dedicated visualization mode with source buttons for instant file navigation.
|
- **Deep Discussion Integration:** Retrieved context fragments are automatically prepended to agent prompts and captured in the discussion history, featuring a dedicated visualization mode with source buttons for instant file navigation.
|
||||||
- **Configurable Strategy:** Users can toggle RAG globally and fine-tune retrieval parameters (source, embedding provider, chunk size/overlap) directly within the AI Settings.
|
- **Configurable Strategy:** Users can toggle RAG globally and fine-tune retrieval parameters (source, embedding provider, chunk size/overlap) directly within the AI Settings.
|
||||||
- **Automated Synchronization:** Features background re-indexing of the project workspace, ensuring the vector store remains consistent with the current project state.
|
- **Automated Synchronization:** Features background re-indexing of the project workspace, ensuring the vector store remains consistent with the current project state.
|
||||||
- **Undo/Redo History Support:** Implements a robust, non-provider based undo/redo system for managing UI state and discussion mutations.
|
- **Undo/Redo History Support:** Implements a robust, non-provider based undo/redo system for managing UI state and discussion mutations.
|
||||||
- **Comprehensive State Snapshots:** Captures all critical UI state, including text inputs (system prompts, AI input), model parameters (Temperature, Top-P), and context management (files, screenshots).
|
- **Comprehensive State Snapshots:** Captures all critical UI state, including text inputs (system prompts, AI input), model parameters (Temperature, Top-P), and context management (files, screenshots).
|
||||||
- **Discussion Mutation Tracking:** Allows reverting and redoing additions, deletions, and structural changes to the discussion history.
|
- **Discussion Mutation Tracking:** Allows reverting and redoing additions, deletions, and structural changes to the discussion history.
|
||||||
- **History List View:** Features a dedicated, scrollable panel showing recent actions with timestamps, allowing users to jump directly to any historical state.
|
- **History List View:** Features a dedicated, scrollable panel showing recent actions with timestamps, allowing users to jump directly to any historical state.
|
||||||
- **Tactile Hotkeys:** Supports industry-standard shortcuts (`Ctrl+Z`, `Ctrl+Y`, `Ctrl+Shift+Z`) for fast, intuitive state navigation.
|
- **Tactile Hotkeys:** Supports industry-standard shortcuts (`Ctrl+Z`, `Ctrl+Y`, `Ctrl+Shift+Z`) for fast, intuitive state navigation.
|
||||||
- **High-Fidelity Selectable UI:** Most read-only labels and logs across the interface (including discussion history, comms payloads, tool outputs, and telemetry metrics) are now implemented as selectable text fields. This enables standard OS-level text selection and copying (Ctrl+C) while maintaining a high-density, non-editable aesthetic.
|
- **High-Fidelity Selectable UI:** Most read-only labels and logs across the interface (including discussion history, comms payloads, tool outputs, and telemetry metrics) are now implemented as selectable text fields. This enables standard OS-level text selection and copying (Ctrl+C) while maintaining a high-density, non-editable aesthetic.
|
||||||
- **High-Fidelity UI Rendering:** Employs advanced 3x font oversampling and sub-pixel positioning to ensure crisp, high-clarity text rendering across all resolutions, enhancing readability for dense logs and complex code fragments.
|
- **High-Fidelity UI Rendering:** Employs advanced 3x font oversampling and sub-pixel positioning to ensure crisp, high-clarity text rendering across all resolutions, enhancing readability for dense logs and complex code fragments.
|
||||||
- **Workspace Docking & Layout Profiles:** Expands layout management to support named workspace profiles, capturing multi-viewport docking arrangements, window visibility, and internal panel states.
|
- **Workspace Docking & Layout Profiles:** Expands layout management to support named workspace profiles, capturing multi-viewport docking arrangements, window visibility, and internal panel states.
|
||||||
- **Scope Inheritance:** Profiles follow a Global and Project inheritance model, allowing for both universal defaults and project-specific layouts.
|
- **Scope Inheritance:** Profiles follow a Global and Project inheritance model, allowing for both universal defaults and project-specific layouts.
|
||||||
- **Contextual Auto-Switch (Experimental):** An opt-in mechanism that automatically binds and loads specific workspace profiles based on the active MMA Tier or task context, dynamically reshaping the UI for the current cognitive load.
|
- **Contextual Auto-Switch (Experimental):** An opt-in mechanism that automatically binds and loads specific workspace profiles based on the active MMA Tier or task context, dynamically reshaping the UI for the current cognitive load.
|
||||||
- **Enhanced MMA Observability:** Worker streams and ticket previews now support direct text selection, allowing for easy extraction of specific logs or reasoning fragments during parallel execution.
|
- **Enhanced MMA Observability:** Worker streams and ticket previews now support direct text selection, allowing for easy extraction of specific logs or reasoning fragments during parallel execution.
|
||||||
- **Transparent Context Visibility:** A dedicated **Session Hub** exposes the exact aggregated markdown and resolved system prompt sent to the AI.
|
- **Transparent Context Visibility:** A dedicated **Session Hub** exposes the exact aggregated markdown and resolved system prompt sent to the AI.
|
||||||
- **Injection Timeline:** Discussion history visually indicates the precise moments when files or screenshots were injected into the session context.
|
- **Injection Timeline:** Discussion history visually indicates the precise moments when files or screenshots were injected into the session context.
|
||||||
@@ -124,18 +124,18 @@ For deep implementation details when planning or implementing tracks, consult `d
|
|||||||
- **Context & Token Visualization:** Detailed UI panels for monitoring real-time token usage, history depth, and **visual cache awareness** (tracking specific files currently live in the provider's context cache).
|
- **Context & Token Visualization:** Detailed UI panels for monitoring real-time token usage, history depth, and **visual cache awareness** (tracking specific files currently live in the provider's context cache).
|
||||||
- **On-Demand Definition Lookup:** Allows developers to request specific class or function definitions during discussions using `@SymbolName` syntax. Injected definitions feature syntax highlighting, intelligent collapsing for long blocks, and a **[Source]** button for instant navigation to the full file.
|
- **On-Demand Definition Lookup:** Allows developers to request specific class or function definitions during discussions using `@SymbolName` syntax. Injected definitions feature syntax highlighting, intelligent collapsing for long blocks, and a **[Source]** button for instant navigation to the full file.
|
||||||
- **Manual Ticket Queue Management:** Provides a dedicated GUI panel for granular control over the implementation queue. Features include color-coded priority assignment (High, Medium, Low), multi-select bulk operations (Execute, Skip, Block), and interactive drag-and-drop reordering with real-time Directed Acyclic Graph (DAG) validation.
|
- **Manual Ticket Queue Management:** Provides a dedicated GUI panel for granular control over the implementation queue. Features include color-coded priority assignment (High, Medium, Low), multi-select bulk operations (Execute, Skip, Block), and interactive drag-and-drop reordering with real-time Directed Acyclic Graph (DAG) validation.
|
||||||
- **System Prompt Presets:** Comprehensive management system for saving and switching between complex system prompt configurations. Features full visibility and customization of the **Foundational Base System Prompt**, allowing users to modify the core instructions that define agent capabilities and tool usage heuristics. - **Scoped Inheritance:** Supports **Global** (application-wide) and **Project-Specific** presets. Project presets with the same name automatically override global counterparts, allowing for fine-tuned context tailoring.
|
- **System Prompt Presets:** Comprehensive management system for saving and switching between complex system prompt configurations. Features full visibility and customization of the **Foundational Base System Prompt**, allowing users to modify the core instructions that define agent capabilities and tool usage heuristics. - **Scoped Inheritance:** Supports **Global** (application-wide) and **Project-Specific** presets. Project presets with the same name automatically override global counterparts, allowing for fine-tuned context tailoring.
|
||||||
- **Command Palette:** A global, keyboard-driven launcher for actions across Manual Slop, triggered by `Ctrl+Shift+P`. Provides fuzzy-search across all built-in and user-defined commands. Includes an **"Everything" mode** (`Ctrl+Shift+E`) that searches across commands, files, symbols, history, and settings. Uses an **async context preview worker** to prevent UI hangs during cross-domain searches. See [guide_command_palette.md](../docs/guide_command_palette.md).
|
- **Command Palette:** A global, keyboard-driven launcher for actions across Manual Slop, triggered by `Ctrl+Shift+P`. Provides fuzzy-search across all built-in and user-defined commands. Includes an **"Everything" mode** (`Ctrl+Shift+E`) that searches across commands, files, symbols, history, and settings. Uses an **async context preview worker** to prevent UI hangs during cross-domain searches. See [guide_command_palette.md](../docs/guide_command_palette.md).
|
||||||
- **Full AI Profiles:** Presets capture not only the system prompt text but also critical model parameters like **Temperature**, **Top-P**, and **Max Output Tokens**.
|
- **Full AI Profiles:** Presets capture not only the system prompt text but also critical model parameters like **Temperature**, **Top-P**, and **Max Output Tokens**.
|
||||||
- **Preset Manager Modal:** A dedicated high-density GUI for creating, editing, and deleting presets with real-time validation and instant application to the active session.
|
- **Preset Manager Modal:** A dedicated high-density GUI for creating, editing, and deleting presets with real-time validation and instant application to the active session.
|
||||||
- **Agent Personas & Unified Profiles:** Consolidates model settings, provider routing, system prompts, tool presets, and bias profiles into named "Persona" entities.
|
- **Agent Personas & Unified Profiles:** Consolidates model settings, provider routing, system prompts, tool presets, and bias profiles into named "Persona" entities.
|
||||||
- **Single Configuration Entity:** Switch models, tool weights, and system prompts simultaneously using a single Persona selection.
|
- **Single Configuration Entity:** Switch models, tool weights, and system prompts simultaneously using a single Persona selection.
|
||||||
- **Persona Editor Modal:** A dedicated high-density GUI for creating, editing, and deleting Personas.
|
- **Persona Editor Modal:** A dedicated high-density GUI for creating, editing, and deleting Personas.
|
||||||
- **MMA Granular Assignment:** Allows assigning specific Personas to individual agents within the 4-Tier Hierarchical MMA.
|
- **MMA Granular Assignment:** Allows assigning specific Personas to individual agents within the 4-Tier Hierarchical MMA.
|
||||||
- **Agent Tool Weighting & Bias:** Influences agent tool selection via a weighting system.
|
- **Agent Tool Weighting & Bias:** Influences agent tool selection via a weighting system.
|
||||||
- **Semantic Nudging:** Automatically prefixes tool and parameter descriptions with priority tags (e.g., [HIGH PRIORITY], [PREFERRED]) to bias model selection.
|
- **Semantic Nudging:** Automatically prefixes tool and parameter descriptions with priority tags (e.g., [HIGH PRIORITY], [PREFERRED]) to bias model selection.
|
||||||
- **Dynamic Tooling Strategy:** Automatically appends a Markdown "Tooling Strategy" section to system instructions based on the active preset and global bias profile.
|
- **Dynamic Tooling Strategy:** Automatically appends a Markdown "Tooling Strategy" section to system instructions based on the active preset and global bias profile.
|
||||||
- **Global Bias Profiles:** Application of category-level multipliers (e.g., Execution-Focused, Discovery-Heavy) to influence agent behavior across broad toolsets.
|
- **Global Bias Profiles:** Application of category-level multipliers (e.g., Execution-Focused, Discovery-Heavy) to influence agent behavior across broad toolsets.
|
||||||
- **Priority Badges & Refined Layout:** High-density, color-coded visual indicators in tool lists showing the assigned priority level of each capability. Displays tool names before radio buttons with consistent spacing for improved readability.
|
- **Priority Badges & Refined Layout:** High-density, color-coded visual indicators in tool lists showing the assigned priority level of each capability. Displays tool names before radio buttons with consistent spacing for improved readability.
|
||||||
- **Category-Based Filtering:** Integrated category filtering in both the Active Tools panel and the Tool Preset Manager, allowing users to quickly manage large toolsets.
|
- **Category-Based Filtering:** Integrated category filtering in both the Active Tools panel and the Tool Preset Manager, allowing users to quickly manage large toolsets.
|
||||||
- **Fine-Grained Weight Control:** Integrated sliders in the Preset Manager for adjusting individual tool weights (1-5) and parameter-level biases.
|
- **Fine-Grained Weight Control:** Integrated sliders in the Preset Manager for adjusting individual tool weights (1-5) and parameter-level biases.
|
||||||
|
|||||||
+17
-17
@@ -1,4 +1,4 @@
|
|||||||
# Technology Stack: Manual Slop
|
# Technology Stack: Manual Slop
|
||||||
|
|
||||||
> **Core Value (added 2026-06-25):** C11/Odin/Jai semantics in this Python runtime. See `conductor/product-guidelines.md` "Core Value", `conductor/code_styleguides/data_oriented_design.md` §8.5, and `conductor/code_styleguides/python.md` §17. Banned: `dict[str, Any]`, `Any`, `Optional[T]`, `hasattr()` for entity dispatch, `.get()` on known fields. Use typed `@dataclass(frozen=True, slots=True)` with explicit fields. Use `Result[T]` + `NIL_T` sentinels.
|
> **Core Value (added 2026-06-25):** C11/Odin/Jai semantics in this Python runtime. See `conductor/product-guidelines.md` "Core Value", `conductor/code_styleguides/data_oriented_design.md` §8.5, and `conductor/code_styleguides/python.md` §17. Banned: `dict[str, Any]`, `Any`, `Optional[T]`, `hasattr()` for entity dispatch, `.get()` on known fields. Use typed `@dataclass(frozen=True, slots=True)` with explicit fields. Use `Result[T]` + `NIL_T` sentinels.
|
||||||
|
|
||||||
@@ -46,19 +46,19 @@
|
|||||||
|
|
||||||
- **src/tool_presets.py:** Extends `ToolPresetManager` to handle nested `Tool` models, weights, and global `BiasProfile` persistence within `tool_presets.toml`.
|
- **src/tool_presets.py:** Extends `ToolPresetManager` to handle nested `Tool` models, weights, and global `BiasProfile` persistence within `tool_presets.toml`.
|
||||||
- **src/mcp_client.py:** Implements the native tool dispatch (45 tools) and the `ExternalMCPManager` for orchestrating third-party Model Context Protocol servers. The typed `ToolSpec` registry now lives in `src/mcp_tool_specs.py` (`ToolSpec` dataclass + `_REGISTRY` + `tool_names()`); `mcp_client.py` re-exports `TOOL_NAMES = mcp_tool_specs.tool_names()` for backward compat. See [docs/guide_mcp_client.md](../docs/guide_mcp_client.md) for the complete 3-layer security model (Allowlist → Validate → Resolve) and tool inventory.
|
- **src/mcp_client.py:** Implements the native tool dispatch (45 tools) and the `ExternalMCPManager` for orchestrating third-party Model Context Protocol servers. The typed `ToolSpec` registry now lives in `src/mcp_tool_specs.py` (`ToolSpec` dataclass + `_REGISTRY` + `tool_names()`); `mcp_client.py` re-exports `TOOL_NAMES = mcp_tool_specs.tool_names()` for backward compat. See [docs/guide_mcp_client.md](../docs/guide_mcp_client.md) for the complete 3-layer security model (Allowlist → Validate → Resolve) and tool inventory.
|
||||||
- **StdioMCPServer:** Manages local MCP servers via asynchronous subprocess pipes (stdin/stdout/stderr).
|
- **StdioMCPServer:** Manages local MCP servers via asynchronous subprocess pipes (stdin/stdout/stderr).
|
||||||
- **RemoteMCPServer (SSE):** Provides a foundation for remote MCP integration via Server-Sent Events.
|
- **RemoteMCPServer (SSE):** Provides a foundation for remote MCP integration via Server-Sent Events.
|
||||||
- **JSON-RPC 2.0 Engine:** Handles asynchronous message routing, request/response matching, and error handling for all external MCP communication.
|
- **JSON-RPC 2.0 Engine:** Handles asynchronous message routing, request/response matching, and error handling for all external MCP communication.
|
||||||
- **AST-Based C/C++ Tools:** Provides `ts_c_get_skeleton`, `ts_cpp_get_skeleton`, `ts_c_get_code_outline`, and `ts_cpp_get_code_outline` for structural analysis of C/C++ codebases using tree-sitter.
|
- **AST-Based C/C++ Tools:** Provides `ts_c_get_skeleton`, `ts_cpp_get_skeleton`, `ts_c_get_code_outline`, and `ts_cpp_get_code_outline` for structural analysis of C/C++ codebases using tree-sitter.
|
||||||
- **AST-Based Python Tools (15):** `py_get_skeleton`, `py_get_code_outline`, `py_get_definition`, `py_update_definition`, `py_get_signature`, `py_set_signature`, `py_get_class_summary`, `py_get_var_declaration`, `py_set_var_declaration`, `py_get_hierarchy`, `py_get_docstring`, `py_get_imports`, `py_find_usages`, `py_check_syntax`, plus structural mutators `py_remove_def`, `py_add_def`, `py_move_def`, `py_region_wrap`.
|
- **AST-Based Python Tools (15):** `py_get_skeleton`, `py_get_code_outline`, `py_get_definition`, `py_update_definition`, `py_get_signature`, `py_set_signature`, `py_get_class_summary`, `py_get_var_declaration`, `py_set_var_declaration`, `py_get_hierarchy`, `py_get_docstring`, `py_get_imports`, `py_find_usages`, `py_check_syntax`, plus structural mutators `py_remove_def`, `py_add_def`, `py_move_def`, `py_region_wrap`.
|
||||||
- **Network Tools:** `web_search` (DuckDuckGo HTML scrape), `fetch_url` (HTML → text).
|
- **Network Tools:** `web_search` (DuckDuckGo HTML scrape), `fetch_url` (HTML → text).
|
||||||
- **Beads Tools (4):** `bd_list`, `bd_create`, `bd_update`, `bd_ready` — interface to the Beads/Dolt backend.
|
- **Beads Tools (4):** `bd_list`, `bd_create`, `bd_update`, `bd_ready` — interface to the Beads/Dolt backend.
|
||||||
|
|
||||||
- **src/api_hooks.py + src/api_hook_client.py:** Implements the Hook API and Python client wrapper for external automation. See [docs/guide_api_hooks.md](../docs/guide_api_hooks.md).
|
- **src/api_hooks.py + src/api_hook_client.py:** Implements the Hook API and Python client wrapper for external automation. See [docs/guide_api_hooks.md](../docs/guide_api_hooks.md).
|
||||||
- **HookServer:** FastAPI/Uvicorn server on `127.0.0.1:8999`, started by `AppController` when `--enable-test-hooks` is set. Exposes 8+ REST endpoints (`/status`, `/api/gui`, `/api/ask`, `/api/gui/mma_status`, `/api/performance`, `/api/comms`, `/api/diagnostics`).
|
- **HookServer:** FastAPI/Uvicorn server on `127.0.0.1:8999`, started by `AppController` when `--enable-test-hooks` is set. Exposes 8+ REST endpoints (`/status`, `/api/gui`, `/api/ask`, `/api/gui/mma_status`, `/api/performance`, `/api/comms`, `/api/diagnostics`).
|
||||||
- **ApiHookClient:** Python client with retry logic, health-check polling, and timeout configuration. Used by all `live_gui` tests, the WorkerPool, and external scripts.
|
- **ApiHookClient:** Python client with retry logic, health-check polling, and timeout configuration. Used by all `live_gui` tests, the WorkerPool, and external scripts.
|
||||||
- **`/api/ask` Protocol:** Non-blocking, ID-based challenge/response for synchronous HITL approvals from external contexts.
|
- **`/api/ask` Protocol:** Non-blocking, ID-based challenge/response for synchronous HITL approvals from external contexts.
|
||||||
- **`_predefined_callbacks` and `_gettable_fields`:** AppController-owned registries that the Hook API consumes to expose any App method as a `custom_callback` action.
|
- **`_predefined_callbacks` and `_gettable_fields`:** AppController-owned registries that the Hook API consumes to expose any App method as a `custom_callback` action.
|
||||||
|
|
||||||
- **src/rag_engine.py:** Core RAG implementation managing the vector store lifecycle, chunking strategies (character-based and AST-aware), and multi-provider search. Integrates with **ChromaDB** for local persistence, uses external embeddings by default, and provides an optional local embedding path via `manual_slop[local-rag]`.
|
- **src/rag_engine.py:** Core RAG implementation managing the vector store lifecycle, chunking strategies (character-based and AST-aware), and multi-provider search. Integrates with **ChromaDB** for local persistence, uses external embeddings by default, and provides an optional local embedding path via `manual_slop[local-rag]`.
|
||||||
|
|
||||||
@@ -108,16 +108,16 @@
|
|||||||
- **Manual Hot-Reload Pipeline:** Implements a `HotReloader` utility that manages module invalidation and state preservation, triggered by keyboard shortcuts (Ctrl+Alt+R) or GUI controls.
|
- **Manual Hot-Reload Pipeline:** Implements a `HotReloader` utility that manages module invalidation and state preservation, triggered by keyboard shortcuts (Ctrl+Alt+R) or GUI controls.
|
||||||
|
|
||||||
- **src/command_palette.py + src/commands.py:** Implements the keyboard-driven Command Palette (Ctrl+Shift+P). See [docs/guide_command_palette.md](../docs/guide_command_palette.md) and [docs/guide_gui_2.md](../docs/guide_gui_2.md#command-palette).
|
- **src/command_palette.py + src/commands.py:** Implements the keyboard-driven Command Palette (Ctrl+Shift+P). See [docs/guide_command_palette.md](../docs/guide_command_palette.md) and [docs/guide_gui_2.md](../docs/guide_gui_2.md#command-palette).
|
||||||
- **CommandRegistry:** Decorator-based command registration (`@registry.register`). 32+ built-in commands including `_toggle_command_palette`, `_open_command_palette`, theme switching, view presets, persona application.
|
- **CommandRegistry:** Decorator-based command registration (`@registry.register`). 32+ built-in commands including `_toggle_command_palette`, `_open_command_palette`, theme switching, view presets, persona application.
|
||||||
- **fuzzy_match:** Subsequence matching with score (consecutive bonus, start-of-word bonus, length penalty).
|
- **fuzzy_match:** Subsequence matching with score (consecutive bonus, start-of-word bonus, length penalty).
|
||||||
- **render_palette_modal:** Centered popup with input field, keyboard navigation (Up/Down/Enter/Esc), and live result filtering.
|
- **render_palette_modal:** Centered popup with input field, keyboard navigation (Up/Down/Enter/Esc), and live result filtering.
|
||||||
- **defensive try/except wrapping:** All action callbacks wrapped to prevent GUI crashes from buggy commands.
|
- **defensive try/except wrapping:** All action callbacks wrapped to prevent GUI crashes from buggy commands.
|
||||||
|
|
||||||
## Per-Source-File Deep Dives
|
## Per-Source-File Deep Dives
|
||||||
|
|
||||||
For the largest source files, consult the dedicated guides in `docs/`:
|
For the largest source files, consult the dedicated guides in `docs/`:
|
||||||
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md)** — `src/gui_2.py` (~437KB main GUI)
|
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md)** — `src/gui_2.py` (~437KB main GUI)
|
||||||
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md)** — `src/ai_client.py` (~166KB multi-provider LLM, 8 providers; inlined `VendorCapabilities` registry)
|
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md)** — `src/ai_client.py` (~166KB multi-provider LLM, 7 providers; inlined `VendorCapabilities` registry)
|
||||||
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md)** — `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB Hook API)
|
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md)** — `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB Hook API)
|
||||||
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md)** — `src/mcp_client.py` (~92KB, 45 tools; tool specs live in `src/mcp_tool_specs.py`)
|
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md)** — `src/mcp_client.py` (~92KB, 45 tools; tool specs live in `src/mcp_tool_specs.py`)
|
||||||
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md)** — `src/app_controller.py` (~240KB headless controller)
|
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md)** — `src/app_controller.py` (~240KB headless controller)
|
||||||
|
|||||||
+198
-198
@@ -42,10 +42,10 @@ Or use Python subprocess with `newline=''` to preserve line endings:
|
|||||||
```python
|
```python
|
||||||
python -c "
|
python -c "
|
||||||
with open('file.py', 'r', encoding='utf-8', newline='') as f:
|
with open('file.py', 'r', encoding='utf-8', newline='') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
content = content.replace(old, new)
|
content = content.replace(old, new)
|
||||||
with open('file.py', 'w', encoding='utf-8', newline='') as f:
|
with open('file.py', 'w', encoding='utf-8', newline='') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
"
|
"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -61,19 +61,19 @@ with open('file.py', 'w', encoding='utf-8', newline='') as f:
|
|||||||
8. **File Naming Convention (HARD RULE, added 2026-06-11):** New `src/<thing>.py` files may only be created on the user's explicit request. Helpers and sub-systems go in the parent module. E.g., AI-client-specific code goes in `src/ai_client.py`; MCP-client code goes in `src/mcp_client.py`. If you find yourself about to create a new `src/<thing>.py` file, ASK FIRST. See `AGENTS.md` "File Size and Naming Convention" for the full rule.
|
8. **File Naming Convention (HARD RULE, added 2026-06-11):** New `src/<thing>.py` files may only be created on the user's explicit request. Helpers and sub-systems go in the parent module. E.g., AI-client-specific code goes in `src/ai_client.py`; MCP-client code goes in `src/mcp_client.py`. If you find yourself about to create a new `src/<thing>.py` file, ASK FIRST. See `AGENTS.md` "File Size and Naming Convention" for the full rule.
|
||||||
8. **Mandatory Research-First Protocol:** Before reading the full content of any file over 50 lines, you MUST use `get_file_summary`, `py_get_skeleton`, `py_get_code_outline`, or `py_get_docstring` to map the architecture and identify specific target ranges. Use `get_git_diff` to understand recent changes. Use `py_find_usages` to locate where symbols are used.
|
8. **Mandatory Research-First Protocol:** Before reading the full content of any file over 50 lines, you MUST use `get_file_summary`, `py_get_skeleton`, `py_get_code_outline`, or `py_get_docstring` to map the architecture and identify specific target ranges. Use `get_git_diff` to understand recent changes. Use `py_find_usages` to locate where symbols are used.
|
||||||
9. **Architecture Documentation Fallback:** When uncertain about threading, event flow, data structures, or module interactions, consult the deep-dive docs in `docs/` (last refreshed: 2026-06-02 via the comprehensive documentation refresh track, **8 new guides added**):
|
9. **Architecture Documentation Fallback:** When uncertain about threading, event flow, data structures, or module interactions, consult the deep-dive docs in `docs/` (last refreshed: 2026-06-02 via the comprehensive documentation refresh track, **8 new guides added**):
|
||||||
- **[docs/guide_architecture.md](../docs/guide_architecture.md):** Thread domains, cross-thread patterns, AI client multi-provider (Gemini, Anthropic, DeepSeek, Gemini CLI, MiniMax), HITL Execution Clutch.
|
- **[docs/guide_architecture.md](../docs/guide_architecture.md):** Thread domains, cross-thread patterns, AI client multi-provider (Gemini, Anthropic, DeepSeek, MiniMax), HITL Execution Clutch.
|
||||||
- **[docs/guide_tools.md](../docs/guide_tools.md):** MCP Bridge 3-layer security, full 45-tool inventory, Hook API, ApiHookClient, `/api/ask` HITL protocol.
|
- **[docs/guide_tools.md](../docs/guide_tools.md):** MCP Bridge 3-layer security, full 45-tool inventory, Hook API, ApiHookClient, `/api/ask` HITL protocol.
|
||||||
- **[docs/guide_mma.md](../docs/guide_mma.md):** Ticket/Track/WorkerContext data structures, DAG engine, ConductorEngine, Tier 2/3/4 lifecycles, persona application.
|
- **[docs/guide_mma.md](../docs/guide_mma.md):** Ticket/Track/WorkerContext data structures, DAG engine, ConductorEngine, Tier 2/3/4 lifecycles, persona application.
|
||||||
- **[docs/guide_simulations.md](../docs/guide_simulations.md):** `live_gui` fixture, Puppeteer pattern, mock provider, test areas by subsystem.
|
- **[docs/guide_simulations.md](../docs/guide_simulations.md):** `live_gui` fixture, Puppeteer pattern, mock provider, test areas by subsystem.
|
||||||
- **[docs/guide_testing.md](../docs/guide_testing.md):** **NEW** — 251 test files, 5 categories, 7 conftest fixtures (`isolate_workspace`, `reset_paths`, `reset_ai_client`, `vlogger`, `kill_process_tree`, `mock_app`, `live_gui` session-scoped), Puppeteer pattern, mock provider, structural testing contract.
|
- **[docs/guide_testing.md](../docs/guide_testing.md):** **NEW** — 251 test files, 5 categories, 7 conftest fixtures (`isolate_workspace`, `reset_paths`, `reset_ai_client`, `vlogger`, `kill_process_tree`, `mock_app`, `live_gui` session-scoped), Puppeteer pattern, mock provider, structural testing contract.
|
||||||
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md):** **NEW** — `src/gui_2.py` (~437KB main GUI): App class lifecycle, ~90 module-level render functions, Multi-Viewport docks, panel registry, command palette integration, ImGuiScope context managers, hot reload support.
|
- **[docs/guide_gui_2.md](../docs/guide_gui_2.md):** **NEW** — `src/gui_2.py` (~437KB main GUI): App class lifecycle, ~90 module-level render functions, Multi-Viewport docks, panel registry, command palette integration, ImGuiScope context managers, hot reload support.
|
||||||
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md):** **NEW** — `src/ai_client.py` (~166KB): multi-provider LLM singleton (8 providers: gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), async dispatch via `asyncio.gather`, threading.local for source tier tagging, Anthropic ephemeral caching + Gemini explicit caching, Tier 4 QA error interception, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`).
|
- **[docs/guide_ai_client.md](../docs/guide_ai_client.md):** **NEW** — `src/ai_client.py` (~166KB): multi-provider LLM singleton (8 providers: gemini, anthropic, deepseek, minimax, qwen, grok, llama), async dispatch via `asyncio.gather`, threading.local for source tier tagging, Anthropic ephemeral caching + Gemini explicit caching, Tier 4 QA error interception, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`).
|
||||||
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md):** **NEW** — `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB): HookServer on `127.0.0.1:8999`, ApiHookClient wrapper, 8+ endpoints, Remote Confirmation Protocol via `/api/ask`.
|
- **[docs/guide_api_hooks.md](../docs/guide_api_hooks.md):** **NEW** — `src/api_hooks.py` + `src/api_hook_client.py` (~51KB + ~38KB): HookServer on `127.0.0.1:8999`, ApiHookClient wrapper, 8+ endpoints, Remote Confirmation Protocol via `/api/ask`.
|
||||||
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md):** **NEW** — `src/mcp_client.py` (~92KB, 45 tools; tool specs live in `src/mcp_tool_specs.py`): 3-layer security (Allowlist → Validate → Resolve), all native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), ExternalMCPManager (Stdio + SSE), JSON-RPC 2.0 engine.
|
- **[docs/guide_mcp_client.md](../docs/guide_mcp_client.md):** **NEW** — `src/mcp_client.py` (~92KB, 45 tools; tool specs live in `src/mcp_tool_specs.py`): 3-layer security (Allowlist → Validate → Resolve), all native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), ExternalMCPManager (Stdio + SSE), JSON-RPC 2.0 engine.
|
||||||
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md):** **NEW** — `src/app_controller.py` (~240KB): headless orchestrator, AppState dataclass, all subsystem managers, `_predefined_callbacks`/`_gettable_fields` Hook API registries, SyncEventQueue, headless mode.
|
- **[docs/guide_app_controller.md](../docs/guide_app_controller.md):** **NEW** — `src/app_controller.py` (~240KB): headless orchestrator, AppState dataclass, all subsystem managers, `_predefined_callbacks`/`_gettable_fields` Hook API registries, SyncEventQueue, headless mode.
|
||||||
- **[docs/guide_multi_agent_conductor.md](../docs/guide_multi_agent_conductor.md):** **NEW** — `src/multi_agent_conductor.py` + `src/dag_engine.py` (~30KB + ~11KB): TrackDAG (iterative DFS cycle detection, Kahn's topological sort), ExecutionEngine (Auto-Queue / Step Mode), MultiAgentConductor + WorkerPool (concurrency 4), the WorkerPool's internal `run_worker_lifecycle` subprocess template (NOT the deprecated `mma_exec.py`; see `docs/guide_meta_boundary.md`).
|
- **[docs/guide_multi_agent_conductor.md](../docs/guide_multi_agent_conductor.md):** **NEW** — `src/multi_agent_conductor.py` + `src/dag_engine.py` (~30KB + ~11KB): TrackDAG (iterative DFS cycle detection, Kahn's topological sort), ExecutionEngine (Auto-Queue / Step Mode), MultiAgentConductor + WorkerPool (concurrency 4), the WorkerPool's internal `run_worker_lifecycle` subprocess template (NOT the deprecated `mma_exec.py`; see `docs/guide_meta_boundary.md`).
|
||||||
- **[docs/guide_models.md](../docs/guide_models.md):** **UPDATED 2026-07-02** — `src/models.py` is now a ~1.5KB legacy re-export shim (`Metadata = TrackMetadata` alias + `PROVIDERS` lazy `__getattr__`). Data models moved to per-system files per `module_taxonomy_refactor_20260627`: `src/mma.py` (TrackMetadata, Ticket, Track, WorkerContext), `src/project_files.py` (FileItem), `src/type_aliases.py` (typed boundary + per-aggregate dataclasses), `src/mcp_tool_specs.py` (typed ToolSpec registry, 45 tools), `src/result_types.py` (Result[T], ErrorInfo). `VendorCapabilities` lives in `src/ai_client.py`.
|
- **[docs/guide_models.md](../docs/guide_models.md):** **UPDATED 2026-07-02** — `src/models.py` is now a ~1.5KB legacy re-export shim (`Metadata = TrackMetadata` alias + `PROVIDERS` lazy `__getattr__`). Data models moved to per-system files per `module_taxonomy_refactor_20260627`: `src/mma.py` (TrackMetadata, Ticket, Track, WorkerContext), `src/project_files.py` (FileItem), `src/type_aliases.py` (typed boundary + per-aggregate dataclasses), `src/mcp_tool_specs.py` (typed ToolSpec registry, 45 tools), `src/result_types.py` (Result[T], ErrorInfo). `VendorCapabilities` lives in `src/ai_client.py`.
|
||||||
- See [docs/Readme.md](../docs/Readme.md) for the full **41-guide index** covering context curation, shaders, RAG, Beads, hot reload, personas, NERV theme, workspace profiles, and command palette.
|
- See [docs/Readme.md](../docs/Readme.md) for the full **41-guide index** covering context curation, shaders, RAG, Beads, hot reload, personas, NERV theme, workspace profiles, and command palette.
|
||||||
|
|
||||||
## Task Workflow
|
## Task Workflow
|
||||||
|
|
||||||
@@ -88,146 +88,146 @@ All tasks follow a strict lifecycle:
|
|||||||
2. **Mark In Progress:** Before beginning work, edit `plan.md` and change the task from `[ ]` to `[~]`
|
2. **Mark In Progress:** Before beginning work, edit `plan.md` and change the task from `[ ]` to `[~]`
|
||||||
|
|
||||||
3. **High-Signal Research Phase:**
|
3. **High-Signal Research Phase:**
|
||||||
- **Identify Dependencies:** Use `list_directory`, `get_tree`, and `py_get_imports` to map file relations.
|
- **Identify Dependencies:** Use `list_directory`, `get_tree`, and `py_get_imports` to map file relations.
|
||||||
- **Map Architecture:** Use `py_get_code_outline` or `py_get_skeleton` on identified files to understand their structure.
|
- **Map Architecture:** Use `py_get_code_outline` or `py_get_skeleton` on identified files to understand their structure.
|
||||||
- **Audit State:** Use `py_get_code_outline` or `py_get_definition` on the target class's `__init__` method to check for existing, unused, or duplicate state variables before adding new ones.
|
- **Audit State:** Use `py_get_code_outline` or `py_get_definition` on the target class's `__init__` method to check for existing, unused, or duplicate state variables before adding new ones.
|
||||||
- **Analyze Changes:** Use `get_git_diff` if the task involves modifying recently updated code.
|
- **Analyze Changes:** Use `get_git_diff` if the task involves modifying recently updated code.
|
||||||
- **Minimize Token Burn:** Only use `read_file` with `start_line`/`end_line` for specific implementation details once target areas are identified.
|
- **Minimize Token Burn:** Only use `read_file` with `start_line`/`end_line` for specific implementation details once target areas are identified.
|
||||||
4. **Write Failing Tests (Red Phase):**
|
4. **Write Failing Tests (Red Phase):**
|
||||||
- **Pre-Delegation Checkpoint:** Before spawning a worker for dangerous or non-trivial changes, ensure your current progress is staged (`git add .`) or committed. This prevents losing iterations if a sub-agent incorrectly uses `git restore`.
|
- **Pre-Delegation Checkpoint:** Before spawning a worker for dangerous or non-trivial changes, ensure your current progress is staged (`git add .`) or committed. This prevents losing iterations if a sub-agent incorrectly uses `git restore`.
|
||||||
- **Zero-Assertion Ban:** You MUST NOT write tests that contain only `pass` or lack meaningful assertions. A test is only valid if it contains assertions that explicitly test the behavioral change and verify the failure condition.
|
- **Zero-Assertion Ban:** You MUST NOT write tests that contain only `pass` or lack meaningful assertions. A test is only valid if it contains assertions that explicitly test the behavioral change and verify the failure condition.
|
||||||
- **Code Style:** ALWAYS explicitly mention "Use exactly 1-space indentation for Python code" when prompting a sub-agent.
|
- **Code Style:** ALWAYS explicitly mention "Use exactly 1-space indentation for Python code" when prompting a sub-agent.
|
||||||
- **Delegate Test Creation:** Do NOT write test code directly. Spawn a Tier 3 Worker via the **OpenCode Task tool** with `subagent_type: "tier3-worker"` and a **surgical prompt** specifying WHERE (file:line range), WHAT (test to create), HOW (which assertions/fixtures to use), and SAFETY (thread constraints if applicable). Example: `"Write tests in tests/test_cost_tracker.py for cost_tracker.py:estimate_cost(). Test all model patterns in MODEL_PRICING dict. Assert unknown model returns 0. Use 1-space indentation."` (If repeating due to failures, set the subagent's `failure_count` higher to switch to a more capable model.) **Note:** the legacy `python scripts/mma_exec.py --role tier3-worker` invocation is DEPRECATED (see §"Conductor Token Firewalling" below); use the OpenCode Task tool instead.
|
- **Delegate Test Creation:** Do NOT write test code directly. Spawn a Tier 3 Worker via the **OpenCode Task tool** with `subagent_type: "tier3-worker"` and a **surgical prompt** specifying WHERE (file:line range), WHAT (test to create), HOW (which assertions/fixtures to use), and SAFETY (thread constraints if applicable). Example: `"Write tests in tests/test_cost_tracker.py for cost_tracker.py:estimate_cost(). Test all model patterns in MODEL_PRICING dict. Assert unknown model returns 0. Use 1-space indentation."` (If repeating due to failures, set the subagent's `failure_count` higher to switch to a more capable model.) **Note:** the legacy `python scripts/mma_exec.py --role tier3-worker` invocation is DEPRECATED (see §"Conductor Token Firewalling" below); use the OpenCode Task tool instead.
|
||||||
- Take the code generated by the Worker and apply it.
|
- Take the code generated by the Worker and apply it.
|
||||||
- **CRITICAL:** Run the tests and confirm that they fail as expected. This is the "Red" phase of TDD. Do not proceed until you have failing tests.
|
- **CRITICAL:** Run the tests and confirm that they fail as expected. This is the "Red" phase of TDD. Do not proceed until you have failing tests.
|
||||||
|
|
||||||
5. **Implement to Pass Tests (Green Phase):**
|
5. **Implement to Pass Tests (Green Phase):**
|
||||||
- **Pre-Delegation Checkpoint:** Ensure current progress is staged or committed before delegating.
|
- **Pre-Delegation Checkpoint:** Ensure current progress is staged or committed before delegating.
|
||||||
- **Code Style:** ALWAYS explicitly mention "Use exactly 1-space indentation for Python code" when prompting a sub-agent.
|
- **Code Style:** ALWAYS explicitly mention "Use exactly 1-space indentation for Python code" when prompting a sub-agent.
|
||||||
- **Delegate Implementation:** Do NOT write the implementation code directly. Spawn a Tier 3 Worker via the **OpenCode Task tool** (`subagent_type: "tier3-worker"`) with a **surgical prompt** specifying WHERE (file:line range to modify), WHAT (the specific change), HOW (which API calls, data structures, or patterns to use), and SAFETY (thread-safety constraints). Example: `"In gui_2.py _render_mma_dashboard (lines 2685-2699), extend the token usage table from 3 to 5 columns. Add 'Model' and 'Est. Cost' using imgui.table_setup_column(). Call cost_tracker.estimate_cost(model, input_tokens, output_tokens). Use 1-space indentation."` (If repeating due to failures, set `failure_count` higher to switch to a more capable model.) **Note:** the legacy `python scripts/mma_exec.py --role tier3-worker` invocation is DEPRECATED; use the OpenCode Task tool.
|
- **Delegate Implementation:** Do NOT write the implementation code directly. Spawn a Tier 3 Worker via the **OpenCode Task tool** (`subagent_type: "tier3-worker"`) with a **surgical prompt** specifying WHERE (file:line range to modify), WHAT (the specific change), HOW (which API calls, data structures, or patterns to use), and SAFETY (thread-safety constraints). Example: `"In gui_2.py _render_mma_dashboard (lines 2685-2699), extend the token usage table from 3 to 5 columns. Add 'Model' and 'Est. Cost' using imgui.table_setup_column(). Call cost_tracker.estimate_cost(model, input_tokens, output_tokens). Use 1-space indentation."` (If repeating due to failures, set `failure_count` higher to switch to a more capable model.) **Note:** the legacy `python scripts/mma_exec.py --role tier3-worker` invocation is DEPRECATED; use the OpenCode Task tool.
|
||||||
- Take the code generated by the Worker and apply it.
|
- Take the code generated by the Worker and apply it.
|
||||||
- Run the test suite again and confirm that all tests now pass. This is the "Green" phase.
|
- Run the test suite again and confirm that all tests now pass. This is the "Green" phase.
|
||||||
|
|
||||||
6. **Refactor (Optional but Recommended):**
|
6. **Refactor (Optional but Recommended):**
|
||||||
- With the safety of passing tests, refactor the implementation code and the test code to improve clarity, remove duplication, and enhance performance without changing the external behavior.
|
- With the safety of passing tests, refactor the implementation code and the test code to improve clarity, remove duplication, and enhance performance without changing the external behavior.
|
||||||
- Rerun tests to ensure they still pass after refactoring.
|
- Rerun tests to ensure they still pass after refactoring.
|
||||||
|
|
||||||
7. **Verify Coverage:** Run coverage reports using the project's chosen tools. For example, in a Python project, this might look like:
|
7. **Verify Coverage:** Run coverage reports using the project's chosen tools. For example, in a Python project, this might look like:
|
||||||
```powershell
|
```powershell
|
||||||
pytest --cov=app --cov-report=html
|
pytest --cov=app --cov-report=html
|
||||||
```
|
```
|
||||||
Target: >80% coverage for new code. The specific tools and commands will vary by language and framework.
|
Target: >80% coverage for new code. The specific tools and commands will vary by language and framework.
|
||||||
|
|
||||||
8. **Document Deviations:** If implementation differs from tech stack:
|
8. **Document Deviations:** If implementation differs from tech stack:
|
||||||
- **STOP** implementation
|
- **STOP** implementation
|
||||||
- Update `tech-stack.md` with new design
|
- Update `tech-stack.md` with new design
|
||||||
- Add dated note explaining the change
|
- Add dated note explaining the change
|
||||||
- Resume implementation
|
- Resume implementation
|
||||||
|
|
||||||
9. **Commit Code Changes:**
|
9. **Commit Code Changes:**
|
||||||
- **CRITICAL - ATOMIC PER-TASK COMMITS**: You MUST commit your changes immediately after completing and verifying a single task. Do NOT move on to the next task in the plan without committing the current one. This ensures precise tracking and safe rollback points.
|
- **CRITICAL - ATOMIC PER-TASK COMMITS**: You MUST commit your changes immediately after completing and verifying a single task. Do NOT move on to the next task in the plan without committing the current one. This ensures precise tracking and safe rollback points.
|
||||||
- Stage all code changes related to the task.
|
- Stage all code changes related to the task.
|
||||||
- Propose a clear, concise commit message e.g, `feat(ui): Create basic HTML structure for calculator`.
|
- Propose a clear, concise commit message e.g, `feat(ui): Create basic HTML structure for calculator`.
|
||||||
- Perform the commit.
|
- Perform the commit.
|
||||||
|
|
||||||
10. **Attach Task Summary with Git Notes:**
|
10. **Attach Task Summary with Git Notes:**
|
||||||
- **Step 9.1: Get Commit Hash:** Obtain the hash of the *just-completed commit* (`git log -1 --format="%H"`).
|
- **Step 9.1: Get Commit Hash:** Obtain the hash of the *just-completed commit* (`git log -1 --format="%H"`).
|
||||||
- **Step 9.2: Draft Note Content:** Create a detailed summary for the completed task. This should include the task name, a summary of changes, a list of all created/modified files, and the core "why" for the change.
|
- **Step 9.2: Draft Note Content:** Create a detailed summary for the completed task. This should include the task name, a summary of changes, a list of all created/modified files, and the core "why" for the change.
|
||||||
- **Step 9.3: Attach Note:** Use the `git notes` command to attach the summary to the commit.
|
- **Step 9.3: Attach Note:** Use the `git notes` command to attach the summary to the commit.
|
||||||
```powershell
|
```powershell
|
||||||
# The note content from the previous step is passed via the -m flag.
|
# The note content from the previous step is passed via the -m flag.
|
||||||
git notes add -m "<note content>" <commit_hash>
|
git notes add -m "<note content>" <commit_hash>
|
||||||
```
|
```
|
||||||
|
|
||||||
11. **Get and Record Task Commit SHA:**
|
11. **Get and Record Task Commit SHA:**
|
||||||
- **Step 10.1: Update Plan:** Read `plan.md`, find the line for the completed task, update its status from `[~]` to `[x]`, and append the first 7 characters of the *just-completed commit's* commit hash.
|
- **Step 10.1: Update Plan:** Read `plan.md`, find the line for the completed task, update its status from `[~]` to `[x]`, and append the first 7 characters of the *just-completed commit's* commit hash.
|
||||||
- **Step 10.2: Write Plan:** Write the updated content back to `plan.md`.
|
- **Step 10.2: Write Plan:** Write the updated content back to `plan.md`.
|
||||||
|
|
||||||
12. **Commit Plan Update:**
|
12. **Commit Plan Update:**
|
||||||
- **Action:** Stage the modified `plan.md` file.
|
- **Action:** Stage the modified `plan.md` file.
|
||||||
- **Action:** Commit this change with a descriptive message (e.g., `conductor(plan): Mark task 'Create user model' as complete`).
|
- **Action:** Commit this change with a descriptive message (e.g., `conductor(plan): Mark task 'Create user model' as complete`).
|
||||||
|
|
||||||
### Phase Completion Verification and Checkpointing Protocol
|
### Phase Completion Verification and Checkpointing Protocol
|
||||||
|
|
||||||
**Trigger:** This protocol is executed immediately after a task is completed that also concludes a phase in `plan.md`.
|
**Trigger:** This protocol is executed immediately after a task is completed that also concludes a phase in `plan.md`.
|
||||||
|
|
||||||
1. **Announce Protocol Start:** Inform the user that the phase is complete and the verification and checkpointing protocol has begun.
|
1. **Announce Protocol Start:** Inform the user that the phase is complete and the verification and checkpointing protocol has begun.
|
||||||
|
|
||||||
2. **Ensure Test Coverage for Phase Changes:**
|
2. **Ensure Test Coverage for Phase Changes:**
|
||||||
- **Step 2.1: Determine Phase Scope:** To identify the files changed in this phase, you must first find the starting point. Read `plan.md` to find the Git commit SHA of the *previous* phase's checkpoint. If no previous checkpoint exists, the scope is all changes since the first commit.
|
- **Step 2.1: Determine Phase Scope:** To identify the files changed in this phase, you must first find the starting point. Read `plan.md` to find the Git commit SHA of the *previous* phase's checkpoint. If no previous checkpoint exists, the scope is all changes since the first commit.
|
||||||
- **Step 2.2: List Changed Files:** Execute `git diff --name-only <previous_checkpoint_sha> HEAD` to get a precise list of all files modified during this phase.
|
- **Step 2.2: List Changed Files:** Execute `git diff --name-only <previous_checkpoint_sha> HEAD` to get a precise list of all files modified during this phase.
|
||||||
- **Step 2.3: Verify and Create Tests:** For each file in the list:
|
- **Step 2.3: Verify and Create Tests:** For each file in the list:
|
||||||
- **CRITICAL:** First, check its extension. Exclude non-code files (e.g., `.json`, `.md`, `.yaml`).
|
- **CRITICAL:** First, check its extension. Exclude non-code files (e.g., `.json`, `.md`, `.yaml`).
|
||||||
- For each remaining code file, verify a corresponding test file exists.
|
- For each remaining code file, verify a corresponding test file exists.
|
||||||
- If a test file is missing, you **must** create one. Before writing the test, **first, analyze other test files in the repository to determine the correct naming convention and testing style.** The new tests **must** validate the functionality described in this phase's tasks (`plan.md`).
|
- If a test file is missing, you **must** create one. Before writing the test, **first, analyze other test files in the repository to determine the correct naming convention and testing style.** The new tests **must** validate the functionality described in this phase's tasks (`plan.md`).
|
||||||
|
|
||||||
3. **Execute Automated Tests in Batches:**
|
3. **Execute Automated Tests in Batches:**
|
||||||
- Because the full suite is large (>360 tests) and contains complex UI simulations, running the entire suite frequently can lead to random timeouts or threading access violations.
|
- Because the full suite is large (>360 tests) and contains complex UI simulations, running the entire suite frequently can lead to random timeouts or threading access violations.
|
||||||
- Before execution, you **must** announce the exact shell command.
|
- Before execution, you **must** announce the exact shell command.
|
||||||
- **CRITICAL:** When verifying changes, **do not run the full suite (`pytest tests/`)**. Instead, run tests in small, targeted batches (maximum 4 test files at a time). Only use long timeouts (`--timeout=60` or `--timeout=120`) if the specific tests in the batch are known to be slow (e.g., simulation tests).
|
- **CRITICAL:** When verifying changes, **do not run the full suite (`pytest tests/`)**. Instead, run tests in small, targeted batches (maximum 4 test files at a time). Only use long timeouts (`--timeout=60` or `--timeout=120`) if the specific tests in the batch are known to be slow (e.g., simulation tests).
|
||||||
- **Example Announcement:** "I will now run the automated test suite to verify the phase. **Command:** `uv run pytest tests/test_specific_feature.py`"
|
- **Example Announcement:** "I will now run the automated test suite to verify the phase. **Command:** `uv run pytest tests/test_specific_feature.py`"
|
||||||
- Execute the announced command.
|
- Execute the announced command.
|
||||||
- If tests fail with significant output (e.g., a large traceback), **DO NOT** attempt to read the raw `stderr` directly into your context. Instead, pipe the output to a log file and **spawn a Tier 4 QA Agent via the OpenCode Task tool (`subagent_type: "tier4-qa"`)** with the error output + an explicit instruction "DO NOT fix — provide root cause analysis only". (The legacy `python scripts/mma_exec.py --role tier4-qa` invocation is DEPRECATED; use the OpenCode Task tool.)
|
- If tests fail with significant output (e.g., a large traceback), **DO NOT** attempt to read the raw `stderr` directly into your context. Instead, pipe the output to a log file and **spawn a Tier 4 QA Agent via the OpenCode Task tool (`subagent_type: "tier4-qa"`)** with the error output + an explicit instruction "DO NOT fix — provide root cause analysis only". (The legacy `python scripts/mma_exec.py --role tier4-qa` invocation is DEPRECATED; use the OpenCode Task tool.)
|
||||||
- You **must** inform the user and begin debugging using the QA Agent's summary. You may attempt to propose a fix a **maximum of two times**. If the tests still fail after your second proposed fix, you **must stop**, report the persistent failure, and ask the user for guidance.
|
- You **must** inform the user and begin debugging using the QA Agent's summary. You may attempt to propose a fix a **maximum of two times**. If the tests still fail after your second proposed fix, you **must stop**, report the persistent failure, and ask the user for guidance.
|
||||||
|
|
||||||
4. **Execute Automated API Hook Verification:**
|
4. **Execute Automated API Hook Verification:**
|
||||||
- **CRITICAL:** The Conductor agent will now automatically execute verification tasks using the application's API hooks.
|
- **CRITICAL:** The Conductor agent will now automatically execute verification tasks using the application's API hooks.
|
||||||
- The agent will announce the start of the automated verification to the user.
|
- The agent will announce the start of the automated verification to the user.
|
||||||
- It will then communicate with the application's IPC server to trigger the necessary verification functions.
|
- It will then communicate with the application's IPC server to trigger the necessary verification functions.
|
||||||
- **Result Handling:**
|
- **Result Handling:**
|
||||||
- All results (successes and failures) from the API hook invocations will be logged.
|
- All results (successes and failures) from the API hook invocations will be logged.
|
||||||
- If all automated verifications pass, the agent will inform the user and proceed to the next step (Create Checkpoint Commit).
|
- If all automated verifications pass, the agent will inform the user and proceed to the next step (Create Checkpoint Commit).
|
||||||
- If any automated verification fails, the agent will halt the workflow, present the detailed failure logs to the user, and await further instructions for debugging or remediation.
|
- If any automated verification fails, the agent will halt the workflow, present the detailed failure logs to the user, and await further instructions for debugging or remediation.
|
||||||
|
|
||||||
5. **Present Automated Verification Results and User Confirmation:**
|
5. **Present Automated Verification Results and User Confirmation:**
|
||||||
- After executing automated verification, the Conductor agent will present the results to the user.
|
- After executing automated verification, the Conductor agent will present the results to the user.
|
||||||
- If verification passed, the agent will state: "Automated verification completed successfully."
|
- If verification passed, the agent will state: "Automated verification completed successfully."
|
||||||
- If verification failed, the agent will state: "Automated verification failed. Please review the logs above for details. You may attempt to propose a fix a **maximum of two times**. If the tests still fail after your second proposed fix, you **must stop**, report the persistent failure, and ask the user for guidance."
|
- If verification failed, the agent will state: "Automated verification failed. Please review the logs above for details. You may attempt to propose a fix a **maximum of two times**. If the tests still fail after your second proposed fix, you **must stop**, report the persistent failure, and ask the user for guidance."
|
||||||
- **PAUSE** and await the user's response. Do not proceed without an explicit yes or confirmation from the user to proceed if tests pass, or guidance if tests fail.
|
- **PAUSE** and await the user's response. Do not proceed without an explicit yes or confirmation from the user to proceed if tests pass, or guidance if tests fail.
|
||||||
|
|
||||||
6. **Create Checkpoint Commit:**
|
6. **Create Checkpoint Commit:**
|
||||||
- Stage all changes. If no changes occurred in this step, proceed with an empty commit.
|
- Stage all changes. If no changes occurred in this step, proceed with an empty commit.
|
||||||
- Perform the commit with a clear and concise message (e.g., `conductor(checkpoint): Checkpoint end of Phase X`).
|
- Perform the commit with a clear and concise message (e.g., `conductor(checkpoint): Checkpoint end of Phase X`).
|
||||||
|
|
||||||
7. **Attach Auditable Verification Report using Git Notes:**
|
7. **Attach Auditable Verification Report using Git Notes:**
|
||||||
- **Step 7.1: Draft Note Content:** Create a detailed verification report including the automated test command, the manual verification steps, and the user's confirmation.
|
- **Step 7.1: Draft Note Content:** Create a detailed verification report including the automated test command, the manual verification steps, and the user's confirmation.
|
||||||
- **Step 7.2: Attach Note:** Use the `git notes` command and the full commit hash from the previous step to attach the full report to the checkpoint commit.
|
- **Step 7.2: Attach Note:** Use the `git notes` command and the full commit hash from the previous step to attach the full report to the checkpoint commit.
|
||||||
|
|
||||||
8. **Get and Record Phase Checkpoint SHA:**
|
8. **Get and Record Phase Checkpoint SHA:**
|
||||||
- **Step 8.1: Get Commit Hash:** Obtain the hash of the *just-created checkpoint commit* (`git log -1 --format="%H"`).
|
- **Step 8.1: Get Commit Hash:** Obtain the hash of the *just-created checkpoint commit* (`git log -1 --format="%H"`).
|
||||||
- **Step 8.2: Update Plan:** Read `plan.md`, find the heading for the completed phase, and append the first 7 characters of the commit hash in the format `[checkpoint: <sha>]`.
|
- **Step 8.2: Update Plan:** Read `plan.md`, find the heading for the completed phase, and append the first 7 characters of the commit hash in the format `[checkpoint: <sha>]`.
|
||||||
- **Step 8.3: Write Plan:** Write the updated content back to `plan.md`.
|
- **Step 8.3: Write Plan:** Write the updated content back to `plan.md`.
|
||||||
|
|
||||||
9. **Commit Plan Update:**
|
9. **Commit Plan Update:**
|
||||||
- **Action:** Stage the modified `plan.md` file.
|
- **Action:** Stage the modified `plan.md` file.
|
||||||
- **Action:** Commit this change with a descriptive message following the format `conductor(plan): Mark phase '<PHASE NAME>' as complete`.
|
- **Action:** Commit this change with a descriptive message following the format `conductor(plan): Mark phase '<PHASE NAME>' as complete`.
|
||||||
|
|
||||||
10. **Announce Completion:** Inform the user that the phase is complete and the checkpoint has been created, with the detailed verification report attached as a git note.
|
10. **Announce Completion:** Inform the user that the phase is complete and the checkpoint has been created, with the detailed verification report attached as a git note.
|
||||||
|
|
||||||
### Verification via API Hooks
|
### Verification via API Hooks
|
||||||
|
|
||||||
For features involving the GUI or complex internal state, unit tests are often insufficient. You MUST use the application's built-in API hooks for empirical verification:
|
For features involving the GUI or complex internal state, unit tests are often insufficient. You MUST use the application's built-in API hooks for empirical verification:
|
||||||
|
|
||||||
1. **Launch the App with Hooks:** Run the application in a separate shell with the `--enable-test-hooks` flag:
|
1. **Launch the App with Hooks:** Run the application in a separate shell with the `--enable-test-hooks` flag:
|
||||||
```powershell
|
```powershell
|
||||||
uv run python gui.py --enable-test-hooks
|
uv run python gui.py --enable-test-hooks
|
||||||
```
|
```
|
||||||
This starts the hook server on port `8999`.
|
This starts the hook server on port `8999`.
|
||||||
|
|
||||||
2. **Use the pytest `live_gui` Fixture:** For automated tests, use the session-scoped `live_gui` fixture defined in `tests/conftest.py`. This fixture handles the lifecycle (startup/shutdown) of the application with hooks enabled.
|
2. **Use the pytest `live_gui` Fixture:** For automated tests, use the session-scoped `live_gui` fixture defined in `tests/conftest.py`. This fixture handles the lifecycle (startup/shutdown) of the application with hooks enabled.
|
||||||
```python
|
```python
|
||||||
def test_my_feature(live_gui):
|
def test_my_feature(live_gui):
|
||||||
# The GUI is now running on port 8999
|
# The GUI is now running on port 8999
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
Note: pytest must be run with `uv`.
|
Note: pytest must be run with `uv`.
|
||||||
|
|
||||||
3. **Verify via ApiHookClient:** Use the `ApiHookClient` in `api_hook_client.py` to interact with the running application. It includes robust retry logic and health checks.
|
3. **Verify via ApiHookClient:** Use the `ApiHookClient` in `api_hook_client.py` to interact with the running application. It includes robust retry logic and health checks.
|
||||||
|
|
||||||
4. **Verify via REST Commands:** Use PowerShell or `curl` to send commands to the application and verify the response. For example, to check health:
|
4. **Verify via REST Commands:** Use PowerShell or `curl` to send commands to the application and verify the response. For example, to check health:
|
||||||
```powershell
|
```powershell
|
||||||
Invoke-RestMethod -Uri "http://127.0.0.1:8999/status" -Method Get
|
Invoke-RestMethod -Uri "http://127.0.0.1:8999/status" -Method Get
|
||||||
```
|
```
|
||||||
|
|
||||||
### Quality Gates
|
### Quality Gates
|
||||||
|
|
||||||
@@ -275,9 +275,9 @@ Before marking any task complete, verify:
|
|||||||
|
|
||||||
### Structural Testing Contract
|
### Structural Testing Contract
|
||||||
|
|
||||||
1. **Ban on Arbitrary Core Mocking:** Tier 3 workers are strictly forbidden from using `unittest.mock.patch` to bypass or stub core infrastructure (e.g., event queues, `ai_client` internals, threading primitives) unless explicitly authorized by the Tier 2 Tech Lead for a specific boundary test.
|
1. **Ban on Arbitrary Core Mocking:** Tier 3 workers are strictly forbidden from using `unittest.mock.patch` to bypass or stub core infrastructure (e.g., event queues, `ai_client` internals, threading primitives) unless explicitly authorized by the Tier 2 Tech Lead for a specific boundary test.
|
||||||
2. **`live_gui` Standard:** All integration and end-to-end testing must utilize the `live_gui` fixture to interact with a real instance of the application via the Hook API. Bypassing the hook server to directly mutate GUI state in tests is prohibited.
|
2. **`live_gui` Standard:** All integration and end-to-end testing must utilize the `live_gui` fixture to interact with a real instance of the application via the Hook API. Bypassing the hook server to directly mutate GUI state in tests is prohibited.
|
||||||
3. **Artifact Isolation:** All test-generated artifacts (logs, temporary workspaces, mock outputs) MUST be written to the `tests/artifacts/` or `tests/logs/` directories. These directories are git-ignored to prevent repository pollution.
|
3. **Artifact Isolation:** All test-generated artifacts (logs, temporary workspaces, mock outputs) MUST be written to the `tests/artifacts/` or `tests/logs/` directories. These directories are git-ignored to prevent repository pollution.
|
||||||
|
|
||||||
### Unit Testing
|
### Unit Testing
|
||||||
|
|
||||||
@@ -368,14 +368,14 @@ This doc describes **META-TOOLING** — the AI agent orchestration layer used by
|
|||||||
- **Mandatory Skill Activation:** As the very first step of any MMA-driven process, including track initialization and implementation phases, the agent MUST activate the `mma-orchestrator` skill (`activate_skill mma-orchestrator`) and their corresponding role's specific tier skill. This is crucial for enforcing the 4-Tier token firewall.
|
- **Mandatory Skill Activation:** As the very first step of any MMA-driven process, including track initialization and implementation phases, the agent MUST activate the `mma-orchestrator` skill (`activate_skill mma-orchestrator`) and their corresponding role's specific tier skill. This is crucial for enforcing the 4-Tier token firewall.
|
||||||
- **The Sub-Agent Bridge (OpenCode Task tool):** All meta-tooling tiered delegation is now via the OpenCode Task tool with the appropriate `subagent_type`. This is the canonical META-TOOLING mechanism; it replaces the legacy `mma_exec.py` invocation. (The application-domain MMA engine in `src/multi_agent_conductor.py` is unchanged and is documented in `docs/guide_multi_agent_conductor.md`.)
|
- **The Sub-Agent Bridge (OpenCode Task tool):** All meta-tooling tiered delegation is now via the OpenCode Task tool with the appropriate `subagent_type`. This is the canonical META-TOOLING mechanism; it replaces the legacy `mma_exec.py` invocation. (The application-domain MMA engine in `src/multi_agent_conductor.py` is unchanged and is documented in `docs/guide_multi_agent_conductor.md`.)
|
||||||
- **Model Tiers:**
|
- **Model Tiers:**
|
||||||
- **Tier 1 (Strategic/Orchestration):** `gemini-3.1-pro-preview`. Focused on product alignment, setup (`/conductor:setup`), and track initialization (`/conductor:newTrack`).
|
- **Tier 1 (Strategic/Orchestration):** `gemini-3.1-pro-preview`. Focused on product alignment, setup (`/conductor:setup`), and track initialization (`/conductor:newTrack`).
|
||||||
- **Tier 2 (Architectural/Tech Lead):** `gemini-3-flash-preview`. Focused on architectural design and track execution (`/conductor:implement`). **Note:** Tier 2 maintains persistent memory throughout a track's implementation.
|
- **Tier 2 (Architectural/Tech Lead):** `gemini-3-flash-preview`. Focused on architectural design and track execution (`/conductor:implement`). **Note:** Tier 2 maintains persistent memory throughout a track's implementation.
|
||||||
- **Tier 3 (Execution/Worker):** `gemini-2.5-flash-lite`. Used for surgical code implementation and test generation. Operates statelessly (Context Amnesia) but has access to file I/O tools.
|
- **Tier 3 (Execution/Worker):** `gemini-2.5-flash-lite`. Used for surgical code implementation and test generation. Operates statelessly (Context Amnesia) but has access to file I/O tools.
|
||||||
- **Tier 4 (Utility/QA):** `gemini-2.5-flash-lite`. Used for log summarization and error analysis. Operates statelessly (Context Amnesia) but has access to diagnostic tools.
|
- **Tier 4 (Utility/QA):** `gemini-2.5-flash-lite`. Used for log summarization and error analysis. Operates statelessly (Context Amnesia) but has access to diagnostic tools.
|
||||||
- **Tiered Delegation Protocol (OpenCode Task tool):**
|
- **Tiered Delegation Protocol (OpenCode Task tool):**
|
||||||
- **Tier 3 Worker:** invoke the Task tool with `subagent_type: "tier3-worker"`, providing a surgical prompt with WHERE/WHAT/HOW/SAFETY/COMMIT structure. **DO NOT** use `python scripts/mma_exec.py --role tier3-worker` (deprecated).
|
- **Tier 3 Worker:** invoke the Task tool with `subagent_type: "tier3-worker"`, providing a surgical prompt with WHERE/WHAT/HOW/SAFETY/COMMIT structure. **DO NOT** use `python scripts/mma_exec.py --role tier3-worker` (deprecated).
|
||||||
- **Tier 4 QA Agent:** invoke the Task tool with `subagent_type: "tier4-qa"`, providing the error output + an explicit instruction "DO NOT fix — provide root cause analysis only".
|
- **Tier 4 QA Agent:** invoke the Task tool with `subagent_type: "tier4-qa"`, providing the error output + an explicit instruction "DO NOT fix — provide root cause analysis only".
|
||||||
- **Tier 1 Orchestrator:** invoke the Task tool with `subagent_type: "tier1-orchestrator"` for track planning tasks.
|
- **Tier 1 Orchestrator:** invoke the Task tool with `subagent_type: "tier1-orchestrator"` for track planning tasks.
|
||||||
|
|
||||||
- **MMA Skill Discipline Tests:** The 5 MMA skills (`mma-orchestrator`, `mma-tier1-orchestrator`, `mma-tier2-tech-lead`, `mma-tier3-worker`, `mma-tier4-qa`) at `.agents/skills/mma-*/SKILL.md` are tested for discipline compliance via `tests/test_mma_skill_discipline.py` (per `conductor/tracks/superpowers_review_apply_high_20260705/spec.md` §3.2 and recommendation #2 from `conductor/tracks/superpowers_review_20260619/decisions.md`). The tests are static-analysis of skill documents (text-pattern assertions), not behavioral tests; they run in <5 seconds, require no live_gui or MMA execution, and verify that load-bearing rules are *prominently documented* (not just buried in prose). Future agents extending the MMA skills must also extend the tests.
|
- **MMA Skill Discipline Tests:** The 5 MMA skills (`mma-orchestrator`, `mma-tier1-orchestrator`, `mma-tier2-tech-lead`, `mma-tier3-worker`, `mma-tier4-qa`) at `.agents/skills/mma-*/SKILL.md` are tested for discipline compliance via `tests/test_mma_skill_discipline.py` (per `conductor/tracks/superpowers_review_apply_high_20260705/spec.md` §3.2 and recommendation #2 from `conductor/tracks/superpowers_review_20260619/decisions.md`). The tests are static-analysis of skill documents (text-pattern assertions), not behavioral tests; they run in <5 seconds, require no live_gui or MMA execution, and verify that load-bearing rules are *prominently documented* (not just buried in prose). Future agents extending the MMA skills must also extend the tests.
|
||||||
- **Observability:** All hierarchical interactions are recorded in `logs/mma_delegation.log` and detailed sub-agent logs are saved to `logs/agents/`. (These logs are populated by the OpenCode Task tool's logging layer.)
|
- **Observability:** All hierarchical interactions are recorded in `logs/mma_delegation.log` and detailed sub-agent logs are saved to `logs/agents/`. (These logs are populated by the OpenCode Task tool's logging layer.)
|
||||||
@@ -470,14 +470,14 @@ The pattern `push_event(...)` → `time.sleep(N)` → `assert` is a guaranteed r
|
|||||||
```python
|
```python
|
||||||
# WRONG: race condition
|
# WRONG: race condition
|
||||||
def test_open_modal(live_gui):
|
def test_open_modal(live_gui):
|
||||||
client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []})
|
client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []})
|
||||||
time.sleep(1) # hope the modal opened
|
time.sleep(1) # hope the modal opened
|
||||||
assert some_cached_value["settings_open"] is True # may be stale
|
assert some_cached_value["settings_open"] is True # may be stale
|
||||||
|
|
||||||
# RIGHT: poll-until-state-visible
|
# RIGHT: poll-until-state-visible
|
||||||
def test_open_modal(live_gui):
|
def test_open_modal(live_gui):
|
||||||
client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []})
|
client.push_event("custom_callback", {"callback": "_toggle_settings", "args": []})
|
||||||
assert client.get_value("show_settings_modal"), "settings modal did not open"
|
assert client.get_value("show_settings_modal"), "settings modal did not open"
|
||||||
```
|
```
|
||||||
|
|
||||||
This pattern surfaced 5+ times in the 2026-06-10 batch-green wave (test_reset_session_clears_mma_and_rag, test_visual_mma, test_visual_sim_gui_ux, test_gui_ux_event_routing, test_z_negative_flows). The fix is always the same: replace `time.sleep` with a poll loop bounded by a retry timeout (typically 5-20 iterations × 0.5s).
|
This pattern surfaced 5+ times in the 2026-06-10 batch-green wave (test_reset_session_clears_mma_and_rag, test_visual_mma, test_visual_sim_gui_ux, test_gui_ux_event_routing, test_z_negative_flows). The fix is always the same: replace `time.sleep` with a poll loop bounded by a retry timeout (typically 5-20 iterations × 0.5s).
|
||||||
@@ -497,9 +497,9 @@ This pattern surfaced 5+ times in the 2026-06-10 batch-green wave (test_reset_se
|
|||||||
|
|
||||||
**How to detect during TDD:**
|
**How to detect during TDD:**
|
||||||
- After modifying a class body, walk the AST and verify all expected methods are class-level:
|
- After modifying a class body, walk the AST and verify all expected methods are class-level:
|
||||||
```bash
|
```bash
|
||||||
uv run python -c "import ast; tree = ast.parse(open('src/gui_2.py').read()); [print(item.name) for n in ast.walk(tree) if isinstance(n, ast.ClassDef) and n.name == 'App' for item in n.body if isinstance(item, ast.FunctionDef)]"
|
uv run python -c "import ast; tree = ast.parse(open('src/gui_2.py').read()); [print(item.name) for n in ast.walk(tree) if isinstance(n, ast.ClassDef) and n.name == 'App' for item in n.body if isinstance(item, ast.FunctionDef)]"
|
||||||
```
|
```
|
||||||
- The skeleton via `manual-slop_py_get_skeleton` should show the method as a class member. If it's missing, it's nested.
|
- The skeleton via `manual-slop_py_get_skeleton` should show the method as a class member. If it's missing, it's nested.
|
||||||
|
|
||||||
**How to fix:** Re-indent the affected method to exactly 2-space class level. Use the file_slice tool or PyCharm-style auto-format to verify. Run the failing test to confirm.
|
**How to fix:** Re-indent the affected method to exactly 2-space class level. Use the file_slice tool or PyCharm-style auto-format to verify. Run the failing test to confirm.
|
||||||
@@ -695,42 +695,42 @@ Audit, Goals, Non-Goals, Architecture, Risks, Verification, etc.) with
|
|||||||
these specific Tier 1 rules:
|
these specific Tier 1 rules:
|
||||||
|
|
||||||
- **Current State Audit is MANDATORY** before writing requirements. Read
|
- **Current State Audit is MANDATORY** before writing requirements. Read
|
||||||
the actual code with MCP tools (`get_file_slice`, `py_get_skeleton`,
|
the actual code with MCP tools (`get_file_slice`, `py_get_skeleton`,
|
||||||
`py_get_definition`, `py_find_usages`). Document existing
|
`py_get_definition`, `py_find_usages`). Document existing
|
||||||
implementations with `file:line` references in a "Current State
|
implementations with `file:line` references in a "Current State
|
||||||
Audit" section. Failure to audit = track failure.
|
Audit" section. Failure to audit = track failure.
|
||||||
- **Frame requirements as GAPS, not features.** "The existing X
|
- **Frame requirements as GAPS, not features.** "The existing X
|
||||||
(file.py:L100-200) has Y; this track fills the gap" — not "Build
|
(file.py:L100-200) has Y; this track fills the gap" — not "Build
|
||||||
feature Z".
|
feature Z".
|
||||||
- **Write worker-ready tasks** in the plan. Each plan task must be
|
- **Write worker-ready tasks** in the plan. Each plan task must be
|
||||||
executable by a Tier 3 worker. The Tier 1 does NOT execute the
|
executable by a Tier 3 worker. The Tier 1 does NOT execute the
|
||||||
plan; the Tier 1 writes it for a Tier 3 to execute.
|
plan; the Tier 1 writes it for a Tier 3 to execute.
|
||||||
- **Reference architecture docs** (`docs/guide_*.md`,
|
- **Reference architecture docs** (`docs/guide_*.md`,
|
||||||
`conductor/code_styleguides/*.md`) in every spec. Every requirement
|
`conductor/code_styleguides/*.md`) in every spec. Every requirement
|
||||||
must point to the existing pattern it follows (or the new pattern it
|
must point to the existing pattern it follows (or the new pattern it
|
||||||
establishes).
|
establishes).
|
||||||
- **For bug fix tracks: Root Cause Analysis** is mandatory. Read the
|
- **For bug fix tracks: Root Cause Analysis** is mandatory. Read the
|
||||||
code, trace the data flow, list specific root cause candidates.
|
code, trace the data flow, list specific root cause candidates.
|
||||||
Don't ship "I tried X, the test still failed, here's a 200-line
|
Don't ship "I tried X, the test still failed, here's a 200-line
|
||||||
report".
|
report".
|
||||||
|
|
||||||
### 3. Metadata format
|
### 3. Metadata format
|
||||||
|
|
||||||
The `metadata.json` follows the standard schema. Specific Tier 1 rules:
|
The `metadata.json` follows the standard schema. Specific Tier 1 rules:
|
||||||
|
|
||||||
- `scope.new_files` / `scope.modified_files` / `scope.deleted_files`
|
- `scope.new_files` / `scope.modified_files` / `scope.deleted_files`
|
||||||
are the file-level scope. No "lines of code changed" estimates.
|
are the file-level scope. No "lines of code changed" estimates.
|
||||||
- `regressions_and_pre_existing_failures` is a list, not a count.
|
- `regressions_and_pre_existing_failures` is a list, not a count.
|
||||||
- `pre_existing_failures_remaining` MUST be `[]` for the track to be
|
- `pre_existing_failures_remaining` MUST be `[]` for the track to be
|
||||||
marked complete.
|
marked complete.
|
||||||
- `deferred_to_followup_tracks` is a list of followup items with
|
- `deferred_to_followup_tracks` is a list of followup items with
|
||||||
title + description + track_status. No "estimated effort".
|
title + description + track_status. No "estimated effort".
|
||||||
- `estimated_effort` field uses `method: "scope (per workflow.md §Tier
|
- `estimated_effort` field uses `method: "scope (per workflow.md §Tier
|
||||||
1 Track Initialization Rules). NO day estimates."` and a per-phase
|
1 Track Initialization Rules). NO day estimates."` and a per-phase
|
||||||
`scope` summary (e.g., `phase_1: "1 task: investigation"`).
|
`scope` summary (e.g., `phase_1: "1 task: investigation"`).
|
||||||
- `risk_register` entries use scope-relative likelihood ("medium"
|
- `risk_register` entries use scope-relative likelihood ("medium"
|
||||||
means "the implementation may be larger than the spec suggests"),
|
means "the implementation may be larger than the spec suggests"),
|
||||||
not time-relative ("takes longer than 2 days").
|
not time-relative ("takes longer than 2 days").
|
||||||
|
|
||||||
### 4. Plan format
|
### 4. Plan format
|
||||||
|
|
||||||
@@ -738,11 +738,11 @@ The `plan.md` follows the standard TDD red-first template. Specific
|
|||||||
Tier 1 rules:
|
Tier 1 rules:
|
||||||
|
|
||||||
- Each task has WHERE / WHAT / HOW / SAFETY / COMMIT / GIT NOTE
|
- Each task has WHERE / WHAT / HOW / SAFETY / COMMIT / GIT NOTE
|
||||||
fields. Tasks are NOT grouped by "day" or "hour".
|
fields. Tasks are NOT grouped by "day" or "hour".
|
||||||
- Phase headers describe the WORK, not the TIME. ("Phase 1:
|
- Phase headers describe the WORK, not the TIME. ("Phase 1:
|
||||||
Investigation" not "Phase 1: Day 1").
|
Investigation" not "Phase 1: Day 1").
|
||||||
- The plan is read by a Tier 3 worker; the Tier 1 never executes it
|
- The plan is read by a Tier 3 worker; the Tier 1 never executes it
|
||||||
themselves.
|
themselves.
|
||||||
|
|
||||||
### 5. The "Reasonable effort" guard
|
### 5. The "Reasonable effort" guard
|
||||||
|
|
||||||
@@ -770,8 +770,8 @@ Every track's `conductor/tracks/<track_id>/state.toml` should follow this struct
|
|||||||
[meta]
|
[meta]
|
||||||
track_id = "<track_id>"
|
track_id = "<track_id>"
|
||||||
name = "<Human-Readable Name>"
|
name = "<Human-Readable Name>"
|
||||||
status = "active" # active | completed
|
status = "active" # active | completed
|
||||||
current_phase = 0 # 0 = pre-Phase 1; 1..N = in Phase N; "complete" if all phases done
|
current_phase = 0 # 0 = pre-Phase 1; 1..N = in Phase N; "complete" if all phases done
|
||||||
last_updated = "<YYYY-MM-DD>"
|
last_updated = "<YYYY-MM-DD>"
|
||||||
|
|
||||||
[blocked_by]
|
[blocked_by]
|
||||||
@@ -818,9 +818,9 @@ When the implementing agent encounters a decision not covered by the plan:
|
|||||||
|
|
||||||
1. **If the decision is purely cosmetic** (e.g., variable naming, comment placement, exact spacing): pick the option that matches the surrounding code style. Document the choice in the commit message.
|
1. **If the decision is purely cosmetic** (e.g., variable naming, comment placement, exact spacing): pick the option that matches the surrounding code style. Document the choice in the commit message.
|
||||||
2. **If the decision affects the architecture** (e.g., the spec's data model doesn't fit the code; the plan's approach doesn't compile; an external library doesn't behave as expected): **STOP. Do not commit. Report to the Tier 2 Tech Lead.** The lead will either:
|
2. **If the decision affects the architecture** (e.g., the spec's data model doesn't fit the code; the plan's approach doesn't compile; an external library doesn't behave as expected): **STOP. Do not commit. Report to the Tier 2 Tech Lead.** The lead will either:
|
||||||
- Update the spec to match the new constraint
|
- Update the spec to match the new constraint
|
||||||
- Add a clarifying task to the plan
|
- Add a clarifying task to the plan
|
||||||
- Defer the work to a follow-up track
|
- Defer the work to a follow-up track
|
||||||
3. **If the decision is a regression** (e.g., the plan's code works but introduces a known bug, or fails a test the plan didn't anticipate): **STOP and report.** Don't ship a known regression to save time. The lead will decide whether to fix forward or roll back.
|
3. **If the decision is a regression** (e.g., the plan's code works but introduces a known bug, or fails a test the plan didn't anticipate): **STOP and report.** Don't ship a known regression to save time. The lead will decide whether to fix forward or roll back.
|
||||||
|
|
||||||
**The principle: small decisions, decide yourself. Large decisions, escalate.** The boundary is "does this decision require a new spec or plan update?"
|
**The principle: small decisions, decide yourself. Large decisions, escalate.** The boundary is "does this decision require a new spec or plan update?"
|
||||||
@@ -913,20 +913,20 @@ This section extends the existing workflow with the patterns surfaced by the `na
|
|||||||
|
|
||||||
```
|
```
|
||||||
- [ ] tests/test_knowledge_store.py: 5+ tests for the 7-category schema
|
- [ ] tests/test_knowledge_store.py: 5+ tests for the 7-category schema
|
||||||
- [ ] parse_harvest_json: 7 categories; rows must be lists
|
- [ ] parse_harvest_json: 7 categories; rows must be lists
|
||||||
- [ ] parse_harvest_json: rejects prose
|
- [ ] parse_harvest_json: rejects prose
|
||||||
- [ ] parse_harvest_json: tolerates ```json ... ``` code-fence
|
- [ ] parse_harvest_json: tolerates ```json ... ``` code-fence
|
||||||
- [ ] parse_harvest_json: rejects non-dict payloads
|
- [ ] parse_harvest_json: rejects non-dict payloads
|
||||||
- [ ] regenerate_digest: 4KB cap; truncation with note
|
- [ ] regenerate_digest: 4KB cap; truncation with note
|
||||||
- [ ] tests/test_knowledge_harvest.py: 8+ tests for the pipeline
|
- [ ] tests/test_knowledge_harvest.py: 8+ tests for the pipeline
|
||||||
- [ ] classify (live/user-kept/prune/harvest/keep)
|
- [ ] classify (live/user-kept/prune/harvest/keep)
|
||||||
- [ ] merge_harvest per category
|
- [ ] merge_harvest per category
|
||||||
- [ ] per-file knowledge: existing-file branch
|
- [ ] per-file knowledge: existing-file branch
|
||||||
- [ ] per-file knowledge: missing-file branch
|
- [ ] per-file knowledge: missing-file branch
|
||||||
- [ ] ledger dedup (sha256-of-content)
|
- [ ] ledger dedup (sha256-of-content)
|
||||||
- [ ] retry budget (2 attempts)
|
- [ ] retry budget (2 attempts)
|
||||||
- [ ] "too-large" budget guard (1MB)
|
- [ ] "too-large" budget guard (1MB)
|
||||||
- [ ] "delete to turn off" regeneration
|
- [ ] "delete to turn off" regeneration
|
||||||
```
|
```
|
||||||
|
|
||||||
### The cache ordering TDD protocol
|
### The cache ordering TDD protocol
|
||||||
@@ -935,17 +935,17 @@ This section extends the existing workflow with the patterns surfaced by the `na
|
|||||||
|
|
||||||
```
|
```
|
||||||
- [ ] tests/test_aggregate_caching.py: the byte-comparison test
|
- [ ] tests/test_aggregate_caching.py: the byte-comparison test
|
||||||
- [ ] first N chars are identical across turns of the same discussion
|
- [ ] first N chars are identical across turns of the same discussion
|
||||||
- [ ] N = aggregate.stable_prefix_length(ctrl)
|
- [ ] N = aggregate.stable_prefix_length(ctrl)
|
||||||
- [ ] failure modes: new layer in wrong position, volatile input leak
|
- [ ] failure modes: new layer in wrong position, volatile input leak
|
||||||
- [ ] tests/test_cache_state.py: 3+ tests for the cache state machine
|
- [ ] tests/test_cache_state.py: 3+ tests for the cache state machine
|
||||||
- [ ] per-provider TTL defaults
|
- [ ] per-provider TTL defaults
|
||||||
- [ ] DiscussionCacheState lifecycle
|
- [ ] DiscussionCacheState lifecycle
|
||||||
- [ ] invalidate + regeneration
|
- [ ] invalidate + regeneration
|
||||||
- [ ] tests/test_gui_caching.py: 3+ live_gui tests for the "Caching" panel
|
- [ ] tests/test_gui_caching.py: 3+ live_gui tests for the "Caching" panel
|
||||||
- [ ] panel renders provider summaries
|
- [ ] panel renders provider summaries
|
||||||
- [ ] invalidate button
|
- [ ] invalidate button
|
||||||
- [ ] per-discussion disable/enable
|
- [ ] per-discussion disable/enable
|
||||||
```
|
```
|
||||||
|
|
||||||
### The compaction TDD protocol
|
### The compaction TDD protocol
|
||||||
@@ -954,16 +954,16 @@ This section extends the existing workflow with the patterns surfaced by the `na
|
|||||||
|
|
||||||
```
|
```
|
||||||
- [ ] tests/test_run_discussion_compaction.py: 10+ tests
|
- [ ] tests/test_run_discussion_compaction.py: 10+ tests
|
||||||
- [ ] compact preserves decisions
|
- [ ] compact preserves decisions
|
||||||
- [ ] compact preserves constraints
|
- [ ] compact preserves constraints
|
||||||
- [ ] compact preserves failures
|
- [ ] compact preserves failures
|
||||||
- [ ] compact preserves artifact refs
|
- [ ] compact preserves artifact refs
|
||||||
- [ ] compact removes duplicates
|
- [ ] compact removes duplicates
|
||||||
- [ ] compact replaces chronology with state
|
- [ ] compact replaces chronology with state
|
||||||
- [ ] compact is substantially smaller
|
- [ ] compact is substantially smaller
|
||||||
- [ ] compact preserves capability
|
- [ ] compact preserves capability
|
||||||
- [ ] compact returns 12-section structure
|
- [ ] compact returns 12-section structure
|
||||||
- [ ] compact continues until self-review passes
|
- [ ] compact continues until self-review passes
|
||||||
```
|
```
|
||||||
|
|
||||||
### The RAG discipline TDD protocol
|
### The RAG discipline TDD protocol
|
||||||
@@ -972,10 +972,10 @@ This section extends the existing workflow with the patterns surfaced by the `na
|
|||||||
|
|
||||||
```
|
```
|
||||||
- [ ] tests/test_rag_discipline.py: 4+ tests
|
- [ ] tests/test_rag_discipline.py: 4+ tests
|
||||||
- [ ] RAG disabled: no {rag-context} block
|
- [ ] RAG disabled: no {rag-context} block
|
||||||
- [ ] RAG results have provenance (file path + chunk)
|
- [ ] RAG results have provenance (file path + chunk)
|
||||||
- [ ] RAG results do not mutate disc_entries
|
- [ ] RAG results do not mutate disc_entries
|
||||||
- [ ] RAG failure returns empty (graceful)
|
- [ ] RAG failure returns empty (graceful)
|
||||||
```
|
```
|
||||||
|
|
||||||
See `conductor/code_styleguides/knowledge_artifacts.md`, `cache_friendly_context.md`, `rag_integration_discipline.md` for the canonical styleguides.
|
See `conductor/code_styleguides/knowledge_artifacts.md`, `cache_friendly_context.md`, `rag_integration_discipline.md` for the canonical styleguides.
|
||||||
|
|||||||
+129
-129
@@ -14,7 +14,7 @@ This documentation suite provides comprehensive technical reference for the Manu
|
|||||||
|
|
||||||
| Guide | Contents |
|
| Guide | Contents |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [Architecture](guide_architecture.md) | Thread domains (GUI Main, Asyncio Worker, HookServer, Ad-hoc), cross-thread data structures (AsyncEventQueue, Guarded Lists, Condition-Variable Dialogs), event system (EventEmitter, SyncEventQueue, UserRequestEvent), application lifetime (boot sequence, shutdown sequence), task pipeline (producer-consumer synchronization), Execution Clutch (HITL mechanism with ConfirmDialog, MMAApprovalDialog, MMASpawnApprovalDialog), AI client multi-provider architecture (Gemini SDK, Anthropic, DeepSeek, Gemini CLI, MiniMax), Anthropic/Gemini caching strategies (4-breakpoint system, server-side TTL), context refresh mechanism (mtime-based file re-reading, diff injection), comms logging (JSON-L format), state machines (ai_status, HITL dialog state) |
|
| [Architecture](guide_architecture.md) | Thread domains (GUI Main, Asyncio Worker, HookServer, Ad-hoc), cross-thread data structures (AsyncEventQueue, Guarded Lists, Condition-Variable Dialogs), event system (EventEmitter, SyncEventQueue, UserRequestEvent), application lifetime (boot sequence, shutdown sequence), task pipeline (producer-consumer synchronization), Execution Clutch (HITL mechanism with ConfirmDialog, MMAApprovalDialog, MMASpawnApprovalDialog), AI client multi-provider architecture (Gemini SDK, Anthropic, DeepSeek, MiniMax), Anthropic/Gemini caching strategies (4-breakpoint system, server-side TTL), context refresh mechanism (mtime-based file re-reading, diff injection), comms logging (JSON-L format), state machines (ai_status, HITL dialog state) |
|
||||||
| [Meta-Boundary](guide_meta_boundary.md) | Explicit distinction between the Application's domain (Strict HITL — `gui_2.py`, `ai_client.py`, `multi_agent_conductor.py`, `dag_engine.py`) and the **Meta-Tooling** domain (the OpenCode Task tool with `.opencode/agents/*` tier prompts, `.gemini/`, `.claude/`, plus the legacy `scripts/mma_exec.py` / `scripts/claude_mma_exec.py` / `scripts/tool_call.py` / `scripts/mcp_server.py` for backward compatibility), preventing feature bleed and safety bypasses via shared bridges like `mcp_client.py`. Documents the Inter-Domain Bridges (`cli_tool_bridge.py`, `claude_tool_bridge.py`) and the `GEMINI_CLI_HOOK_CONTEXT` environment variable. **Note (2026-06-27):** the legacy `mma_exec.py` / `claude_mma_exec.py` are DEPRECATED for meta-tooling sub-agent delegation; the OpenCode Task tool is the canonical mechanism. |
|
| [Meta-Boundary](guide_meta_boundary.md) | Explicit distinction between the Application's domain (Strict HITL — `gui_2.py`, `ai_client.py`, `multi_agent_conductor.py`, `dag_engine.py`) and the **Meta-Tooling** domain (the OpenCode Task tool with `.opencode/agents/*` tier prompts, `.gemini/`, `.claude/`, plus the legacy `scripts/mma_exec.py` / `scripts/claude_mma_exec.py` / `scripts/tool_call.py` / `scripts/mcp_server.py` for backward compatibility), preventing feature bleed and safety bypasses via shared bridges like `mcp_client.py`. Documents the Inter-Domain Bridges (`cli_tool_bridge.py`, `claude_tool_bridge.py`) and the `GEMINI_CLI_HOOK_CONTEXT` environment variable. **Note (2026-06-27):** the legacy `mma_exec.py` / `claude_mma_exec.py` are DEPRECATED for meta-tooling sub-agent delegation; the OpenCode Task tool is the canonical mechanism. |
|
||||||
| [Tools & IPC](guide_tools.md) | MCP Bridge 3-layer security model (Allowlist Construction, Path Validation, Resolution Gate), all 45 MCP tool signatures (plus `run_powershell` from `src/shell_runner.py`, for a canonical 46 in `models.AGENT_TOOL_NAMES`) with parameters and behavior (File I/O, AST-Based, Analysis, Network, Runtime, Beads), Hook API GET/POST endpoints with request/response formats, ApiHookClient method reference (Connection Methods, State Query Methods, GUI Manipulation Methods, Polling Methods, HITL Method), `/api/ask` synchronous HITL protocol (blocking request-response over HTTP), session logging (comms.log, toolcalls.log, apihooks.log, clicalls.log, scripts/generated/*.ps1), shell runner (mcp_env.toml configuration, run_powershell function with 60s timeout, qa_callback and patch_callback integration for Tier 4 QA + auto-patch) |
|
| [Tools & IPC](guide_tools.md) | MCP Bridge 3-layer security model (Allowlist Construction, Path Validation, Resolution Gate), all 45 MCP tool signatures (plus `run_powershell` from `src/shell_runner.py`, for a canonical 46 in `models.AGENT_TOOL_NAMES`) with parameters and behavior (File I/O, AST-Based, Analysis, Network, Runtime, Beads), Hook API GET/POST endpoints with request/response formats, ApiHookClient method reference (Connection Methods, State Query Methods, GUI Manipulation Methods, Polling Methods, HITL Method), `/api/ask` synchronous HITL protocol (blocking request-response over HTTP), session logging (comms.log, toolcalls.log, apihooks.log, clicalls.log, scripts/generated/*.ps1), shell runner (mcp_env.toml configuration, run_powershell function with 60s timeout, qa_callback and patch_callback integration for Tier 4 QA + auto-patch) |
|
||||||
| [MMA Orchestration](guide_mma.md) | Ticket/Track/WorkerContext data structures (from `models.py`), DAG engine (TrackDAG class with cycle detection, topological sort, cascade_blocks; ExecutionEngine class with tick-based state machine), ConductorEngine execution loop (run method, _push_state for state broadcast, parse_json_tickets for ingestion), Tier 2 ticket generation (generate_tickets, topological_sort), Tier 3 worker lifecycle (run_worker_lifecycle with Context Amnesia, AST skeleton injection, HITL clutch integration via confirm_spawn and confirm_execution), Tier 4 QA integration (run_tier4_analysis, run_tier4_patch_callback), token firewalling (tier_usage tracking, model escalation), track state persistence (TrackState, save_track_state, load_track_state, get_all_tracks) |
|
| [MMA Orchestration](guide_mma.md) | Ticket/Track/WorkerContext data structures (from `models.py`), DAG engine (TrackDAG class with cycle detection, topological sort, cascade_blocks; ExecutionEngine class with tick-based state machine), ConductorEngine execution loop (run method, _push_state for state broadcast, parse_json_tickets for ingestion), Tier 2 ticket generation (generate_tickets, topological_sort), Tier 3 worker lifecycle (run_worker_lifecycle with Context Amnesia, AST skeleton injection, HITL clutch integration via confirm_spawn and confirm_execution), Tier 4 QA integration (run_tier4_analysis, run_tier4_patch_callback), token firewalling (tier_usage tracking, model escalation), track state persistence (TrackState, save_track_state, load_track_state, get_all_tracks) |
|
||||||
@@ -31,12 +31,12 @@ This documentation suite provides comprehensive technical reference for the Manu
|
|||||||
| [Testing](guide_testing.md) | 322 test files, 5 test categories (unit, integration, live_gui, perf, simulation), 7 conftest fixtures (`isolate_workspace`, `reset_paths`, `reset_ai_client`, `vlogger`, `kill_process_tree`, `mock_app`, `live_gui` session-scoped), Hook API testing pattern, Puppeteer pattern for MMA simulation, mock provider strategy, opt-in clean install test, opt-in docker test, coverage targets, anti-patterns (no arbitrary core mocking, artifact isolation to `tests/artifacts/`), early-render C-level crash pattern (`_ini_capture_ready` defer-not-catch for `imgui.save_ini_settings_to_memory`), live_gui authoring contract (wait-for-ready pattern over `time.sleep`, narrow test paths over kitchen-sink `render_main_interface` mocks), test-ordering sensitivity (session-scoped fixture) |
|
| [Testing](guide_testing.md) | 322 test files, 5 test categories (unit, integration, live_gui, perf, simulation), 7 conftest fixtures (`isolate_workspace`, `reset_paths`, `reset_ai_client`, `vlogger`, `kill_process_tree`, `mock_app`, `live_gui` session-scoped), Hook API testing pattern, Puppeteer pattern for MMA simulation, mock provider strategy, opt-in clean install test, opt-in docker test, coverage targets, anti-patterns (no arbitrary core mocking, artifact isolation to `tests/artifacts/`), early-render C-level crash pattern (`_ini_capture_ready` defer-not-catch for `imgui.save_ini_settings_to_memory`), live_gui authoring contract (wait-for-ready pattern over `time.sleep`, narrow test paths over kitchen-sink `render_main_interface` mocks), test-ordering sensitivity (session-scoped fixture) |
|
||||||
| [Themes](guide_themes.md) | TOML-based theming system: file layout (`themes/<name>.toml` global + `project_themes.toml` per-project), schema (`syntax_palette` + `[colors]` table with `imgui.Col_` snake_case keys), 4-syntax-palette upstream limit (`imgui-bundle` ships `dark`/`light`/`mariana`/`retro_blue` only), built-in vs TOML palette dispatch, `load_themes_from_disk` / `get_syntax_palette_for_theme` / `apply_syntax_palette` public API, hot-reload behavior, color-callable convention (`C_LBL()` / `C_VAL()` for theme-aware helpers) |
|
| [Themes](guide_themes.md) | TOML-based theming system: file layout (`themes/<name>.toml` global + `project_themes.toml` per-project), schema (`syntax_palette` + `[colors]` table with `imgui.Col_` snake_case keys), 4-syntax-palette upstream limit (`imgui-bundle` ships `dark`/`light`/`mariana`/`retro_blue` only), built-in vs TOML palette dispatch, `load_themes_from_disk` / `get_syntax_palette_for_theme` / `apply_syntax_palette` public API, hot-reload behavior, color-callable convention (`C_LBL()` / `C_VAL()` for theme-aware helpers) |
|
||||||
| [GUI Main](guide_gui_2.md) | `src/gui_2.py` reference: App class lifecycle, ~90 module-level render functions (UI Delegation Pattern), immgui immediate-mode rendering, Multi-Viewport docks, panel registry, command palette integration, ImGuiScope context managers, hot reload support, key bindings (Ctrl+Shift+P, Ctrl+Alt+R, Ctrl+Z/Y), `_capture_workspace_profile` defer-not-catch pattern (line 813-841, `_ini_capture_ready` flag for `imgui.save_ini_settings_to_memory`), theme color-callable pattern (e.g. `DIR_COLORS`/`KIND_COLORS` dicts store `C_VAL` not `C_VAL()` and are called at use site), `__getattr__` ui_ attrs hasattr-guard (bcdc26d0 silent-None fix), `_LazyModule` / `_FiledialogStub` lazy import proxies, `startup_profiler` + `render_warmup_status_indicator` integration, native `_detect_refresh_rate_win32` (ctypes.EnumDisplaySettingsW) |
|
| [GUI Main](guide_gui_2.md) | `src/gui_2.py` reference: App class lifecycle, ~90 module-level render functions (UI Delegation Pattern), immgui immediate-mode rendering, Multi-Viewport docks, panel registry, command palette integration, ImGuiScope context managers, hot reload support, key bindings (Ctrl+Shift+P, Ctrl+Alt+R, Ctrl+Z/Y), `_capture_workspace_profile` defer-not-catch pattern (line 813-841, `_ini_capture_ready` flag for `imgui.save_ini_settings_to_memory`), theme color-callable pattern (e.g. `DIR_COLORS`/`KIND_COLORS` dicts store `C_VAL` not `C_VAL()` and are called at use site), `__getattr__` ui_ attrs hasattr-guard (bcdc26d0 silent-None fix), `_LazyModule` / `_FiledialogStub` lazy import proxies, `startup_profiler` + `render_warmup_status_indicator` integration, native `_detect_refresh_rate_win32` (ctypes.EnumDisplaySettingsW) |
|
||||||
| [AI Client](guide_ai_client.md) | `src/ai_client.py` reference: multi-provider LLM singleton (8 providers: gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), async dispatch with `asyncio.gather`, threading.local for source tier tagging, context caching (Anthropic ephemeral + Gemini explicit), system prompt assembly, error interception for Tier 4 QA, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`), `Result[str]`-returning `send()` public API |
|
| [AI Client](guide_ai_client.md) | `src/ai_client.py` reference: multi-provider LLM singleton (7 providers: gemini, anthropic, deepseek, minimax, qwen, grok, llama), async dispatch with `asyncio.gather`, threading.local for source tier tagging, context caching (Anthropic ephemeral + Gemini explicit), system prompt assembly, error interception for Tier 4 QA, inlined `VendorCapabilities` registry (moved from the deleted `src/vendor_capabilities.py`), `Result[str]`-returning `send()` public API |
|
||||||
| [API Hooks](guide_api_hooks.md) | `src/api_hooks.py` + `src/api_hook_client.py` reference: HookServer on `127.0.0.1:8999`, ApiHookClient Python wrapper, 8+ endpoints (`/status`, `/api/gui`, `/api/ask`, `/api/gui/mma_status`, `/api/performance`, `/api/comms`, `/api/diagnostics`), Remote Confirmation Protocol via `/api/ask` (synchronous blocking HITL), `custom_callback` action for invoking any registered App method |
|
| [API Hooks](guide_api_hooks.md) | `src/api_hooks.py` + `src/api_hook_client.py` reference: HookServer on `127.0.0.1:8999`, ApiHookClient Python wrapper, 8+ endpoints (`/status`, `/api/gui`, `/api/ask`, `/api/gui/mma_status`, `/api/performance`, `/api/comms`, `/api/diagnostics`), Remote Confirmation Protocol via `/api/ask` (synchronous blocking HITL), `custom_callback` action for invoking any registered App method |
|
||||||
| [MCP Client](guide_mcp_client.md) | `src/mcp_client.py` reference: 45 native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), 3-layer security model (Allowlist Construction, Path Validation, Resolution Gate), `dispatch()`/`async_dispatch()` entry points, ExternalMCPManager for external MCP servers (Stdio + SSE), JSON-RPC 2.0 engine, public API, configuration |
|
| [MCP Client](guide_mcp_client.md) | `src/mcp_client.py` reference: 45 native tools (File I/O, Python AST, C/C++ AST, Analysis, Network, Runtime, Beads), 3-layer security model (Allowlist Construction, Path Validation, Resolution Gate), `dispatch()`/`async_dispatch()` entry points, ExternalMCPManager for external MCP servers (Stdio + SSE), JSON-RPC 2.0 engine, public API, configuration |
|
||||||
| [App Controller](guide_app_controller.md) | `src/app_controller.py` reference: headless orchestrator owning AppState and all subsystem managers (PresetManager, PersonaManager, ContextPresetManager, ToolPresetManager, ToolBiasEngine, RAGEngine, HistoryManager, WorkspaceManager, HookServer, HotReloader, PathManager), `_predefined_callbacks` and `_gettable_fields` registries for Hook API, SyncEventQueue bridge, preset/persona/context coordination, headless mode |
|
| [App Controller](guide_app_controller.md) | `src/app_controller.py` reference: headless orchestrator owning AppState and all subsystem managers (PresetManager, PersonaManager, ContextPresetManager, ToolPresetManager, ToolBiasEngine, RAGEngine, HistoryManager, WorkspaceManager, HookServer, HotReloader, PathManager), `_predefined_callbacks` and `_gettable_fields` registries for Hook API, SyncEventQueue bridge, preset/persona/context coordination, headless mode |
|
||||||
| [MMA Engine](guide_multi_agent_conductor.md) | `src/multi_agent_conductor.py` + `src/dag_engine.py` reference: TrackDAG with cycle detection (iterative DFS) and topological sort (Kahn's variant), ExecutionEngine with Auto-Queue / Step Mode state machine, MultiAgentConductor with WorkerPool (configurable concurrency, default 4), the WorkerPool's internal `run_worker_lifecycle` subprocess template (NOT the meta-tooling `mma_exec.py` — that's deprecated; see `guide_meta_boundary.md`), parse_plan_md utility (now in `src/mma.py`), Beads mode delegation |
|
| [MMA Engine](guide_multi_agent_conductor.md) | `src/multi_agent_conductor.py` + `src/dag_engine.py` reference: TrackDAG with cycle detection (iterative DFS) and topological sort (Kahn's variant), ExecutionEngine with Auto-Queue / Step Mode state machine, MultiAgentConductor with WorkerPool (configurable concurrency, default 4), the WorkerPool's internal `run_worker_lifecycle` subprocess template (NOT the meta-tooling `mma_exec.py` — that's deprecated; see `guide_meta_boundary.md`), parse_plan_md utility (now in `src/mma.py`), Beads mode delegation |
|
||||||
| [Data Models](guide_models.md) | `src/models.py` is now a ~1.5KB legacy re-export shim (`Metadata = TrackMetadata` alias + `PROVIDERS` lazy `__getattr__`). Data models moved to per-system files per `module_taxonomy_refactor_20260627`: `src/mma.py` (TrackMetadata, Ticket, Track, WorkerContext), `src/project_files.py` (FileItem), `src/type_aliases.py` (typed boundary + per-aggregate dataclasses: Metadata, CommsLogEntry, HistoryMessage, ToolDefinition, SessionInsights, DiscussionSettings, CustomSlice, MMAUsageStats, ProviderPayload, UIPanelConfig, PathInfo, FileItemsDiff, JsonPrimitive/JsonValue), `src/mcp_tool_specs.py` (typed ToolSpec registry, 45 tools), `src/result_types.py` (Result[T], ErrorInfo, ErrorKind). `VendorCapabilities` lives in `src/ai_client.py` `#region: Vendor Capabilities`. `PROVIDERS` constant in `src/ai_client.py` (8 providers: gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama). |
|
| [Data Models](guide_models.md) | `src/models.py` is now a ~1.5KB legacy re-export shim (`Metadata = TrackMetadata` alias + `PROVIDERS` lazy `__getattr__`). Data models moved to per-system files per `module_taxonomy_refactor_20260627`: `src/mma.py` (TrackMetadata, Ticket, Track, WorkerContext), `src/project_files.py` (FileItem), `src/type_aliases.py` (typed boundary + per-aggregate dataclasses: Metadata, CommsLogEntry, HistoryMessage, ToolDefinition, SessionInsights, DiscussionSettings, CustomSlice, MMAUsageStats, ProviderPayload, UIPanelConfig, PathInfo, FileItemsDiff, JsonPrimitive/JsonValue), `src/mcp_tool_specs.py` (typed ToolSpec registry, 45 tools), `src/result_types.py` (Result[T], ErrorInfo, ErrorKind). `VendorCapabilities` lives in `src/ai_client.py` `#region: Vendor Capabilities`. `PROVIDERS` constant in `src/ai_client.py` (7 providers: gemini, anthropic, deepseek, minimax, qwen, grok, llama). |
|
||||||
| [Discussions](guide_discussions.md) | The Discussion system: 23-operation matrix A1-A7 (per-entry) + B1-B11 (discussion-level) + C1-C5 (undo/redo), Take naming convention (`<base>_take_<n>`), branching at any entry (`project_manager.branch_discussion`), promotion to top-level (`project_manager.promote_take`), user-managed role list (`app.disc_roles`), per-role filter linked to MMA persona focus, `_disc_entries_lock` thread-safety contract, Hook API session endpoints |
|
| [Discussions](guide_discussions.md) | The Discussion system: 23-operation matrix A1-A7 (per-entry) + B1-B11 (discussion-level) + C1-C5 (undo/redo), Take naming convention (`<base>_take_<n>`), branching at any entry (`project_manager.branch_discussion`), promotion to top-level (`project_manager.promote_take`), user-managed role list (`app.disc_roles`), per-role filter linked to MMA persona focus, `_disc_entries_lock` thread-safety contract, Hook API session endpoints |
|
||||||
| [State Lifecycle](guide_state_lifecycle.md) | Undo/redo via `HistoryManager` + `UISnapshot` (13 captured fields, 100-snapshot capacity, debounced change detection at render frame), reset flow (`_handle_reset_session` — clears 30+ fields, replaces project, preserves `active_project_path` per the 2026-06-08 regression fix), `App.__getattr__`/`__setattr__` state delegation to Controller, 8-thread io_pool with 11 lock-protected regions (per `IO_POOL_MAX_WORKERS = 8` in `src/io_pool.py:20`; bumped 4→8 in 4a338486 on 2026-06-06), hot-reload integration |
|
| [State Lifecycle](guide_state_lifecycle.md) | Undo/redo via `HistoryManager` + `UISnapshot` (13 captured fields, 100-snapshot capacity, debounced change detection at render frame), reset flow (`_handle_reset_session` — clears 30+ fields, replaces project, preserves `active_project_path` per the 2026-06-08 regression fix), `App.__getattr__`/`__setattr__` state delegation to Controller, 8-thread io_pool with 11 lock-protected regions (per `IO_POOL_MAX_WORKERS = 8` in `src/io_pool.py:20`; bumped 4→8 in 4a338486 on 2026-06-06), hot-reload integration |
|
||||||
| [Context Aggregation](guide_context_aggregation.md) | The `aggregate.py` (518-line) pipeline: 3 aggregation strategies (`auto`/`summarize`/`full`), 7 per-file view modes (`full`/`summary`/`skeleton`/`outline`/`masked`/`custom`/`none`), full `FileItem` schema (9 fields + `__post_init__` normalizer), `ContextPreset` schema and `ContextPresetManager`, Tier 3 worker variant (`build_tier3_context` with FuzzyAnchor re-resolution and focus-file handling), `force_full`/`auto_aggregate` short-circuits, output file numbering, cache strategy (static prefix + dynamic history) |
|
| [Context Aggregation](guide_context_aggregation.md) | The `aggregate.py` (518-line) pipeline: 3 aggregation strategies (`auto`/`summarize`/`full`), 7 per-file view modes (`full`/`summary`/`skeleton`/`outline`/`masked`/`custom`/`none`), full `FileItem` schema (9 fields + `__post_init__` normalizer), `ContextPreset` schema and `ContextPresetManager`, Tier 3 worker variant (`build_tier3_context` with FuzzyAnchor re-resolution and focus-file handling), `force_full`/`auto_aggregate` short-circuits, output file numbering, cache strategy (static prefix + dynamic history) |
|
||||||
@@ -85,8 +85,8 @@ Controls what context is compiled and sent to the AI.
|
|||||||
- **Base Dir**: Root directory for path resolution and MCP tool constraints.
|
- **Base Dir**: Root directory for path resolution and MCP tool constraints.
|
||||||
- **Paths**: Explicit files or wildcard globs (`src/**/*.py`).
|
- **Paths**: Explicit files or wildcard globs (`src/**/*.py`).
|
||||||
- **File Flags**:
|
- **File Flags**:
|
||||||
- **Auto-Aggregate**: Include in context compilation.
|
- **Auto-Aggregate**: Include in context compilation.
|
||||||
- **Force Full**: Bypass summary-only mode for this file.
|
- **Force Full**: Bypass summary-only mode for this file.
|
||||||
- **Cache Indicator**: Green dot (●) indicates file is in provider's context cache.
|
- **Cache Indicator**: Green dot (●) indicates file is in provider's context cache.
|
||||||
|
|
||||||
### Discussion Hub
|
### Discussion Hub
|
||||||
@@ -101,7 +101,7 @@ Manages conversational branches to prevent context poisoning across tasks.
|
|||||||
|
|
||||||
### AI Settings Panel
|
### AI Settings Panel
|
||||||
|
|
||||||
- **Provider**: Switch between API backends (Gemini, Anthropic, DeepSeek, Gemini CLI, MiniMax).
|
- **Provider**: Switch between API backends (Gemini, Anthropic, DeepSeek, MiniMax).
|
||||||
- **Model**: Select from available models for the current provider.
|
- **Model**: Select from available models for the current provider.
|
||||||
- **Fetch Models**: Queries the active provider for the latest model list.
|
- **Fetch Models**: Queries the active provider for the latest model list.
|
||||||
- **Temperature / Max Tokens**: Generation parameters.
|
- **Temperature / Max Tokens**: Generation parameters.
|
||||||
@@ -324,127 +324,127 @@ EXPANDED = "${HOME}/subdir"
|
|||||||
|
|
||||||
```
|
```
|
||||||
manual_slop/
|
manual_slop/
|
||||||
├── conductor/ # Conductor system
|
├── conductor/ # Conductor system
|
||||||
│ ├── tracks/ # Track directories
|
│ ├── tracks/ # Track directories
|
||||||
│ │ └── <track_id>/ # Per-track files
|
│ │ └── <track_id>/ # Per-track files
|
||||||
│ │ ├── spec.md
|
│ │ ├── spec.md
|
||||||
│ │ ├── plan.md
|
│ │ ├── plan.md
|
||||||
│ │ ├── metadata.json
|
│ │ ├── metadata.json
|
||||||
│ │ └── state.toml
|
│ │ └── state.toml
|
||||||
│ ├── archive/ # Completed tracks
|
│ ├── archive/ # Completed tracks
|
||||||
│ ├── product.md # Product definition
|
│ ├── product.md # Product definition
|
||||||
│ ├── product-guidelines.md
|
│ ├── product-guidelines.md
|
||||||
│ ├── tech-stack.md
|
│ ├── tech-stack.md
|
||||||
│ ├── workflow.md
|
│ ├── workflow.md
|
||||||
│ ├── index.md
|
│ ├── index.md
|
||||||
│ └── edit_workflow.md
|
│ └── edit_workflow.md
|
||||||
├── docs/ # Deep-dive documentation (27 guides + specs/plans)
|
├── docs/ # Deep-dive documentation (27 guides + specs/plans)
|
||||||
│ ├── guide_ai_client.md # Multi-provider LLM client
|
│ ├── guide_ai_client.md # Multi-provider LLM client
|
||||||
│ ├── guide_api_hooks.md # HookServer + ApiHookClient
|
│ ├── guide_api_hooks.md # HookServer + ApiHookClient
|
||||||
│ ├── guide_app_controller.md # Headless AppController
|
│ ├── guide_app_controller.md # Headless AppController
|
||||||
│ ├── guide_architecture.md # Threading, event system, state machines
|
│ ├── guide_architecture.md # Threading, event system, state machines
|
||||||
│ ├── guide_beads.md # Beads/Dolt issue tracking
|
│ ├── guide_beads.md # Beads/Dolt issue tracking
|
||||||
│ ├── guide_command_palette.md # Command palette + 33 registered commands
|
│ ├── guide_command_palette.md # Command palette + 33 registered commands
|
||||||
│ ├── guide_context_aggregation.md # aggregate.py pipeline (strategies + view modes)
|
│ ├── guide_context_aggregation.md # aggregate.py pipeline (strategies + view modes)
|
||||||
│ ├── guide_context_curation.md # Granular AST control + Fuzzy Anchor slices
|
│ ├── guide_context_curation.md # Granular AST control + Fuzzy Anchor slices
|
||||||
│ ├── guide_discussions.md # Discussion system + A1-A7 matrix
|
│ ├── guide_discussions.md # Discussion system + A1-A7 matrix
|
||||||
│ ├── guide_docker_deployment.md # Docker + Gitea registry deployment
|
│ ├── guide_docker_deployment.md # Docker + Gitea registry deployment
|
||||||
│ ├── guide_gui_2.md # Main ImGui interface (App class, render functions)
|
│ ├── guide_gui_2.md # Main ImGui interface (App class, render functions)
|
||||||
│ ├── guide_hot_reload.md # State-preserving module reloading
|
│ ├── guide_hot_reload.md # State-preserving module reloading
|
||||||
│ ├── guide_mcp_client.md # 45 MCP tools + 3-layer security
|
│ ├── guide_mcp_client.md # 45 MCP tools + 3-layer security
|
||||||
│ ├── guide_meta_boundary.md # Application vs Meta-Tooling split
|
│ ├── guide_meta_boundary.md # Application vs Meta-Tooling split
|
||||||
│ ├── guide_mma.md # 4-Tier MMA concepts
|
│ ├── guide_mma.md # 4-Tier MMA concepts
|
||||||
│ ├── guide_models.md # Data model registry
|
│ ├── guide_models.md # Data model registry
|
||||||
│ ├── guide_multi_agent_conductor.md # ConductorEngine + TrackDAG + WorkerPool
|
│ ├── guide_multi_agent_conductor.md # ConductorEngine + TrackDAG + WorkerPool
|
||||||
│ ├── guide_nerv_theme.md # NERV Tactical Console theme
|
│ ├── guide_nerv_theme.md # NERV Tactical Console theme
|
||||||
│ ├── guide_personas.md # Unified agent profile system
|
│ ├── guide_personas.md # Unified agent profile system
|
||||||
│ ├── guide_rag.md # RAG subsystem (ChromaDB + embeddings)
|
│ ├── guide_rag.md # RAG subsystem (ChromaDB + embeddings)
|
||||||
│ ├── guide_shaders_and_window.md # Shader injection + custom window frame
|
│ ├── guide_shaders_and_window.md # Shader injection + custom window frame
|
||||||
│ ├── guide_simulations.md # Test framework + Puppeteer pattern
|
│ ├── guide_simulations.md # Test framework + Puppeteer pattern
|
||||||
│ ├── guide_state_lifecycle.md # Undo/redo + state delegation
|
│ ├── guide_state_lifecycle.md # Undo/redo + state delegation
|
||||||
│ ├── guide_testing.md # 322 test files + 7 conftest fixtures
|
│ ├── guide_testing.md # 322 test files + 7 conftest fixtures
|
||||||
│ ├── guide_themes.md # Multi-theme TOML system
|
│ ├── guide_themes.md # Multi-theme TOML system
|
||||||
│ ├── guide_tools.md # MCP tools + shell runner
|
│ ├── guide_tools.md # MCP tools + shell runner
|
||||||
│ ├── guide_workspace_profiles.md # Workspace profile save/load
|
│ ├── guide_workspace_profiles.md # Workspace profile save/load
|
||||||
│ ├── Readme.md
|
│ ├── Readme.md
|
||||||
│ ├── MMA_Support/ # Legacy MMA reference (deprecated)
|
│ ├── MMA_Support/ # Legacy MMA reference (deprecated)
|
||||||
│ ├── reports/ # Phase 5 reports
|
│ ├── reports/ # Phase 5 reports
|
||||||
│ └── superpowers/ # Specs and plans for design work
|
│ └── superpowers/ # Specs and plans for design work
|
||||||
├── src/ # Core implementation (53 modules)
|
├── src/ # Core implementation (53 modules)
|
||||||
│ ├── gui_2.py # Primary ImGui interface
|
│ ├── gui_2.py # Primary ImGui interface
|
||||||
│ ├── app_controller.py # Headless controller
|
│ ├── app_controller.py # Headless controller
|
||||||
│ ├── ai_client.py # Multi-provider LLM (Gemini, Anthropic, DeepSeek, MiniMax)
|
│ ├── ai_client.py # Multi-provider LLM (Gemini, Anthropic, DeepSeek, MiniMax)
|
||||||
│ ├── mcp_client.py # 45 MCP tools + 1 shell runner (canonical 46) with 3-layer security
|
│ ├── mcp_client.py # 45 MCP tools + 1 shell runner (canonical 46) with 3-layer security
|
||||||
│ ├── api_hooks.py # HookServer REST API on :8999
|
│ ├── api_hooks.py # HookServer REST API on :8999
|
||||||
│ ├── api_hook_client.py # Python client for the Hook API
|
│ ├── api_hook_client.py # Python client for the Hook API
|
||||||
│ ├── multi_agent_conductor.py # ConductorEngine
|
│ ├── multi_agent_conductor.py # ConductorEngine
|
||||||
│ ├── dag_engine.py # TrackDAG + ExecutionEngine
|
│ ├── dag_engine.py # TrackDAG + ExecutionEngine
|
||||||
│ ├── models.py # Ticket, Track, WorkerContext, etc.
|
│ ├── models.py # Ticket, Track, WorkerContext, etc.
|
||||||
│ ├── events.py # EventEmitter, SyncEventQueue
|
│ ├── events.py # EventEmitter, SyncEventQueue
|
||||||
│ ├── project_manager.py # TOML persistence, discussion management
|
│ ├── project_manager.py # TOML persistence, discussion management
|
||||||
│ ├── session_logger.py # JSON-L + markdown audit trails
|
│ ├── session_logger.py # JSON-L + markdown audit trails
|
||||||
│ ├── rag_engine.py # RAG (ChromaDB + embedding providers)
|
│ ├── rag_engine.py # RAG (ChromaDB + embedding providers)
|
||||||
│ ├── beads_client.py # Beads/Dolt issue tracking client
|
│ ├── beads_client.py # Beads/Dolt issue tracking client
|
||||||
│ ├── hot_reloader.py # State-preserving module reloader
|
│ ├── hot_reloader.py # State-preserving module reloader
|
||||||
│ ├── personas.py # Unified agent profile manager
|
│ ├── personas.py # Unified agent profile manager
|
||||||
│ ├── presets.py # System prompt preset manager
|
│ ├── presets.py # System prompt preset manager
|
||||||
│ ├── context_presets.py # Context composition preset manager
|
│ ├── context_presets.py # Context composition preset manager
|
||||||
│ ├── tool_presets.py # Tool preset manager
|
│ ├── tool_presets.py # Tool preset manager
|
||||||
│ ├── tool_bias.py # Tool bias engine
|
│ ├── tool_bias.py # Tool bias engine
|
||||||
│ ├── command_palette.py # Command palette + fuzzy matcher
|
│ ├── command_palette.py # Command palette + fuzzy matcher
|
||||||
│ ├── commands.py # 33 registered commands
|
│ ├── commands.py # 33 registered commands
|
||||||
│ ├── workspace_manager.py # Workspace profile save/load
|
│ ├── workspace_manager.py # Workspace profile save/load
|
||||||
│ ├── theme_2.py # Theme system (palette/font/etc.)
|
│ ├── theme_2.py # Theme system (palette/font/etc.)
|
||||||
│ ├── theme_nerv.py # NERV Tactical Console theme
|
│ ├── theme_nerv.py # NERV Tactical Console theme
|
||||||
│ ├── theme_nerv_fx.py # NERV FX (scanlines, flicker, alert)
|
│ ├── theme_nerv_fx.py # NERV FX (scanlines, flicker, alert)
|
||||||
│ ├── shell_runner.py # PowerShell execution with 60s timeout + qa_callback + patch_callback
|
│ ├── shell_runner.py # PowerShell execution with 60s timeout + qa_callback + patch_callback
|
||||||
│ ├── file_cache.py # ASTParser (tree-sitter)
|
│ ├── file_cache.py # ASTParser (tree-sitter)
|
||||||
│ ├── summarize.py # Heuristic file summaries
|
│ ├── summarize.py # Heuristic file summaries
|
||||||
│ ├── outline_tool.py # Hierarchical code outline
|
│ ├── outline_tool.py # Hierarchical code outline
|
||||||
│ ├── fuzzy_anchor.py # Fuzzy anchor slice algorithm
|
│ ├── fuzzy_anchor.py # Fuzzy anchor slice algorithm
|
||||||
│ ├── history.py # Undo/redo HistoryManager
|
│ ├── history.py # Undo/redo HistoryManager
|
||||||
│ ├── imgui_scopes.py # ImGui context managers
|
│ ├── imgui_scopes.py # ImGui context managers
|
||||||
│ ├── performance_monitor.py # FPS/CPU tracking
|
│ ├── performance_monitor.py # FPS/CPU tracking
|
||||||
│ ├── log_registry.py # Session metadata
|
│ ├── log_registry.py # Session metadata
|
||||||
│ ├── log_pruner.py # Automated log cleanup
|
│ ├── log_pruner.py # Automated log cleanup
|
||||||
│ ├── paths.py # Centralized path resolution
|
│ ├── paths.py # Centralized path resolution
|
||||||
│ ├── cost_tracker.py # Token cost estimation
|
│ ├── cost_tracker.py # Token cost estimation
|
||||||
│ ├── gemini_cli_adapter.py # CLI subprocess adapter
|
│ ├── gemini_cli_adapter.py # CLI subprocess adapter
|
||||||
│ ├── mma_prompts.py # Tier-specific system prompts
|
│ ├── mma_prompts.py # Tier-specific system prompts
|
||||||
│ ├── summary_cache.py # SHA256-keyed summary LRU cache
|
│ ├── summary_cache.py # SHA256-keyed summary LRU cache
|
||||||
│ ├── markdown_helper.py # Markdown rendering helpers
|
│ ├── markdown_helper.py # Markdown rendering helpers
|
||||||
│ ├── patch_modal.py # Patch approval modal
|
│ ├── patch_modal.py # Patch approval modal
|
||||||
│ ├── diff_viewer.py # Diff rendering
|
│ ├── diff_viewer.py # Diff rendering
|
||||||
│ ├── external_editor.py # External editor integration
|
│ ├── external_editor.py # External editor integration
|
||||||
│ ├── orchestrator_pm.py # Orchestrator project manager
|
│ ├── orchestrator_pm.py # Orchestrator project manager
|
||||||
│ ├── conductor_tech_lead.py # Tier 2 ticket generation
|
│ ├── conductor_tech_lead.py # Tier 2 ticket generation
|
||||||
│ ├── synthesis_formatter.py # Multi-take synthesis
|
│ ├── synthesis_formatter.py # Multi-take synthesis
|
||||||
│ ├── thinking_parser.py # AI thinking-trace extraction
|
│ ├── thinking_parser.py # AI thinking-trace extraction
|
||||||
│ └── __init__.py
|
│ └── __init__.py
|
||||||
├── simulation/ # Test simulations
|
├── simulation/ # Test simulations
|
||||||
│ ├── sim_base.py # BaseSimulation class
|
│ ├── sim_base.py # BaseSimulation class
|
||||||
│ ├── workflow_sim.py # WorkflowSimulator
|
│ ├── workflow_sim.py # WorkflowSimulator
|
||||||
│ ├── user_agent.py # UserSimAgent
|
│ ├── user_agent.py # UserSimAgent
|
||||||
│ ├── sim_context.py # ContextSimulation
|
│ ├── sim_context.py # ContextSimulation
|
||||||
│ ├── sim_execution.py # ExecutionSimulation
|
│ ├── sim_execution.py # ExecutionSimulation
|
||||||
│ ├── sim_ai_settings.py # AISettingsSimulation
|
│ ├── sim_ai_settings.py # AISettingsSimulation
|
||||||
│ └── sim_tools.py # ToolsSimulation
|
│ └── sim_tools.py # ToolsSimulation
|
||||||
├── tests/ # Test suite (251 files)
|
├── tests/ # Test suite (251 files)
|
||||||
│ ├── conftest.py # Fixtures (live_gui, isolate_workspace, etc.)
|
│ ├── conftest.py # Fixtures (live_gui, isolate_workspace, etc.)
|
||||||
│ ├── mock_gemini_cli.py # Mock provider for integration tests
|
│ ├── mock_gemini_cli.py # Mock provider for integration tests
|
||||||
│ ├── test_*.py # Unit tests
|
│ ├── test_*.py # Unit tests
|
||||||
│ ├── *_sim.py # Integration tests using live_gui
|
│ ├── *_sim.py # Integration tests using live_gui
|
||||||
│ ├── test_clean_install.py # Opt-in: clones repo and verifies hooks
|
│ ├── test_clean_install.py # Opt-in: clones repo and verifies hooks
|
||||||
│ ├── test_docker_build.py # Opt-in: builds Docker image
|
│ ├── test_docker_build.py # Opt-in: builds Docker image
|
||||||
│ ├── artifacts/ # Git-ignored; test outputs
|
│ ├── artifacts/ # Git-ignored; test outputs
|
||||||
│ └── logs/ # Git-ignored; live_gui log files
|
│ └── logs/ # Git-ignored; live_gui log files
|
||||||
├── scripts/ # Utility scripts
|
├── scripts/ # Utility scripts
|
||||||
│ ├── generated/ # AI-generated scripts
|
│ ├── generated/ # AI-generated scripts
|
||||||
│ ├── check_test_toml_paths.py # Audit script (CI gate)
|
│ ├── check_test_toml_paths.py # Audit script (CI gate)
|
||||||
│ ├── docker_build.sh
|
│ ├── docker_build.sh
|
||||||
│ └── docker_run.sh
|
│ └── docker_run.sh
|
||||||
├── sloppy.py # Main entry point
|
├── sloppy.py # Main entry point
|
||||||
├── config.toml # Global configuration
|
├── config.toml # Global configuration
|
||||||
├── manual_slop.toml # Active project config (current)
|
├── manual_slop.toml # Active project config (current)
|
||||||
└── credentials.toml # API keys (gitignored)
|
└── credentials.toml # API keys (gitignored)
|
||||||
```
|
```
|
||||||
|
|||||||
+182
-182
@@ -6,14 +6,14 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
`src/ai_client.py` (~166KB) is the **unified LLM client** for 8 providers. It abstracts the differences between providers (Gemini, Anthropic, DeepSeek, MiniMax, Gemini CLI, Qwen, Grok, Llama) behind a single `send()` function.
|
`src/ai_client.py` (~166KB) is the **unified LLM client** for 7 providers. It abstracts the differences between providers (Gemini, Anthropic, DeepSeek, MiniMax, Qwen, Grok, Llama) behind a single `send()` function.
|
||||||
|
|
||||||
The module is a **stateful singleton** — all provider state is held in module-level globals. There is no class wrapping; the module itself is the abstraction layer.
|
The module is a **stateful singleton** — all provider state is held in module-level globals. There is no class wrapping; the module itself is the abstraction layer.
|
||||||
|
|
||||||
The 8 providers split into 3 API shapes:
|
The 7 providers split into 3 API shapes:
|
||||||
- **Native SDK**: Gemini (google-genai), Anthropic (anthropic), Qwen (DashScope)
|
- **Native SDK**: Gemini (google-genai), Anthropic (anthropic), Qwen (DashScope)
|
||||||
- **OpenAI-compatible**: MiniMax, Grok, Llama (Ollama/OpenRouter/custom), DeepSeek
|
- **OpenAI-compatible**: MiniMax, Grok, Llama (Ollama/OpenRouter/custom), DeepSeek
|
||||||
- **Subprocess**: Gemini CLI
|
- **Subprocess**:
|
||||||
|
|
||||||
The OpenAI-compatible vendors all call the shared helper in `src/openai_compatible.py` (added 2026-06-06 by the `qwen_llama_grok_integration_20260606` track; see "Shared OpenAI-Compatible Helper" section below). The MiniMax provider's `_send_minimax` was refactored to use this helper (Phase 4 of the same track, 231 → 75 lines, 68% reduction).
|
The OpenAI-compatible vendors all call the shared helper in `src/openai_compatible.py` (added 2026-06-06 by the `qwen_llama_grok_integration_20260606` track; see "Shared OpenAI-Compatible Helper" section below). The MiniMax provider's `_send_minimax` was refactored to use this helper (Phase 4 of the same track, 231 → 75 lines, 68% reduction).
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ The OpenAI-compatible vendors all call the shared helper in `src/openai_compatib
|
|||||||
|
|
||||||
## Module-Level Imports
|
## Module-Level Imports
|
||||||
|
|
||||||
> **Important:** The provider SDKs are **NOT** imported at module level. `import google.genai`, `import anthropic`, `import openai`, `import dashscope`, and `import fastapi` are heavy (~430-955ms each on cold load) and are now obtained via `src.module_loader._require_warmed("google.genai")` and similar calls, after the `WarmupManager` has loaded them in the background. The module-level globals you see in the State section (`_gemini_client`, `_anthropic_client`, etc.) are typed as `Optional` because they're populated by `_require_warmed()` on first use, not at import time. (Updated 2026-07-02: there are 8 providers, not 5 — the original "5 SDKs" count predated the qwen/grok/llama additions.)
|
> **Important:** The provider SDKs are **NOT** imported at module level. `import google.genai`, `import anthropic`, `import openai`, `import dashscope`, and `import fastapi` are heavy (~430-955ms each on cold load) and are now obtained via `src.module_loader._require_warmed("google.genai")` and similar calls, after the `WarmupManager` has loaded them in the background. The module-level globals you see in the State section (`_gemini_client`, `_anthropic_client`, etc.) are typed as `Optional` because they're populated by `_require_warmed()` on first use, not at import time. (Updated 2026-07-02: there are 7 providers, not 5 — the original "5 SDKs" count predated the qwen/grok/llama additions.)
|
||||||
|
|
||||||
This change was part of the 2026-06-06 `startup_speedup_20260606` track. Before: `import src.ai_client` took ~1800ms. After: ~161ms. The remaining cost is the bare module skeleton.
|
This change was part of the 2026-06-06 `startup_speedup_20260606` track. Before: `import src.ai_client` took ~1800ms. After: ~161ms. The remaining cost is the bare module skeleton.
|
||||||
|
|
||||||
@@ -29,19 +29,19 @@ This change was part of the 2026-06-06 `startup_speedup_20260606` track. Before:
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────┐
|
||||||
│ ai_client.send(md_content, user_message, ...) │
|
│ ai_client.send(md_content, user_message, ...) │
|
||||||
│ │
|
│ │
|
||||||
│ 1. _send_lock.acquire() — serialize all calls │
|
│ 1. _send_lock.acquire() — serialize all calls │
|
||||||
│ 2. Read _provider / _model │
|
│ 2. Read _provider / _model │
|
||||||
│ 3. Route to provider-specific _send_<provider>() │
|
│ 3. Route to provider-specific _send_<provider>() │
|
||||||
│ 4. Return str response │
|
│ 4. Return str response │
|
||||||
└─────────────────┬───────────────────────────────┘
|
└─────────────────┬───────────────────────────────┘
|
||||||
│ dispatches based on _provider
|
│ dispatches based on _provider
|
||||||
▼
|
▼
|
||||||
┌────────┬─────────┬────────┬──────────┐
|
┌────────┬─────────┬────────┬──────────┐
|
||||||
▼ ▼ ▼ ▼ ▼
|
▼ ▼ ▼ ▼ ▼
|
||||||
_gemini _anthropic _deepseek _minimax _gemini_cli
|
_gemini _anthropic _deepseek _minimax _gemini_cli
|
||||||
(subprocess)
|
(subprocess)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -94,18 +94,18 @@ _gemini_cli_adapter: Optional[GeminiCliAdapter] = None
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def send(
|
def send(
|
||||||
md_content: str,
|
md_content: str,
|
||||||
user_message: str,
|
user_message: str,
|
||||||
base_dir: str = ".",
|
base_dir: str = ".",
|
||||||
file_items: list[dict] | None = None,
|
file_items: list[dict] | None = None,
|
||||||
discussion_history: str = "",
|
discussion_history: str = "",
|
||||||
stream: bool = False,
|
stream: bool = False,
|
||||||
pre_tool_callback: Optional[Callable] = None,
|
pre_tool_callback: Optional[Callable] = None,
|
||||||
qa_callback: Optional[Callable] = None,
|
qa_callback: Optional[Callable] = None,
|
||||||
enable_tools: bool = True,
|
enable_tools: bool = True,
|
||||||
stream_callback: Optional[Callable] = None,
|
stream_callback: Optional[Callable] = None,
|
||||||
patch_callback: Optional[Callable] = None,
|
patch_callback: Optional[Callable] = None,
|
||||||
rag_engine: Optional[Any] = None,
|
rag_engine: Optional[Any] = None,
|
||||||
) -> Result[str]:
|
) -> Result[str]:
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ ai_client.set_model_params(temp=0.7, max_tok=4096, top_p=0.9, trunc_limit=4000)
|
|||||||
### Session Management
|
### Session Management
|
||||||
|
|
||||||
```python
|
```python
|
||||||
ai_client.reset_session() # Clears all provider state, history, cache
|
ai_client.reset_session() # Clears all provider state, history, cache
|
||||||
```
|
```
|
||||||
|
|
||||||
### Event Hooks
|
### Event Hooks
|
||||||
@@ -171,10 +171,10 @@ ai_client.events.on("my_event", my_handler)
|
|||||||
### Comms Log
|
### Comms Log
|
||||||
|
|
||||||
```python
|
```python
|
||||||
ai_client._append_comms(direction, kind, payload) # Add entry
|
ai_client._append_comms(direction, kind, payload) # Add entry
|
||||||
ai_client.get_comms_log() # Read all
|
ai_client.get_comms_log() # Read all
|
||||||
ai_client.clear_comms_log() # Clear
|
ai_client.clear_comms_log() # Clear
|
||||||
ai_client.get_token_stats(md_content) # Estimate token usage
|
ai_client.get_token_stats(md_content) # Estimate token usage
|
||||||
```
|
```
|
||||||
|
|
||||||
### Provider Error Taxonomy — Legacy (Pre-Refactor)
|
### Provider Error Taxonomy — Legacy (Pre-Refactor)
|
||||||
@@ -187,12 +187,12 @@ ai_client.get_token_stats(md_content) # Estimate token usage
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class ProviderError(Exception):
|
class ProviderError(Exception):
|
||||||
kind: str # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown"
|
kind: str # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown"
|
||||||
provider: str
|
provider: str
|
||||||
original: Exception
|
original: Exception
|
||||||
|
|
||||||
def ui_message(self) -> str:
|
def ui_message(self) -> str:
|
||||||
"""Returns a user-friendly error message."""
|
"""Returns a user-friendly error message."""
|
||||||
```
|
```
|
||||||
|
|
||||||
`ProviderError` was raised by provider-specific `_send_*` functions on failure.
|
`ProviderError` was raised by provider-specific `_send_*` functions on failure.
|
||||||
@@ -210,30 +210,30 @@ All providers follow the same high-level pattern in `_send_*`:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def _send_<provider>(md_content, user_message, ...):
|
def _send_<provider>(md_content, user_message, ...):
|
||||||
for round in range(MAX_TOOL_ROUNDS + 2): # up to 10 rounds
|
for round in range(MAX_TOOL_ROUNDS + 2): # up to 10 rounds
|
||||||
response = provider_api_call(md_content, user_message, history, tools)
|
response = provider_api_call(md_content, user_message, history, tools)
|
||||||
comms_log(direction="IN", kind="response", payload=response)
|
comms_log(direction="IN", kind="response", payload=response)
|
||||||
|
|
||||||
if not has_function_calls(response):
|
if not has_function_calls(response):
|
||||||
return extract_text(response)
|
return extract_text(response)
|
||||||
|
|
||||||
for call in response.function_calls:
|
for call in response.function_calls:
|
||||||
if pre_tool_callback and pre_tool_callback(...) is rejected:
|
if pre_tool_callback and pre_tool_callback(...) is rejected:
|
||||||
return rejection_message
|
return rejection_message
|
||||||
tool_result = dispatch(call.name, call.args, base_dir)
|
tool_result = dispatch(call.name, call.args, base_dir)
|
||||||
append_tool_result_to_history(call, tool_result)
|
append_tool_result_to_history(call, tool_result)
|
||||||
|
|
||||||
# Context refresh: re-read all tracked files (mtime check)
|
# Context refresh: re-read all tracked files (mtime check)
|
||||||
_reread_file_items(file_items)
|
_reread_file_items(file_items)
|
||||||
|
|
||||||
# Truncate tool outputs at _history_trunc_limit
|
# Truncate tool outputs at _history_trunc_limit
|
||||||
truncate_tool_outputs(history)
|
truncate_tool_outputs(history)
|
||||||
|
|
||||||
# Cumulative byte check
|
# Cumulative byte check
|
||||||
if cumulative_tool_bytes > 500_000:
|
if cumulative_tool_bytes > 500_000:
|
||||||
inject_warning()
|
inject_warning()
|
||||||
|
|
||||||
return final_response
|
return final_response
|
||||||
```
|
```
|
||||||
|
|
||||||
The constants:
|
The constants:
|
||||||
@@ -273,7 +273,7 @@ The constants:
|
|||||||
- **History trimming**: similar to Anthropic (drop turn pairs at threshold)
|
- **History trimming**: similar to Anthropic (drop turn pairs at threshold)
|
||||||
- **History repair**: `_repair_minimax_history`
|
- **History repair**: `_repair_minimax_history`
|
||||||
|
|
||||||
### Gemini CLI
|
###
|
||||||
|
|
||||||
- **Subprocess adapter**: `GeminiCliAdapter` in `src/gemini_cli_adapter.py`
|
- **Subprocess adapter**: `GeminiCliAdapter` in `src/gemini_cli_adapter.py`
|
||||||
- **Persistent session**: CLI maintains its own session ID
|
- **Persistent session**: CLI maintains its own session ID
|
||||||
@@ -288,9 +288,9 @@ The constants:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4:
|
if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4:
|
||||||
while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3:
|
while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3:
|
||||||
hist.pop(0) # Assistant
|
hist.pop(0) # Assistant
|
||||||
hist.pop(0) # User
|
hist.pop(0) # User
|
||||||
```
|
```
|
||||||
|
|
||||||
### Anthropic (180K limit)
|
### Anthropic (180K limit)
|
||||||
@@ -314,8 +314,8 @@ No built-in trimming (relies on the caller to keep history short).
|
|||||||
### Gemini Server-Side Cache
|
### Gemini Server-Side Cache
|
||||||
|
|
||||||
```python
|
```python
|
||||||
_gemini_cache_md_hash: Optional[str] = None # Hash of cached content
|
_gemini_cache_md_hash: Optional[str] = None # Hash of cached content
|
||||||
_gemini_cache_created_at: Optional[float] = None # Monotonic time
|
_gemini_cache_created_at: Optional[float] = None # Monotonic time
|
||||||
```
|
```
|
||||||
|
|
||||||
The cache decision is a 3-way branch on each `_send_gemini` call:
|
The cache decision is a 3-way branch on each `_send_gemini` call:
|
||||||
@@ -344,8 +344,8 @@ After the last tool call in each round, `_reread_file_items(file_items)` checks
|
|||||||
2. If unchanged: pass through as-is
|
2. If unchanged: pass through as-is
|
||||||
3. If changed: re-read content, store `old_content` for diffing, update `mtime`
|
3. If changed: re-read content, store `old_content` for diffing, update `mtime`
|
||||||
4. Changed files are diffed via `_build_file_diff_text`:
|
4. Changed files are diffed via `_build_file_diff_text`:
|
||||||
- Files ≤ 200 lines: emit full content
|
- Files ≤ 200 lines: emit full content
|
||||||
- Files > 200 lines with `old_content`: emit `difflib.unified_diff`
|
- Files > 200 lines with `old_content`: emit `difflib.unified_diff`
|
||||||
5. Diff is appended to the last tool's output as `[SYSTEM: FILES UPDATED]\n\n{diff}`
|
5. Diff is appended to the last tool's output as `[SYSTEM: FILES UPDATED]\n\n{diff}`
|
||||||
6. Stale `[FILES UPDATED]` blocks are stripped from older history turns by `_strip_stale_file_refreshes`
|
6. Stale `[FILES UPDATED]` blocks are stripped from older history turns by `_strip_stale_file_refreshes`
|
||||||
|
|
||||||
@@ -359,19 +359,19 @@ For Tier 4: when an error occurs, `qa_callback` may be invoked to get a Tier 4 A
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def run_tier4_analysis(stderr: str) -> str:
|
def run_tier4_analysis(stderr: str) -> str:
|
||||||
"""Stateless Tier 4 QA analysis of an error message."""
|
"""Stateless Tier 4 QA analysis of an error message."""
|
||||||
# Uses a dedicated system prompt for error triage
|
# Uses a dedicated system prompt for error triage
|
||||||
# Returns analysis text (root cause, suggested fix)
|
# Returns analysis text (root cause, suggested fix)
|
||||||
# Does NOT modify any code — analysis only
|
# Does NOT modify any code — analysis only
|
||||||
```
|
```
|
||||||
|
|
||||||
For Tier 4 patch generation:
|
For Tier 4 patch generation:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def run_tier4_patch_generation(error: str, file_context: str) -> str:
|
def run_tier4_patch_generation(error: str, file_context: str) -> str:
|
||||||
"""Generate a unified diff patch from an error and file context."""
|
"""Generate a unified diff patch from an error and file context."""
|
||||||
# Returns the patch as a string
|
# Returns the patch as a string
|
||||||
# The caller (typically the patch modal) presents it for human review
|
# The caller (typically the patch modal) presents it for human review
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -416,10 +416,10 @@ def run_tier4_patch_generation(error: str, file_context: str) -> str:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def test_set_provider():
|
def test_set_provider():
|
||||||
from src import ai_client
|
from src import ai_client
|
||||||
ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest")
|
ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest")
|
||||||
assert ai_client.get_provider() == "anthropic"
|
assert ai_client.get_provider() == "anthropic"
|
||||||
ai_client.reset_session() # Cleanup
|
ai_client.reset_session() # Cleanup
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mocked Tests
|
### Mocked Tests
|
||||||
@@ -428,12 +428,12 @@ def test_set_provider():
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
def test_send_routes_to_provider(monkeypatch):
|
def test_send_routes_to_provider(monkeypatch):
|
||||||
with patch.object(ai_client, "_send_anthropic", return_value="mocked") as m:
|
with patch.object(ai_client, "_send_anthropic", return_value="mocked") as m:
|
||||||
ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest")
|
ai_client.set_provider("anthropic", "claude-3-5-sonnet-latest")
|
||||||
result = ai_client.send("system", "user")
|
result = ai_client.send("system", "user")
|
||||||
assert result == "mocked"
|
assert result == "mocked"
|
||||||
m.assert_called_once()
|
m.assert_called_once()
|
||||||
ai_client.reset_session()
|
ai_client.reset_session()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Integration (real API)
|
### Integration (real API)
|
||||||
@@ -451,7 +451,7 @@ canonical reference is
|
|||||||
### Result-Based Returns
|
### Result-Based Returns
|
||||||
|
|
||||||
All `_send_<vendor>_result()` functions (8 vendors: Gemini, Anthropic,
|
All `_send_<vendor>_result()` functions (8 vendors: Gemini, Anthropic,
|
||||||
DeepSeek, MiniMax, Gemini CLI, Qwen, Llama, Grok — plus the
|
DeepSeek, MiniMax, Qwen, Llama, Grok — plus the
|
||||||
`_send_llama_native` Ollama adapter) return `Result[str]` with `errors: list[ErrorInfo]`. SDK
|
`_send_llama_native` Ollama adapter) return `Result[str]` with `errors: list[ErrorInfo]`. SDK
|
||||||
exceptions are caught at the boundary (`src/openai_compatible.py`,
|
exceptions are caught at the boundary (`src/openai_compatible.py`,
|
||||||
`src/qwen_adapter.py`) and converted to `ErrorInfo` dataclasses. The
|
`src/qwen_adapter.py`) and converted to `ErrorInfo` dataclasses. The
|
||||||
@@ -469,10 +469,10 @@ meaning — do not overload `UNKNOWN` when a new failure mode surfaces
|
|||||||
### Public API
|
### Public API
|
||||||
|
|
||||||
- **`ai_client.send(...)`** — the public API. Returns
|
- **`ai_client.send(...)`** — the public API. Returns
|
||||||
`Result[str]` (with `errors: list[ErrorInfo]` as a side-channel field).
|
`Result[str]` (with `errors: list[ErrorInfo]` as a side-channel field).
|
||||||
Accepts 13+ parameters including 8 callbacks.
|
Accepts 13+ parameters including 8 callbacks.
|
||||||
Internally calls `_send_<vendor>()` for the active provider (the
|
Internally calls `_send_<vendor>()` for the active provider (the
|
||||||
vendor functions return `Result[str]` directly).
|
vendor functions return `Result[str]` directly).
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
@@ -482,9 +482,9 @@ from src.result_types import ErrorKind
|
|||||||
|
|
||||||
r = ai_client.send("system prompt", "user message")
|
r = ai_client.send("system prompt", "user message")
|
||||||
if not r.ok:
|
if not r.ok:
|
||||||
for err in r.errors:
|
for err in r.errors:
|
||||||
log.error(err.ui_message())
|
log.error(err.ui_message())
|
||||||
# err.kind is one of ErrorKind.*; err.source is "ai_client.<vendor>"
|
# err.kind is one of ErrorKind.*; err.source is "ai_client.<vendor>"
|
||||||
# use r.data regardless (it's the zero-initialized "" on failure)
|
# use r.data regardless (it's the zero-initialized "" on failure)
|
||||||
print(r.data)
|
print(r.data)
|
||||||
```
|
```
|
||||||
@@ -492,10 +492,10 @@ print(r.data)
|
|||||||
### Migration Notes for Existing Callers
|
### Migration Notes for Existing Callers
|
||||||
|
|
||||||
- All production call sites and tests now use `send()`. The
|
- All production call sites and tests now use `send()`. The
|
||||||
legacy `send()` function was removed in the
|
legacy `send()` function was removed in the
|
||||||
`public_api_migration_and_ui_polish_20260615` track.
|
`public_api_migration_and_ui_polish_20260615` track.
|
||||||
- Tests that mock `ai_client._send_<vendor>` should use the
|
- Tests that mock `ai_client._send_<vendor>` should use the
|
||||||
`Result(data=...)` return value pattern.
|
`Result(data=...)` return value pattern.
|
||||||
|
|
||||||
### See Also (in-doc)
|
### See Also (in-doc)
|
||||||
|
|
||||||
@@ -532,34 +532,34 @@ Added 2026-06-06 by the `qwen_llama_grok_integration_20260606` track. Operates o
|
|||||||
```python
|
```python
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class NormalizedResponse:
|
class NormalizedResponse:
|
||||||
text: str
|
text: str
|
||||||
tool_calls: list[dict[str, Any]]
|
tool_calls: list[dict[str, Any]]
|
||||||
usage_input_tokens: int
|
usage_input_tokens: int
|
||||||
usage_output_tokens: int
|
usage_output_tokens: int
|
||||||
usage_cache_read_tokens: int
|
usage_cache_read_tokens: int
|
||||||
usage_cache_creation_tokens: int
|
usage_cache_creation_tokens: int
|
||||||
raw_response: Any
|
raw_response: Any
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OpenAICompatibleRequest:
|
class OpenAICompatibleRequest:
|
||||||
messages: list[dict[str, Any]]
|
messages: list[dict[str, Any]]
|
||||||
model: str
|
model: str
|
||||||
temperature: float = 0.0
|
temperature: float = 0.0
|
||||||
top_p: float = 1.0
|
top_p: float = 1.0
|
||||||
max_tokens: int = 8192
|
max_tokens: int = 8192
|
||||||
tools: Optional[list[dict[str, Any]]] = None
|
tools: Optional[list[dict[str, Any]]] = None
|
||||||
tool_choice: str = "auto"
|
tool_choice: str = "auto"
|
||||||
stream: bool = False
|
stream: bool = False
|
||||||
stream_callback: Optional[Callable[[str], None]] = None
|
stream_callback: Optional[Callable[[str], None]] = None
|
||||||
```
|
```
|
||||||
|
|
||||||
### The Function
|
### The Function
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def send_openai_compatible(
|
def send_openai_compatible(
|
||||||
client: Any, # openai.OpenAI client with vendor-specific base_url + auth
|
client: Any, # openai.OpenAI client with vendor-specific base_url + auth
|
||||||
request: OpenAICompatibleRequest,
|
request: OpenAICompatibleRequest,
|
||||||
*, capabilities: "VendorCapabilities", # from src/ai_client.py #region: Vendor Capabilities
|
*, capabilities: "VendorCapabilities", # from src/ai_client.py #region: Vendor Capabilities
|
||||||
) -> NormalizedResponse:
|
) -> NormalizedResponse:
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -577,16 +577,16 @@ The function:
|
|||||||
```python
|
```python
|
||||||
# _send_grok, _send_llama (single-shot placeholders), _send_minimax (with restored tool loop)
|
# _send_grok, _send_llama (single-shot placeholders), _send_minimax (with restored tool loop)
|
||||||
def _send_grok(md_content, user_message, base_dir, file_items=None, discussion_history="", stream=False, ...):
|
def _send_grok(md_content, user_message, base_dir, file_items=None, discussion_history="", stream=False, ...):
|
||||||
client = _ensure_grok_client() # openai.OpenAI(api_key=..., base_url="https://api.x.ai/v1")
|
client = _ensure_grok_client() # openai.OpenAI(api_key=..., base_url="https://api.x.ai/v1")
|
||||||
with _grok_history_lock:
|
with _grok_history_lock:
|
||||||
# ... build messages, append user, system + context ...
|
# ... build messages, append user, system + context ...
|
||||||
request = OpenAICompatibleRequest(
|
request = OpenAICompatibleRequest(
|
||||||
messages=messages, model=_model, stream=stream,
|
messages=messages, model=_model, stream=stream,
|
||||||
stream_callback=stream_callback,
|
stream_callback=stream_callback,
|
||||||
)
|
)
|
||||||
caps = get_capabilities("grok", _model)
|
caps = get_capabilities("grok", _model)
|
||||||
response = send_openai_compatible(client, request, capabilities=caps)
|
response = send_openai_compatible(client, request, capabilities=caps)
|
||||||
# ... append to history, return response.text ...
|
# ... append to history, return response.text ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Qwen Adapter (`src/qwen_adapter.py`)
|
### Qwen Adapter (`src/qwen_adapter.py`)
|
||||||
@@ -610,28 +610,28 @@ Added 2026-06-11 by the `qwen_llama_grok_followup_20260611` track. Wraps `send_o
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def run_with_tool_loop(
|
def run_with_tool_loop(
|
||||||
client: Any,
|
client: Any,
|
||||||
request: OpenAICompatibleRequest | Callable[[int], OpenAICompatibleRequest],
|
request: OpenAICompatibleRequest | Callable[[int], OpenAICompatibleRequest],
|
||||||
*,
|
*,
|
||||||
capabilities: "VendorCapabilities",
|
capabilities: "VendorCapabilities",
|
||||||
pre_tool_callback: Optional[Callable] = None,
|
pre_tool_callback: Optional[Callable] = None,
|
||||||
qa_callback: Optional[Callable] = None,
|
qa_callback: Optional[Callable] = None,
|
||||||
stream_callback: Optional[Callable[[str], None]] = None,
|
stream_callback: Optional[Callable[[str], None]] = None,
|
||||||
patch_callback: Optional[Callable] = None,
|
patch_callback: Optional[Callable] = None,
|
||||||
base_dir: str,
|
base_dir: str,
|
||||||
vendor_name: str,
|
vendor_name: str,
|
||||||
history_lock: Optional[threading.Lock] = None,
|
history_lock: Optional[threading.Lock] = None,
|
||||||
history: Optional[list] = None,
|
history: Optional[list] = None,
|
||||||
trim_func: Optional[Callable] = None,
|
trim_func: Optional[Callable] = None,
|
||||||
send_func: Optional[Callable[[int], "NormalizedResponse"]] = None,
|
send_func: Optional[Callable[[int], "NormalizedResponse"]] = None,
|
||||||
on_pre_dispatch: Optional[Callable] = None,
|
on_pre_dispatch: Optional[Callable] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
```
|
```
|
||||||
|
|
||||||
**Two extensions** were added beyond the original signature:
|
**Two extensions** were added beyond the original signature:
|
||||||
|
|
||||||
1. `request` accepts a `Callable[[int], OpenAICompatibleRequest]` (per-round history rebuild). Use this when the vendor mutates history between rounds (e.g., MiniMax's per-round append).
|
1. `request` accepts a `Callable[[int], OpenAICompatibleRequest]` (per-round history rebuild). Use this when the vendor mutates history between rounds (e.g., MiniMax's per-round append).
|
||||||
2. `send_func + on_pre_dispatch` allows vendored call paths (e.g., Gemini CLI's `GeminiCliAdapter`) to share the loop + dispatch without going through `send_openai_compatible`.
|
2. `send_func + on_pre_dispatch` allows vendored call paths (e.g., 's `GeminiCliAdapter`) to share the loop + dispatch without going through `send_openai_compatible`.
|
||||||
|
|
||||||
**Vendors applied** (as of 2026-06-11):
|
**Vendors applied** (as of 2026-06-11):
|
||||||
- `_send_minimax` (was inline, now uses helper)
|
- `_send_minimax` (was inline, now uses helper)
|
||||||
@@ -657,7 +657,7 @@ Added 2026-06-11. When `_llama_base_url` is `localhost` / `127.0.0.1` (Ollama de
|
|||||||
The dispatcher check is in `_send_llama` at the function head:
|
The dispatcher check is in `_send_llama` at the function head:
|
||||||
```python
|
```python
|
||||||
if "localhost" in _llama_base_url or "127.0.0.1" in _llama_base_url:
|
if "localhost" in _llama_base_url or "127.0.0.1" in _llama_base_url:
|
||||||
return _send_llama_native(...)
|
return _send_llama_native(...)
|
||||||
```
|
```
|
||||||
|
|
||||||
For OpenRouter, custom URLs, and other cloud Llama endpoints, the existing OpenAI-compat path is unchanged.
|
For OpenRouter, custom URLs, and other cloud Llama endpoints, the existing OpenAI-compat path is unchanged.
|
||||||
@@ -714,11 +714,11 @@ The test in `tests/test_aggregate_caching.py` ensures the first N characters of
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def test_aggregate_stable_to_volatile_ordering():
|
def test_aggregate_stable_to_volatile_ordering():
|
||||||
ctrl = mock_app_controller()
|
ctrl = mock_app_controller()
|
||||||
turn1 = aggregate.build_initial_context(ctrl, user_message="first")
|
turn1 = aggregate.build_initial_context(ctrl, user_message="first")
|
||||||
turn2 = aggregate.build_initial_context(ctrl, user_message="second")
|
turn2 = aggregate.build_initial_context(ctrl, user_message="second")
|
||||||
N = aggregate.stable_prefix_length(ctrl)
|
N = aggregate.stable_prefix_length(ctrl)
|
||||||
assert turn1[:N] == turn2[:N], f"Stable prefix mismatch: {turn1[:N]!r} != {turn2[:N]!r}"
|
assert turn1[:N] == turn2[:N], f"Stable prefix mismatch: {turn1[:N]!r} != {turn2[:N]!r}"
|
||||||
```
|
```
|
||||||
|
|
||||||
**The test is the contract.** If a new layer is added in the wrong position, the test fails; the agent must move the layer to the stable position or update the test with written justification.
|
**The test is the contract.** If a new layer is added in the wrong position, the test fails; the agent must move the layer to the stable position or update the test with written justification.
|
||||||
@@ -729,17 +729,17 @@ def test_aggregate_stable_to_volatile_ordering():
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def _send_anthropic(messages, *, cache_prefix_chars=None):
|
def _send_anthropic(messages, *, cache_prefix_chars=None):
|
||||||
if cache_prefix_chars is not None:
|
if cache_prefix_chars is not None:
|
||||||
content_blocks = cache_prefix_blocks(messages, cache_prefix_chars)
|
content_blocks = cache_prefix_blocks(messages, cache_prefix_chars)
|
||||||
else:
|
else:
|
||||||
content_blocks = messages
|
content_blocks = messages
|
||||||
|
|
||||||
response = anthropic_client.messages.create(
|
response = anthropic_client.messages.create(
|
||||||
model=model,
|
model=model,
|
||||||
max_tokens=8192,
|
max_tokens=8192,
|
||||||
messages=[{"role": "user", "content": content_blocks}],
|
messages=[{"role": "user", "content": content_blocks}],
|
||||||
)
|
)
|
||||||
return _result_with_usage(response.content, response.usage, messages)
|
return _result_with_usage(response.content, response.usage, messages)
|
||||||
```
|
```
|
||||||
|
|
||||||
**The `cache_prefix_blocks` helper** splits the message at the given char offsets and marks each prefix with `cache_control: {"type": "ephemeral"}`. Max 3 prefix blocks (provider limit is 4 breakpoints per request).
|
**The `cache_prefix_blocks` helper** splits the message at the given char offsets and marks each prefix with `cache_control: {"type": "ephemeral"}`. Max 3 prefix blocks (provider limit is 4 breakpoints per request).
|
||||||
@@ -750,17 +750,17 @@ def _send_anthropic(messages, *, cache_prefix_chars=None):
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def _send_gemini(messages, *, cache_ttl_seconds=3600):
|
def _send_gemini(messages, *, cache_ttl_seconds=3600):
|
||||||
if cache_ttl_seconds > 0:
|
if cache_ttl_seconds > 0:
|
||||||
cached_content = genai_client.caches.create(
|
cached_content = genai_client.caches.create(
|
||||||
model=model, contents=stable_prefix_messages, ttl=f"{cache_ttl_seconds}s",
|
model=model, contents=stable_prefix_messages, ttl=f"{cache_ttl_seconds}s",
|
||||||
)
|
)
|
||||||
response = genai_client.models.generate_content(
|
response = genai_client.models.generate_content(
|
||||||
model=model, contents=volatile_messages,
|
model=model, contents=volatile_messages,
|
||||||
config=genai.types.GenerateContentConfig(cached_content=cached_content.name),
|
config=genai.types.GenerateContentConfig(cached_content=cached_content.name),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
response = genai_client.models.generate_content(model=model, contents=messages)
|
response = genai_client.models.generate_content(model=model, contents=messages)
|
||||||
return _result_with_usage(response.text, response.usage_metadata, messages)
|
return _result_with_usage(response.text, response.usage_metadata, messages)
|
||||||
```
|
```
|
||||||
|
|
||||||
**The default TTL is 1 hour**; configurable per-discussion via the GUI.
|
**The default TTL is 1 hour**; configurable per-discussion via the GUI.
|
||||||
@@ -783,21 +783,21 @@ No application-side control; the provider handles caching. The GUI just shows "C
|
|||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class DiscussionCacheState:
|
class DiscussionCacheState:
|
||||||
discussion_id: str
|
discussion_id: str
|
||||||
provider: str
|
provider: str
|
||||||
cached_at: datetime
|
cached_at: datetime
|
||||||
expires_at: Optional[datetime] # None for OpenAI implicit
|
expires_at: Optional[datetime] # None for OpenAI implicit
|
||||||
hit_count: int = 0
|
hit_count: int = 0
|
||||||
tokens_cached: int = 0
|
tokens_cached: int = 0
|
||||||
last_invalidated_at: Optional[datetime] = None
|
last_invalidated_at: Optional[datetime] = None
|
||||||
caching_enabled: bool = True
|
caching_enabled: bool = True
|
||||||
```
|
```
|
||||||
|
|
||||||
**The Hook API additions:**
|
**The Hook API additions:**
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/cache # list all discussion cache states
|
GET /api/cache # list all discussion cache states
|
||||||
GET /api/cache/<discussion_id> # get one
|
GET /api/cache/<discussion_id> # get one
|
||||||
POST /api/cache/<discussion_id>/invalidate
|
POST /api/cache/<discussion_id>/invalidate
|
||||||
POST /api/cache/<discussion_id>/disable
|
POST /api/cache/<discussion_id>/disable
|
||||||
POST /api/cache/<discussion_id>/enable
|
POST /api/cache/<discussion_id>/enable
|
||||||
@@ -809,15 +809,15 @@ POST /api/cache/<discussion_id>/enable
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def _send_claude_code(message, model, *, allowed_tools=None, max_turns=1):
|
def _send_claude_code(message, model, *, allowed_tools=None, max_turns=1):
|
||||||
options = ClaudeAgentOptions(
|
options = ClaudeAgentOptions(
|
||||||
model=None if not model or model == "default" else model,
|
model=None if not model or model == "default" else model,
|
||||||
max_turns=max_turns,
|
max_turns=max_turns,
|
||||||
tools=list(allowed_tools) if allowed_tools else [],
|
tools=list(allowed_tools) if allowed_tools else [],
|
||||||
allowed_tools=list(allowed_tools) if allowed_tools else [],
|
allowed_tools=list(allowed_tools) if allowed_tools else [],
|
||||||
cwd=os.getcwd(),
|
cwd=os.getcwd(),
|
||||||
)
|
)
|
||||||
# ... claude_agent_sdk.query(prompt=message, options=options)
|
# ... claude_agent_sdk.query(prompt=message, options=options)
|
||||||
return _result_with_usage(text, usage, message)
|
return _result_with_usage(text, usage, message)
|
||||||
```
|
```
|
||||||
|
|
||||||
### The cross-references
|
### The cross-references
|
||||||
|
|||||||
+285
-285
@@ -20,20 +20,20 @@ The codebase is organized into a `src/` layout to separate implementation from c
|
|||||||
|
|
||||||
```
|
```
|
||||||
manual_slop/
|
manual_slop/
|
||||||
├── conductor/ # Conductor tracks, specs, and plans
|
├── conductor/ # Conductor tracks, specs, and plans
|
||||||
├── docs/ # Deep-dive architectural documentation
|
├── docs/ # Deep-dive architectural documentation
|
||||||
├── logs/ # Session logs, agent traces, and errors
|
├── logs/ # Session logs, agent traces, and errors
|
||||||
├── scripts/ # Build, migration, and IPC bridge scripts
|
├── scripts/ # Build, migration, and IPC bridge scripts
|
||||||
├── src/ # Core Python implementation
|
├── src/ # Core Python implementation
|
||||||
│ ├── ai_client.py # LLM provider abstraction
|
│ ├── ai_client.py # LLM provider abstraction
|
||||||
│ ├── gui_2.py # Main ImGui application
|
│ ├── gui_2.py # Main ImGui application
|
||||||
│ ├── mcp_client.py # MCP tool implementation
|
│ ├── mcp_client.py # MCP tool implementation
|
||||||
│ └── ... # Other core modules
|
│ └── ... # Other core modules
|
||||||
├── tests/ # Pytest suite and simulation fixtures
|
├── tests/ # Pytest suite and simulation fixtures
|
||||||
├── simulation/ # Workflow and agent simulation logic
|
├── simulation/ # Workflow and agent simulation logic
|
||||||
├── sloppy.py # Primary application entry point
|
├── sloppy.py # Primary application entry point
|
||||||
├── config.toml # Global application settings
|
├── config.toml # Global application settings
|
||||||
└── manual_slop.toml # Project-specific configuration
|
└── manual_slop.toml # Project-specific configuration
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -59,9 +59,9 @@ self._loop_thread.start()
|
|||||||
|
|
||||||
# _run_event_loop:
|
# _run_event_loop:
|
||||||
def _run_event_loop(self) -> None:
|
def _run_event_loop(self) -> None:
|
||||||
asyncio.set_event_loop(self._loop)
|
asyncio.set_event_loop(self._loop)
|
||||||
self._loop.create_task(self._process_event_queue())
|
self._loop.create_task(self._process_event_queue())
|
||||||
self._loop.run_forever()
|
self._loop.run_forever()
|
||||||
```
|
```
|
||||||
|
|
||||||
The GUI thread uses `asyncio.run_coroutine_threadsafe(coro, self._loop)` to push work into this loop.
|
The GUI thread uses `asyncio.run_coroutine_threadsafe(coro, self._loop)` to push work into this loop.
|
||||||
@@ -75,12 +75,12 @@ For concurrent multi-agent execution, the application uses `threading.local()` t
|
|||||||
_local_storage = threading.local()
|
_local_storage = threading.local()
|
||||||
|
|
||||||
def get_current_tier() -> Optional[str]:
|
def get_current_tier() -> Optional[str]:
|
||||||
"""Returns the current tier from thread-local storage."""
|
"""Returns the current tier from thread-local storage."""
|
||||||
return getattr(_local_storage, "current_tier", None)
|
return getattr(_local_storage, "current_tier", None)
|
||||||
|
|
||||||
def set_current_tier(tier: Optional[str]) -> None:
|
def set_current_tier(tier: Optional[str]) -> None:
|
||||||
"""Sets the current tier in thread-local storage."""
|
"""Sets the current tier in thread-local storage."""
|
||||||
_local_storage.current_tier = tier
|
_local_storage.current_tier = tier
|
||||||
```
|
```
|
||||||
|
|
||||||
This ensures that comms log entries and tool calls are correctly tagged with their source tier even when multiple workers execute concurrently.
|
This ensures that comms log entries and tool calls are correctly tagged with their source tier even when multiple workers execute concurrently.
|
||||||
@@ -96,10 +96,10 @@ All cross-thread communication uses one of three patterns:
|
|||||||
```python
|
```python
|
||||||
# events.py
|
# events.py
|
||||||
class AsyncEventQueue:
|
class AsyncEventQueue:
|
||||||
_queue: asyncio.Queue # holds Tuple[str, Any] items
|
_queue: asyncio.Queue # holds Tuple[str, Any] items
|
||||||
|
|
||||||
async def put(self, event_name: str, payload: Any = None) -> None
|
async def put(self, event_name: str, payload: Any = None) -> None
|
||||||
async def get(self) -> Tuple[str, Any]
|
async def get(self) -> Tuple[str, Any]
|
||||||
```
|
```
|
||||||
|
|
||||||
The central event bus. Uses `asyncio.Queue`, so non-asyncio threads must enqueue via `asyncio.run_coroutine_threadsafe()`. Consumer is `App._process_event_queue()`, running as a long-lived coroutine on the asyncio loop.
|
The central event bus. Uses `asyncio.Queue`, so non-asyncio threads must enqueue via `asyncio.run_coroutine_threadsafe()`. Consumer is `App._process_event_queue()`, running as a long-lived coroutine on the asyncio loop.
|
||||||
@@ -125,8 +125,8 @@ self._pending_history_adds_lock = threading.Lock()
|
|||||||
|
|
||||||
Additional locks:
|
Additional locks:
|
||||||
```python
|
```python
|
||||||
self._send_thread_lock = threading.Lock() # Guards send_thread creation
|
self._send_thread_lock = threading.Lock() # Guards send_thread creation
|
||||||
self._pending_dialog_lock = threading.Lock() # Guards _pending_dialog + _pending_actions dict
|
self._pending_dialog_lock = threading.Lock() # Guards _pending_dialog + _pending_actions dict
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pattern C: Condition-Variable Dialogs (Bidirectional Blocking)
|
### Pattern C: Condition-Variable Dialogs (Bidirectional Blocking)
|
||||||
@@ -143,10 +143,10 @@ Three classes in `events.py` (89 lines, no external dependencies beyond `asyncio
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class EventEmitter:
|
class EventEmitter:
|
||||||
_listeners: Dict[str, List[Callable]]
|
_listeners: Dict[str, List[Callable]]
|
||||||
|
|
||||||
def on(self, event_name: str, callback: Callable) -> None
|
def on(self, event_name: str, callback: Callable) -> None
|
||||||
def emit(self, event_name: str, *args: Any, **kwargs: Any) -> None
|
def emit(self, event_name: str, *args: Any, **kwargs: Any) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Synchronous pub-sub. Callbacks execute in the caller's thread. Used by `ai_client.events` for lifecycle hooks (`request_start`, `response_received`, `tool_execution`). No thread safety — relies on consistent single-thread usage.
|
Synchronous pub-sub. Callbacks execute in the caller's thread. Used by `ai_client.events` for lifecycle hooks (`request_start`, `response_received`, `tool_execution`). No thread safety — relies on consistent single-thread usage.
|
||||||
@@ -159,13 +159,13 @@ Described above in Pattern A.
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class UserRequestEvent:
|
class UserRequestEvent:
|
||||||
prompt: str # User's raw input text
|
prompt: str # User's raw input text
|
||||||
stable_md: str # Generated markdown context (files, screenshots)
|
stable_md: str # Generated markdown context (files, screenshots)
|
||||||
file_items: List[Any] # File attachment items for dynamic refresh
|
file_items: List[Any] # File attachment items for dynamic refresh
|
||||||
disc_text: str # Serialized discussion history
|
disc_text: str # Serialized discussion history
|
||||||
base_dir: str # Working directory for shell commands
|
base_dir: str # Working directory for shell commands
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]
|
def to_dict(self) -> Dict[str, Any]
|
||||||
```
|
```
|
||||||
|
|
||||||
Pure data carrier. Created on the GUI thread in `_handle_generate_send`, consumed on the asyncio thread in `_handle_request_event`.
|
Pure data carrier. Created on the GUI thread in `_handle_generate_send`, consumed on the asyncio thread in `_handle_request_event`.
|
||||||
@@ -180,8 +180,8 @@ The `App.__init__` (lines 152-296) follows this precise order:
|
|||||||
|
|
||||||
1. **Config hydration**: Reads `config.toml` (global) and `<project>.toml` (local). Builds the initial "world view" — tracked files, discussion history, active models.
|
1. **Config hydration**: Reads `config.toml` (global) and `<project>.toml` (local). Builds the initial "world view" — tracked files, discussion history, active models.
|
||||||
2. **Thread bootstrapping**:
|
2. **Thread bootstrapping**:
|
||||||
- Asyncio event loop thread starts (`_loop_thread`).
|
- Asyncio event loop thread starts (`_loop_thread`).
|
||||||
- `HookServer` starts as a daemon if `test_hooks_enabled` or provider is `gemini_cli`.
|
- `HookServer` starts as a daemon if `test_hooks_enabled` or provider is `gemini_cli`.
|
||||||
3. **Callback wiring** (`_init_ai_and_hooks`): Connects `ai_client.confirm_and_run_callback`, `comms_log_callback`, `tool_log_callback` to GUI handlers.
|
3. **Callback wiring** (`_init_ai_and_hooks`): Connects `ai_client.confirm_and_run_callback`, `comms_log_callback`, `tool_log_callback` to GUI handlers.
|
||||||
4. **UI entry**: Main thread enters `immapp.run()`. GUI is now alive; background threads are ready.
|
4. **UI entry**: Main thread enters `immapp.run()`. GUI is now alive; background threads are ready.
|
||||||
|
|
||||||
@@ -199,10 +199,10 @@ The asyncio loop thread is a daemon — it dies with the process. `App.shutdown(
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
if self._loop.is_running():
|
if self._loop.is_running():
|
||||||
self._loop.call_soon_threadsafe(self._loop.stop)
|
self._loop.call_soon_threadsafe(self._loop.stop)
|
||||||
if self._loop_thread.is_alive():
|
if self._loop_thread.is_alive():
|
||||||
self._loop_thread.join(timeout=2.0)
|
self._loop_thread.join(timeout=2.0)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -212,25 +212,25 @@ def shutdown(self) -> None:
|
|||||||
### Request Flow
|
### Request Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
GUI Thread Asyncio Thread GUI Thread (next frame)
|
GUI Thread Asyncio Thread GUI Thread (next frame)
|
||||||
────────── ────────────── ──────────────────────
|
────────── ────────────── ──────────────────────
|
||||||
1. User clicks "Gen + Send"
|
1. User clicks "Gen + Send"
|
||||||
2. _handle_generate_send():
|
2. _handle_generate_send():
|
||||||
- Compiles md context
|
- Compiles md context
|
||||||
- Creates UserRequestEvent
|
- Creates UserRequestEvent
|
||||||
- Enqueues via
|
- Enqueues via
|
||||||
run_coroutine_threadsafe ──> 3. _process_event_queue():
|
run_coroutine_threadsafe ──> 3. _process_event_queue():
|
||||||
awaits event_queue.get()
|
awaits event_queue.get()
|
||||||
routes "user_request" to
|
routes "user_request" to
|
||||||
_handle_request_event()
|
_handle_request_event()
|
||||||
4. Configures ai_client
|
4. Configures ai_client
|
||||||
5. ai_client.send() BLOCKS
|
5. ai_client.send() BLOCKS
|
||||||
(seconds to minutes)
|
(seconds to minutes)
|
||||||
6. On completion, enqueues
|
6. On completion, enqueues
|
||||||
"response" event back ──> 7. _process_pending_gui_tasks():
|
"response" event back ──> 7. _process_pending_gui_tasks():
|
||||||
Drains task list under lock
|
Drains task list under lock
|
||||||
Sets ai_response text
|
Sets ai_response text
|
||||||
Triggers terminal blink
|
Triggers terminal blink
|
||||||
```
|
```
|
||||||
|
|
||||||
### Event Types Routed by `_process_event_queue`
|
### Event Types Routed by `_process_event_queue`
|
||||||
@@ -253,13 +253,13 @@ Called once per ImGui frame on the **main GUI thread**. This is the sole safe po
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def _process_pending_gui_tasks(self) -> None:
|
def _process_pending_gui_tasks(self) -> None:
|
||||||
if not self._pending_gui_tasks:
|
if not self._pending_gui_tasks:
|
||||||
return
|
return
|
||||||
with self._pending_gui_tasks_lock:
|
with self._pending_gui_tasks_lock:
|
||||||
tasks = self._pending_gui_tasks[:] # Snapshot
|
tasks = self._pending_gui_tasks[:] # Snapshot
|
||||||
self._pending_gui_tasks.clear() # Release lock fast
|
self._pending_gui_tasks.clear() # Release lock fast
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
# Process each task outside the lock
|
# Process each task outside the lock
|
||||||
```
|
```
|
||||||
|
|
||||||
Acquires the lock briefly to snapshot the task list, then processes outside the lock. Minimizes lock contention with producer threads.
|
Acquires the lock briefly to snapshot the task list, then processes outside the lock. Minimizes lock contention with producer threads.
|
||||||
@@ -294,43 +294,43 @@ The "Execution Clutch" ensures every destructive AI action passes through an aud
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class ConfirmDialog:
|
class ConfirmDialog:
|
||||||
_uid: str # uuid4 identifier
|
_uid: str # uuid4 identifier
|
||||||
_script: str # The PowerShell script text (editable)
|
_script: str # The PowerShell script text (editable)
|
||||||
_base_dir: str # Working directory
|
_base_dir: str # Working directory
|
||||||
_condition: threading.Condition # Blocking primitive
|
_condition: threading.Condition # Blocking primitive
|
||||||
_done: bool # Signal flag
|
_done: bool # Signal flag
|
||||||
_approved: bool # User's decision
|
_approved: bool # User's decision
|
||||||
|
|
||||||
def wait(self) -> tuple[bool, str] # Blocks until _done; returns (approved, script)
|
def wait(self) -> tuple[bool, str] # Blocks until _done; returns (approved, script)
|
||||||
```
|
```
|
||||||
|
|
||||||
**`MMAApprovalDialog`** — MMA tier step approval:
|
**`MMAApprovalDialog`** — MMA tier step approval:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class MMAApprovalDialog:
|
class MMAApprovalDialog:
|
||||||
_ticket_id: str
|
_ticket_id: str
|
||||||
_payload: str # The step payload (editable)
|
_payload: str # The step payload (editable)
|
||||||
_condition: threading.Condition
|
_condition: threading.Condition
|
||||||
_done: bool
|
_done: bool
|
||||||
_approved: bool
|
_approved: bool
|
||||||
|
|
||||||
def wait(self) -> tuple[bool, str] # Returns (approved, payload)
|
def wait(self) -> tuple[bool, str] # Returns (approved, payload)
|
||||||
```
|
```
|
||||||
|
|
||||||
**`MMASpawnApprovalDialog`** — Sub-agent spawn approval:
|
**`MMASpawnApprovalDialog`** — Sub-agent spawn approval:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class MMASpawnApprovalDialog:
|
class MMASpawnApprovalDialog:
|
||||||
_ticket_id: str
|
_ticket_id: str
|
||||||
_role: str # tier3-worker, tier4-qa, etc.
|
_role: str # tier3-worker, tier4-qa, etc.
|
||||||
_prompt: str # Spawn prompt (editable)
|
_prompt: str # Spawn prompt (editable)
|
||||||
_context_md: str # Context document (editable)
|
_context_md: str # Context document (editable)
|
||||||
_condition: threading.Condition
|
_condition: threading.Condition
|
||||||
_done: bool
|
_done: bool
|
||||||
_approved: bool
|
_approved: bool
|
||||||
_abort: bool # Can abort entire track
|
_abort: bool # Can abort entire track
|
||||||
|
|
||||||
def wait(self) -> dict[str, Any] # Returns {approved, abort, prompt, context_md}
|
def wait(self) -> dict[str, Any] # Returns {approved, abort, prompt, context_md}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Blocking Flow
|
### Blocking Flow
|
||||||
@@ -338,27 +338,27 @@ class MMASpawnApprovalDialog:
|
|||||||
Using `ConfirmDialog` as exemplar:
|
Using `ConfirmDialog` as exemplar:
|
||||||
|
|
||||||
```
|
```
|
||||||
ASYNCIO THREAD (ai_client tool callback) GUI MAIN THREAD
|
ASYNCIO THREAD (ai_client tool callback) GUI MAIN THREAD
|
||||||
───────────────────────────────────────── ───────────────
|
───────────────────────────────────────── ───────────────
|
||||||
1. ai_client calls _confirm_and_run(script)
|
1. ai_client calls _confirm_and_run(script)
|
||||||
2. Creates ConfirmDialog(script, base_dir)
|
2. Creates ConfirmDialog(script, base_dir)
|
||||||
3. Stores dialog:
|
3. Stores dialog:
|
||||||
- Headless: _pending_actions[uid] = dialog
|
- Headless: _pending_actions[uid] = dialog
|
||||||
- GUI mode: _pending_dialog = dialog
|
- GUI mode: _pending_dialog = dialog
|
||||||
4. If test_hooks_enabled:
|
4. If test_hooks_enabled:
|
||||||
pushes to _api_event_queue
|
pushes to _api_event_queue
|
||||||
5. dialog.wait() BLOCKS on _condition
|
5. dialog.wait() BLOCKS on _condition
|
||||||
6. Next frame: ImGui renders
|
6. Next frame: ImGui renders
|
||||||
_pending_dialog in modal
|
_pending_dialog in modal
|
||||||
7. User clicks Approve/Reject
|
7. User clicks Approve/Reject
|
||||||
8. _handle_approve_script():
|
8. _handle_approve_script():
|
||||||
with dialog._condition:
|
with dialog._condition:
|
||||||
dialog._approved = True
|
dialog._approved = True
|
||||||
dialog._done = True
|
dialog._done = True
|
||||||
dialog._condition.notify_all()
|
dialog._condition.notify_all()
|
||||||
9. wait() returns (True, potentially_edited_script)
|
9. wait() returns (True, potentially_edited_script)
|
||||||
10. Executes shell_runner.run_powershell()
|
10. Executes shell_runner.run_powershell()
|
||||||
11. Returns output to ai_client
|
11. Returns output to ai_client
|
||||||
```
|
```
|
||||||
|
|
||||||
The `_condition.wait(timeout=0.1)` uses a 100ms polling interval inside a loop — a polling-with-condition hybrid that ensures the blocking thread wakes periodically.
|
The `_condition.wait(timeout=0.1)` uses a 100ms polling interval inside a loop — a polling-with-condition hybrid that ensures the blocking thread wakes periodically.
|
||||||
@@ -373,14 +373,14 @@ The `_condition.wait(timeout=0.1)` uses a 100ms polling interval inside a loop
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def resolve_pending_action(self, action_id: str, approved: bool) -> bool:
|
def resolve_pending_action(self, action_id: str, approved: bool) -> bool:
|
||||||
with self._pending_dialog_lock:
|
with self._pending_dialog_lock:
|
||||||
if action_id in self._pending_actions:
|
if action_id in self._pending_actions:
|
||||||
dialog = self._pending_actions[action_id]
|
dialog = self._pending_actions[action_id]
|
||||||
with dialog._condition:
|
with dialog._condition:
|
||||||
dialog._approved = approved
|
dialog._approved = approved
|
||||||
dialog._done = True
|
dialog._done = True
|
||||||
dialog._condition.notify_all()
|
dialog._condition.notify_all()
|
||||||
return True
|
return True
|
||||||
```
|
```
|
||||||
|
|
||||||
**MMA approval path**:
|
**MMA approval path**:
|
||||||
@@ -395,14 +395,14 @@ def resolve_pending_action(self, action_id: str, approved: bool) -> bool:
|
|||||||
### Module-Level State
|
### Module-Level State
|
||||||
|
|
||||||
```python
|
```python
|
||||||
_provider: str = "gemini" # "gemini" | "anthropic" | "deepseek" | "gemini_cli" | "minimax"
|
_provider: str = "gemini" # "gemini" | "anthropic" | "deepseek" | "gemini_cli" | "minimax"
|
||||||
_model: str = "gemini-2.5-flash-lite"
|
_model: str = "gemini-2.5-flash-lite"
|
||||||
_temperature: float = 0.0
|
_temperature: float = 0.0
|
||||||
_top_p: float = 1.0
|
_top_p: float = 1.0
|
||||||
_max_tokens: int = 8192
|
_max_tokens: int = 8192
|
||||||
_history_trunc_limit: int = 8000 # Char limit for truncating old tool outputs
|
_history_trunc_limit: int = 8000 # Char limit for truncating old tool outputs
|
||||||
|
|
||||||
_send_lock: threading.Lock # Serializes ALL send() calls across providers
|
_send_lock: threading.Lock # Serializes ALL send() calls across providers
|
||||||
```
|
```
|
||||||
|
|
||||||
Per-provider client objects:
|
Per-provider client objects:
|
||||||
@@ -410,16 +410,16 @@ Per-provider client objects:
|
|||||||
```python
|
```python
|
||||||
# Gemini (SDK-managed stateful chat)
|
# Gemini (SDK-managed stateful chat)
|
||||||
_gemini_client: genai.Client | None
|
_gemini_client: genai.Client | None
|
||||||
_gemini_chat: Any # Holds history internally
|
_gemini_chat: Any # Holds history internally
|
||||||
_gemini_cache: Any # Server-side CachedContent
|
_gemini_cache: Any # Server-side CachedContent
|
||||||
_gemini_cache_md_hash: str | None # Hash for cache invalidation
|
_gemini_cache_md_hash: str | None # Hash for cache invalidation
|
||||||
_gemini_cache_created_at: float | None # Monotonic time of cache creation
|
_gemini_cache_created_at: float | None # Monotonic time of cache creation
|
||||||
_gemini_cached_file_paths: list[str] # File paths included in the active cache
|
_gemini_cached_file_paths: list[str] # File paths included in the active cache
|
||||||
_GEMINI_CACHE_TTL: int = 3600 # 1-hour; rebuilt at 90% (3240s)
|
_GEMINI_CACHE_TTL: int = 3600 # 1-hour; rebuilt at 90% (3240s)
|
||||||
|
|
||||||
# Anthropic (client-managed history)
|
# Anthropic (client-managed history)
|
||||||
_anthropic_client: anthropic.Anthropic | None
|
_anthropic_client: anthropic.Anthropic | None
|
||||||
_anthropic_history: list[dict] # Mutable [{role, content}, ...]
|
_anthropic_history: list[dict] # Mutable [{role, content}, ...]
|
||||||
_anthropic_history_lock: threading.Lock
|
_anthropic_history_lock: threading.Lock
|
||||||
|
|
||||||
# DeepSeek (raw HTTP, client-managed history)
|
# DeepSeek (raw HTTP, client-managed history)
|
||||||
@@ -439,27 +439,27 @@ _gemini_cli_adapter: GeminiCliAdapter | None
|
|||||||
Safety limits:
|
Safety limits:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
MAX_TOOL_ROUNDS: int = 10 # Max tool-call loop iterations per send()
|
MAX_TOOL_ROUNDS: int = 10 # Max tool-call loop iterations per send()
|
||||||
_MAX_TOOL_OUTPUT_BYTES: int = 500_000 # 500KB cumulative tool output budget
|
_MAX_TOOL_OUTPUT_BYTES: int = 500_000 # 500KB cumulative tool output budget
|
||||||
_ANTHROPIC_CHUNK_SIZE: int = 120_000 # Max chars per system text block
|
_ANTHROPIC_CHUNK_SIZE: int = 120_000 # Max chars per system text block
|
||||||
_ANTHROPIC_MAX_PROMPT_TOKENS: int = 180_000 # 200k limit minus headroom
|
_ANTHROPIC_MAX_PROMPT_TOKENS: int = 180_000 # 200k limit minus headroom
|
||||||
_GEMINI_MAX_INPUT_TOKENS: int = 900_000 # 1M window minus headroom
|
_GEMINI_MAX_INPUT_TOKENS: int = 900_000 # 1M window minus headroom
|
||||||
```
|
```
|
||||||
|
|
||||||
### The `send()` Dispatcher
|
### The `send()` Dispatcher
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def send(md_content, user_message, base_dir=".", file_items=None,
|
def send(md_content, user_message, base_dir=".", file_items=None,
|
||||||
discussion_history="", stream=False,
|
discussion_history="", stream=False,
|
||||||
pre_tool_callback=None, qa_callback=None,
|
pre_tool_callback=None, qa_callback=None,
|
||||||
enable_tools=True, stream_callback=None, patch_callback=None,
|
enable_tools=True, stream_callback=None, patch_callback=None,
|
||||||
rag_engine=None) -> str:
|
rag_engine=None) -> str:
|
||||||
with _send_lock:
|
with _send_lock:
|
||||||
if _provider == "gemini": return _send_gemini(...)
|
if _provider == "gemini": return _send_gemini(...)
|
||||||
elif _provider == "gemini_cli": return _send_gemini_cli(...)
|
elif _provider == "gemini_cli": return _send_gemini_cli(...)
|
||||||
elif _provider == "anthropic": return _send_anthropic(...)
|
elif _provider == "anthropic": return _send_anthropic(...)
|
||||||
elif _provider == "deepseek": return _send_deepseek(..., stream=stream)
|
elif _provider == "deepseek": return _send_deepseek(..., stream=stream)
|
||||||
elif _provider == "minimax": return _send_minimax(..., stream=stream)
|
elif _provider == "minimax": return _send_minimax(..., stream=stream)
|
||||||
```
|
```
|
||||||
|
|
||||||
`_send_lock` serializes all API calls — only one provider call can be in-flight at a time. All providers share the same callback signatures. Return type is always `str`.
|
`_send_lock` serializes all API calls — only one provider call can be in-flight at a time. All providers share the same callback signatures. Return type is always `str`.
|
||||||
@@ -496,11 +496,11 @@ All providers follow the same high-level loop, iterated up to `MAX_TOOL_ROUNDS +
|
|||||||
3. Log to comms log; emit events.
|
3. Log to comms log; emit events.
|
||||||
4. If no function calls or max rounds exceeded: **break**.
|
4. If no function calls or max rounds exceeded: **break**.
|
||||||
5. For each function call:
|
5. For each function call:
|
||||||
- If `pre_tool_callback` rejects: return rejection text.
|
- If `pre_tool_callback` rejects: return rejection text.
|
||||||
- Dispatch to `mcp_client.dispatch()` or `shell_runner.run_powershell()`.
|
- Dispatch to `mcp_client.dispatch()` or `shell_runner.run_powershell()`.
|
||||||
- After the **last** call of this round: run `_reread_file_items()` for context refresh.
|
- After the **last** call of this round: run `_reread_file_items()` for context refresh.
|
||||||
- Truncate tool output at `_history_trunc_limit` chars.
|
- Truncate tool output at `_history_trunc_limit` chars.
|
||||||
- Accumulate `_cumulative_tool_bytes`.
|
- Accumulate `_cumulative_tool_bytes`.
|
||||||
6. If cumulative bytes > 500KB: inject warning.
|
6. If cumulative bytes > 500KB: inject warning.
|
||||||
7. Package tool results in provider-specific format; loop.
|
7. Package tool results in provider-specific format; loop.
|
||||||
|
|
||||||
@@ -512,8 +512,8 @@ After the last tool call in each round, `_reread_file_items(file_items)` checks
|
|||||||
2. If unchanged: pass through as-is.
|
2. If unchanged: pass through as-is.
|
||||||
3. If changed: re-read content, store `old_content` for diffing, update `mtime`.
|
3. If changed: re-read content, store `old_content` for diffing, update `mtime`.
|
||||||
4. Changed files are diffed via `_build_file_diff_text`:
|
4. Changed files are diffed via `_build_file_diff_text`:
|
||||||
- Files <= 200 lines: emit full content.
|
- Files <= 200 lines: emit full content.
|
||||||
- Files > 200 lines with `old_content`: emit `difflib.unified_diff`.
|
- Files > 200 lines with `old_content`: emit `difflib.unified_diff`.
|
||||||
5. Diff is appended to the last tool's output as `[SYSTEM: FILES UPDATED]\n\n{diff}`.
|
5. Diff is appended to the last tool's output as `[SYSTEM: FILES UPDATED]\n\n{diff}`.
|
||||||
6. Stale `[FILES UPDATED]` blocks are stripped from older history turns by `_strip_stale_file_refreshes` to prevent context bloat.
|
6. Stale `[FILES UPDATED]` blocks are stripped from older history turns by `_strip_stale_file_refreshes` to prevent context bloat.
|
||||||
|
|
||||||
@@ -550,27 +550,27 @@ Independent tool calls within a single round execute concurrently via `asyncio.g
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
async def _execute_tool_calls_concurrently(
|
async def _execute_tool_calls_concurrently(
|
||||||
calls: list[Any],
|
calls: list[Any],
|
||||||
base_dir: str,
|
base_dir: str,
|
||||||
pre_tool_callback: ...,
|
pre_tool_callback: ...,
|
||||||
qa_callback: ...,
|
qa_callback: ...,
|
||||||
r_idx: int,
|
r_idx: int,
|
||||||
provider: str,
|
provider: str,
|
||||||
patch_callback: ... = None,
|
patch_callback: ... = None,
|
||||||
) -> list[tuple[str, str, str, str]]: # (tool_name, call_id, output, original_name)
|
) -> list[tuple[str, str, str, str]]: # (tool_name, call_id, output, original_name)
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Per-Call Worker
|
### Per-Call Worker
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def _execute_single_tool_call_async(
|
async def _execute_single_tool_call_async(
|
||||||
name: str, args: dict, call_id: str, base_dir: str,
|
name: str, args: dict, call_id: str, base_dir: str,
|
||||||
pre_tool_callback, qa_callback, r_idx: int,
|
pre_tool_callback, qa_callback, r_idx: int,
|
||||||
tier: str | None = None,
|
tier: str | None = None,
|
||||||
patch_callback = None,
|
patch_callback = None,
|
||||||
) -> tuple[str, str, str, str]:
|
) -> tuple[str, str, str, str]:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
`tier: str | None` is propagated to the comms log and pre-tool callback so audit trails can attribute tool calls to a specific MMA tier (e.g., "Tier 3", "Tier 4"). Thread-local `_local_storage.current_tier` is the source; the parameter is the explicit pass-through.
|
`tier: str | None` is propagated to the comms log and pre-tool callback so audit trails can attribute tool calls to a specific MMA tier (e.g., "Tier 3", "Tier 4"). Thread-local `_local_storage.current_tier` is the source; the parameter is the explicit pass-through.
|
||||||
@@ -587,11 +587,11 @@ If any individual call raises, `asyncio.gather` with `return_exceptions=True` co
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def send(md_content, user_message, base_dir=".", file_items=None, ...,
|
def send(md_content, user_message, base_dir=".", file_items=None, ...,
|
||||||
rag_engine: Optional[Any] = None) -> str:
|
rag_engine: Optional[Any] = None) -> str:
|
||||||
if rag_engine is not None:
|
if rag_engine is not None:
|
||||||
retrieved = rag_engine.query(user_message, top_k=5)
|
retrieved = rag_engine.query(user_message, top_k=5)
|
||||||
md_content = _inject_rag_context(md_content, retrieved)
|
md_content = _inject_rag_context(md_content, retrieved)
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
The RAG engine is **not** owned by `ai_client`; the caller (typically `AppController` for the main discussion flow, or `multi_agent_conductor.run_worker_lifecycle` for Tier 3 workers) is responsible for instantiating and configuring it. This keeps `ai_client` decoupled from any specific retrieval backend (ChromaDB local, external MCP RAG server, or none).
|
The RAG engine is **not** owned by `ai_client`; the caller (typically `AppController` for the main discussion flow, or `multi_agent_conductor.run_worker_lifecycle` for Tier 3 workers) is responsible for instantiating and configuring it. This keeps `ai_client` decoupled from any specific retrieval backend (ChromaDB local, external MCP RAG server, or none).
|
||||||
@@ -612,7 +612,7 @@ When a Tier 3 worker's test run fails, the engine can request a Tier 4 patch ins
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def run_tier4_patch_generation(error: str, file_context: str) -> str:
|
def run_tier4_patch_generation(error: str, file_context: str) -> str:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Flow
|
### Flow
|
||||||
@@ -637,7 +637,7 @@ Long discussions accumulate tool outputs and intermediate reasoning that bloat t
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def run_discussion_compression(discussion_text: str) -> str:
|
def run_discussion_compression(discussion_text: str) -> str:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Flow
|
### Flow
|
||||||
@@ -649,7 +649,7 @@ def run_discussion_compression(discussion_text: str) -> str:
|
|||||||
|
|
||||||
### Provider Robustness
|
### Provider Robustness
|
||||||
|
|
||||||
The function tolerates case- and whitespace-variation in the provider string (`" MiniMax "` is normalized to `"minimax"`). This is important because the active provider may be set via different code paths (TOML, env var, runtime override).
|
The function tolerates case- and whitespace-variation in the provider string (`" MiniMax "` is normalized to `"minimax"`). This is important because the active provider may be set via different code paths (TOML, env var, runtime override).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -661,7 +661,7 @@ For very large files, the heuristic `summarise_file` in `src/summarize.py` may b
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def run_subagent_summarization(file_path: str, content: str, is_code: bool, outline: str) -> str:
|
def run_subagent_summarization(file_path: str, content: str, is_code: bool, outline: str) -> str:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### When Invoked
|
### When Invoked
|
||||||
@@ -688,17 +688,17 @@ Every API interaction is logged to a module-level list with real-time GUI push:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
|
def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
|
||||||
entry = {
|
entry = {
|
||||||
"ts": datetime.now().strftime("%H:%M:%S"),
|
"ts": datetime.now().strftime("%H:%M:%S"),
|
||||||
"direction": direction, # "OUT" (to API) or "IN" (from API)
|
"direction": direction, # "OUT" (to API) or "IN" (from API)
|
||||||
"kind": kind, # "request" | "response" | "tool_call" | "tool_result"
|
"kind": kind, # "request" | "response" | "tool_call" | "tool_result"
|
||||||
"provider": _provider,
|
"provider": _provider,
|
||||||
"model": _model,
|
"model": _model,
|
||||||
"payload": payload,
|
"payload": payload,
|
||||||
}
|
}
|
||||||
_comms_log.append(entry)
|
_comms_log.append(entry)
|
||||||
if comms_log_callback:
|
if comms_log_callback:
|
||||||
comms_log_callback(entry) # Real-time push to GUI
|
comms_log_callback(entry) # Real-time push to GUI
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -709,10 +709,10 @@ def _append_comms(direction: str, kind: str, payload: dict[str, Any]) -> None:
|
|||||||
|
|
||||||
```
|
```
|
||||||
"idle" -> "sending..." -> [AI call in progress]
|
"idle" -> "sending..." -> [AI call in progress]
|
||||||
-> "running powershell..." -> "powershell done, awaiting AI..."
|
-> "running powershell..." -> "powershell done, awaiting AI..."
|
||||||
-> "fetching url..." | "searching web..."
|
-> "fetching url..." | "searching web..."
|
||||||
-> "done" | "error"
|
-> "done" | "error"
|
||||||
-> "idle" (on reset)
|
-> "idle" (on reset)
|
||||||
```
|
```
|
||||||
|
|
||||||
### HITL Dialog State (Binary per type)
|
### HITL Dialog State (Binary per type)
|
||||||
@@ -748,32 +748,32 @@ Every interaction is designed to be auditable:
|
|||||||
```python
|
```python
|
||||||
# Comms log entry (JSON-L)
|
# Comms log entry (JSON-L)
|
||||||
{
|
{
|
||||||
"ts": "14:32:05",
|
"ts": "14:32:05",
|
||||||
"direction": "OUT",
|
"direction": "OUT",
|
||||||
"kind": "tool_call",
|
"kind": "tool_call",
|
||||||
"provider": "gemini",
|
"provider": "gemini",
|
||||||
"model": "gemini-2.5-flash-lite",
|
"model": "gemini-2.5-flash-lite",
|
||||||
"payload": {
|
"payload": {
|
||||||
"name": "run_powershell",
|
"name": "run_powershell",
|
||||||
"id": "call_abc123",
|
"id": "call_abc123",
|
||||||
"script": "Get-ChildItem"
|
"script": "Get-ChildItem"
|
||||||
},
|
},
|
||||||
"source_tier": "Tier 3",
|
"source_tier": "Tier 3",
|
||||||
"local_ts": 1709875925.123
|
"local_ts": 1709875925.123
|
||||||
}
|
}
|
||||||
|
|
||||||
# Performance metrics (via get_metrics())
|
# Performance metrics (via get_metrics())
|
||||||
{
|
{
|
||||||
"fps": 60.0,
|
"fps": 60.0,
|
||||||
"fps_avg": 58.5,
|
"fps_avg": 58.5,
|
||||||
"last_frame_time_ms": 16.67,
|
"last_frame_time_ms": 16.67,
|
||||||
"frame_time_ms_avg": 17.1,
|
"frame_time_ms_avg": 17.1,
|
||||||
"cpu_percent": 12.5,
|
"cpu_percent": 12.5,
|
||||||
"cpu_percent_avg": 15.2,
|
"cpu_percent_avg": 15.2,
|
||||||
"input_lag_ms": 2.3,
|
"input_lag_ms": 2.3,
|
||||||
"input_lag_ms_avg": 3.1,
|
"input_lag_ms_avg": 3.1,
|
||||||
"time_render_mma_dashboard_ms": 5.2,
|
"time_render_mma_dashboard_ms": 5.2,
|
||||||
"time_render_mma_dashboard_ms_avg": 4.8
|
"time_render_mma_dashboard_ms_avg": 4.8
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -787,30 +787,30 @@ The `WorkerPool` class in `multi_agent_conductor.py` manages a bounded pool of w
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class WorkerPool:
|
class WorkerPool:
|
||||||
def __init__(self, max_workers: int = 4):
|
def __init__(self, max_workers: int = 4):
|
||||||
self.max_workers = max_workers
|
self.max_workers = max_workers
|
||||||
self._active: dict[str, threading.Thread] = {}
|
self._active: dict[str, threading.Thread] = {}
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
self._semaphore = threading.Semaphore(max_workers)
|
self._semaphore = threading.Semaphore(max_workers)
|
||||||
|
|
||||||
def spawn(self, ticket_id: str, target: Callable, args: tuple) -> Optional[threading.Thread]:
|
def spawn(self, ticket_id: str, target: Callable, args: tuple) -> Optional[threading.Thread]:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
if len(self._active) >= self.max_workers:
|
if len(self._active) >= self.max_workers:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def wrapper(*a, **kw):
|
def wrapper(*a, **kw):
|
||||||
try:
|
try:
|
||||||
with self._semaphore:
|
with self._semaphore:
|
||||||
target(*a, **kw)
|
target(*a, **kw)
|
||||||
finally:
|
finally:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._active.pop(ticket_id, None)
|
self._active.pop(ticket_id, None)
|
||||||
|
|
||||||
t = threading.Thread(target=wrapper, args=args, daemon=True)
|
t = threading.Thread(target=wrapper, args=args, daemon=True)
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._active[ticket_id] = t
|
self._active[ticket_id] = t
|
||||||
t.start()
|
t.start()
|
||||||
return t
|
return t
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key behaviors**:
|
**Key behaviors**:
|
||||||
@@ -825,22 +825,22 @@ The `ConductorEngine` orchestrates ticket execution within a track:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class ConductorEngine:
|
class ConductorEngine:
|
||||||
def __init__(self, track: Track, event_queue: Optional[SyncEventQueue] = None,
|
def __init__(self, track: Track, event_queue: Optional[SyncEventQueue] = None,
|
||||||
auto_queue: bool = False) -> None:
|
auto_queue: bool = False) -> None:
|
||||||
self.track = track
|
self.track = track
|
||||||
self.event_queue = event_queue
|
self.event_queue = event_queue
|
||||||
self.dag = TrackDAG(self.track.tickets)
|
self.dag = TrackDAG(self.track.tickets)
|
||||||
self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue)
|
self.engine = ExecutionEngine(self.dag, auto_queue=auto_queue)
|
||||||
self.pool = WorkerPool(max_workers=4)
|
self.pool = WorkerPool(max_workers=4)
|
||||||
self._abort_events: dict[str, threading.Event] = {}
|
self._abort_events: dict[str, threading.Event] = {}
|
||||||
self._pause_event = threading.Event()
|
self._pause_event = threading.Event()
|
||||||
self._tier_usage_lock = threading.Lock()
|
self._tier_usage_lock = threading.Lock()
|
||||||
self.tier_usage = {
|
self.tier_usage = {
|
||||||
"Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"},
|
"Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"},
|
||||||
"Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"},
|
"Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"},
|
||||||
"Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
|
"Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
|
||||||
"Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
|
"Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Main execution loop** (`run` method):
|
**Main execution loop** (`run` method):
|
||||||
@@ -864,17 +864,17 @@ self._abort_events[ticket.id] = threading.Event()
|
|||||||
# Worker checks abort at three points:
|
# Worker checks abort at three points:
|
||||||
# 1. Before major work
|
# 1. Before major work
|
||||||
if abort_event.is_set():
|
if abort_event.is_set():
|
||||||
ticket.status = "killed"
|
ticket.status = "killed"
|
||||||
return "ABORTED"
|
return "ABORTED"
|
||||||
|
|
||||||
# 2. Before tool execution (in clutch_callback)
|
# 2. Before tool execution (in clutch_callback)
|
||||||
if abort_event.is_set():
|
if abort_event.is_set():
|
||||||
return False # Reject tool
|
return False # Reject tool
|
||||||
|
|
||||||
# 3. After blocking send() returns
|
# 3. After blocking send() returns
|
||||||
if abort_event.is_set():
|
if abort_event.is_set():
|
||||||
ticket.status = "killed"
|
ticket.status = "killed"
|
||||||
return "ABORTED"
|
return "ABORTED"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -907,21 +907,21 @@ The `ProviderError` class provides structured error classification:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class ProviderError(Exception):
|
class ProviderError(Exception):
|
||||||
def __init__(self, kind: str, provider: str, original: Exception):
|
def __init__(self, kind: str, provider: str, original: Exception):
|
||||||
self.kind = kind # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown"
|
self.kind = kind # "quota" | "rate_limit" | "auth" | "balance" | "network" | "unknown"
|
||||||
self.provider = provider
|
self.provider = provider
|
||||||
self.original = original
|
self.original = original
|
||||||
|
|
||||||
def ui_message(self) -> str:
|
def ui_message(self) -> str:
|
||||||
labels = {
|
labels = {
|
||||||
"quota": "QUOTA EXHAUSTED",
|
"quota": "QUOTA EXHAUSTED",
|
||||||
"rate_limit": "RATE LIMITED",
|
"rate_limit": "RATE LIMITED",
|
||||||
"auth": "AUTH / API KEY ERROR",
|
"auth": "AUTH / API KEY ERROR",
|
||||||
"balance": "BALANCE / BILLING ERROR",
|
"balance": "BALANCE / BILLING ERROR",
|
||||||
"network": "NETWORK / CONNECTION ERROR",
|
"network": "NETWORK / CONNECTION ERROR",
|
||||||
"unknown": "API ERROR",
|
"unknown": "API ERROR",
|
||||||
}
|
}
|
||||||
return f"[{self.provider.upper()} {labels.get(self.kind, 'API ERROR')}]\n\n{self.original}"
|
return f"[{self.provider.upper()} {labels.get(self.kind, 'API ERROR')}]\n\n{self.original}"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Recovery Patterns
|
### Error Recovery Patterns
|
||||||
@@ -944,29 +944,29 @@ class ProviderError(Exception):
|
|||||||
**Gemini (40% threshold)**:
|
**Gemini (40% threshold)**:
|
||||||
```python
|
```python
|
||||||
if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4:
|
if total_in > _GEMINI_MAX_INPUT_TOKENS * 0.4:
|
||||||
while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3:
|
while len(hist) > 4 and total_in > _GEMINI_MAX_INPUT_TOKENS * 0.3:
|
||||||
# Drop oldest message pairs
|
# Drop oldest message pairs
|
||||||
hist.pop(0) # Assistant
|
hist.pop(0) # Assistant
|
||||||
hist.pop(0) # User
|
hist.pop(0) # User
|
||||||
```
|
```
|
||||||
|
|
||||||
**Anthropic (180K limit)**:
|
**Anthropic (180K limit)**:
|
||||||
```python
|
```python
|
||||||
def _trim_anthropic_history(system_blocks, history):
|
def _trim_anthropic_history(system_blocks, history):
|
||||||
est = _estimate_prompt_tokens(system_blocks, history)
|
est = _estimate_prompt_tokens(system_blocks, history)
|
||||||
while len(history) > 3 and est > _ANTHROPIC_MAX_PROMPT_TOKENS:
|
while len(history) > 3 and est > _ANTHROPIC_MAX_PROMPT_TOKENS:
|
||||||
# Drop turn pairs, preserving tool_result chains
|
# Drop turn pairs, preserving tool_result chains
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tool Output Budget
|
### Tool Output Budget
|
||||||
|
|
||||||
```python
|
```python
|
||||||
_MAX_TOOL_OUTPUT_BYTES: int = 500_000 # 500KB cumulative
|
_MAX_TOOL_OUTPUT_BYTES: int = 500_000 # 500KB cumulative
|
||||||
|
|
||||||
if _cumulative_tool_bytes > _MAX_TOOL_OUTPUT_BYTES:
|
if _cumulative_tool_bytes > _MAX_TOOL_OUTPUT_BYTES:
|
||||||
# Inject warning, force final answer
|
# Inject warning, force final answer
|
||||||
parts.append("SYSTEM WARNING: Cumulative tool output exceeded 500KB budget.")
|
parts.append("SYSTEM WARNING: Cumulative tool output exceeded 500KB budget.")
|
||||||
```
|
```
|
||||||
|
|
||||||
### AST Cache (file_cache.py)
|
### AST Cache (file_cache.py)
|
||||||
@@ -975,17 +975,17 @@ if _cumulative_tool_bytes > _MAX_TOOL_OUTPUT_BYTES:
|
|||||||
_ast_cache: Dict[str, Tuple[float, tree_sitter.Tree]] = {}
|
_ast_cache: Dict[str, Tuple[float, tree_sitter.Tree]] = {}
|
||||||
|
|
||||||
def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree:
|
def get_cached_tree(self, path: Optional[str], code: str) -> tree_sitter.Tree:
|
||||||
mtime = p.stat().st_mtime if p.exists() else 0.0
|
mtime = p.stat().st_mtime if p.exists() else 0.0
|
||||||
if path in _ast_cache:
|
if path in _ast_cache:
|
||||||
cached_mtime, tree = _ast_cache[path]
|
cached_mtime, tree = _ast_cache[path]
|
||||||
if cached_mtime == mtime:
|
if cached_mtime == mtime:
|
||||||
return tree
|
return tree
|
||||||
# Parse and cache with simple LRU (max 10 entries)
|
# Parse and cache with simple LRU (max 10 entries)
|
||||||
if len(_ast_cache) >= 10:
|
if len(_ast_cache) >= 10:
|
||||||
del _ast_cache[next(iter(_ast_cache))]
|
del _ast_cache[next(iter(_ast_cache))]
|
||||||
tree = self.parse(code)
|
tree = self.parse(code)
|
||||||
_ast_cache[path] = (mtime, tree)
|
_ast_cache[path] = (mtime, tree)
|
||||||
return tree
|
return tree
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ This is one of the most-touched modules in the project. After the nagent_review,
|
|||||||
|
|
||||||
```
|
```
|
||||||
aggregate.run(config, aggregation_strategy)
|
aggregate.run(config, aggregation_strategy)
|
||||||
├─ find_next_increment(output_dir, namespace) # next file number for output
|
├─ find_next_increment(output_dir, namespace) # next file number for output
|
||||||
├─ build_file_items(base_dir, files) # read + view-mode transform
|
├─ build_file_items(base_dir, files) # read + view-mode transform
|
||||||
├─ build_markdown_from_items(file_items, ...) # compose sections
|
├─ build_markdown_from_items(file_items, ...) # compose sections
|
||||||
│ ├─ ## Files (or Files (Summary) or Files (Tier 3 - Focused))
|
│ ├─ ## Files (or Files (Summary) or Files (Tier 3 - Focused))
|
||||||
│ │ └─ _build_files_section_from_items OR summarize.build_summary_markdown
|
│ │ └─ _build_files_section_from_items OR summarize.build_summary_markdown
|
||||||
│ ├─ ## Screenshots (if any)
|
│ ├─ ## Screenshots (if any)
|
||||||
│ ├─ ## Beads Mode: Progress Track (if execution_mode == "beads")
|
│ ├─ ## Beads Mode: Progress Track (if execution_mode == "beads")
|
||||||
│ └─ ## Discussion History (if any)
|
│ └─ ## Discussion History (if any)
|
||||||
└─ output_file.write_text(markdown)
|
└─ output_file.write_text(markdown)
|
||||||
```
|
```
|
||||||
|
|
||||||
The **output** is a markdown file at `{output_dir}/{namespace}_{NNN}.md` where `NNN` is a zero-padded increment. The pipeline does not *send* the markdown — that's the AI client's job. The pipeline *produces* the markdown.
|
The **output** is a markdown file at `{output_dir}/{namespace}_{NNN}.md` where `NNN` is a zero-padded increment. The pipeline does not *send* the markdown — that's the AI client's job. The pipeline *produces* the markdown.
|
||||||
@@ -54,11 +54,11 @@ The **return value** is `(markdown: str, output_file: Path, file_items: list[dic
|
|||||||
**Implementation:** `aggregate.py:330-346 build_markdown_from_items`. The three-way dispatch is at lines 335-339:
|
**Implementation:** `aggregate.py:330-346 build_markdown_from_items`. The three-way dispatch is at lines 335-339:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
if aggregation_strategy == "summarize": parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items))
|
if aggregation_strategy == "summarize": parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items))
|
||||||
elif aggregation_strategy == "full": parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
|
elif aggregation_strategy == "full": parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
|
||||||
else: # auto
|
else: # auto
|
||||||
if summary_only: parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items))
|
if summary_only: parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items))
|
||||||
else: parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
|
else: parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
|
||||||
```
|
```
|
||||||
|
|
||||||
The `auto` strategy is the *only* one that respects `config.project.summary_only`; the other two are explicit overrides. Personas can also set `aggregation_strategy` (per `guide_personas.md`), and a persona-set strategy overrides the config-level setting.
|
The `auto` strategy is the *only* one that respects `config.project.summary_only`; the other two are explicit overrides. Personas can also set `aggregation_strategy` (per `guide_personas.md`), and a persona-set strategy overrides the config-level setting.
|
||||||
@@ -92,16 +92,16 @@ The `auto` strategy is the *only* one that respects `config.project.summary_only
|
|||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class FileItem:
|
class FileItem:
|
||||||
path: str # the artifact identity (path-keyed, no inode)
|
path: str # the artifact identity (path-keyed, no inode)
|
||||||
auto_aggregate: bool = True # include in auto-aggregation? (skip in build_*_from_items if False)
|
auto_aggregate: bool = True # include in auto-aggregation? (skip in build_*_from_items if False)
|
||||||
force_full: bool = False # bypass view_mode; force raw content
|
force_full: bool = False # bypass view_mode; force raw content
|
||||||
view_mode: str = 'full' # one of: full, summary, skeleton, outline, masked, custom, none
|
view_mode: str = 'full' # one of: full, summary, skeleton, outline, masked, custom, none
|
||||||
selected: bool = False # for batch operations (the Context Panel multi-select)
|
selected: bool = False # for batch operations (the Context Panel multi-select)
|
||||||
ast_signatures: bool = False # include only signatures (skeleton-equivalent shortcut)
|
ast_signatures: bool = False # include only signatures (skeleton-equivalent shortcut)
|
||||||
ast_definitions: bool = False # include only definitions (skeleton-equivalent shortcut)
|
ast_definitions: bool = False # include only definitions (skeleton-equivalent shortcut)
|
||||||
ast_mask: dict[str, str] # per-symbol mask: {symbol_path: 'def'|'sig'|'hide'} (from Structural File Editor)
|
ast_mask: dict[str, str] # per-symbol mask: {symbol_path: 'def'|'sig'|'hide'} (from Structural File Editor)
|
||||||
custom_slices: list[dict] # Fuzzy Anchor slices: {start_line, end_line, tag, comment, ...}
|
custom_slices: list[dict] # Fuzzy Anchor slices: {start_line, end_line, tag, comment, ...}
|
||||||
injected_at: Optional[float] # timestamp of last injection
|
injected_at: Optional[float] # timestamp of last injection
|
||||||
```
|
```
|
||||||
|
|
||||||
The 9 fields are *all* serialized by `to_dict()` and *all* deserialized by `from_dict()` (with `.get(..., default)` for forward compatibility). The dataclass is round-trip-safe through TOML.
|
The 9 fields are *all* serialized by `to_dict()` and *all* deserialized by `from_dict()` (with `.get(..., default)` for forward compatibility). The dataclass is round-trip-safe through TOML.
|
||||||
@@ -114,13 +114,13 @@ A `custom_slices` entry is `{start_line, end_line, tag, comment, ...}` (plus Fuz
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
{
|
{
|
||||||
"start_line": int, # 1-based original line
|
"start_line": int, # 1-based original line
|
||||||
"end_line": int, # 1-based original line (inclusive)
|
"end_line": int, # 1-based original line (inclusive)
|
||||||
"tag": str|None, # human label, defaults to None
|
"tag": str|None, # human label, defaults to None
|
||||||
"comment": str|None, # human comment, defaults to None
|
"comment": str|None, # human comment, defaults to None
|
||||||
"content_hash": str, # SHA-256 of the slice content (for Fuzzy Anchor stability)
|
"content_hash": str, # SHA-256 of the slice content (for Fuzzy Anchor stability)
|
||||||
"anchor_lines": [str, ...],# surrounding context for re-resolution
|
"anchor_lines": [str, ...],# surrounding context for re-resolution
|
||||||
# plus the original positioning metadata
|
# plus the original positioning metadata
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -144,10 +144,10 @@ Multiple slices in a file are joined with `\n\n`.
|
|||||||
```python
|
```python
|
||||||
@dataclass
|
@dataclass
|
||||||
class ContextPreset:
|
class ContextPreset:
|
||||||
name: str # the preset name (used as TOML key)
|
name: str # the preset name (used as TOML key)
|
||||||
files: list[ContextFileEntry] = field(default_factory=list)
|
files: list[ContextFileEntry] = field(default_factory=list)
|
||||||
screenshots: list[str] = field(default_factory=list)
|
screenshots: list[str] = field(default_factory=list)
|
||||||
description: str = ""
|
description: str = ""
|
||||||
```
|
```
|
||||||
|
|
||||||
`ContextFileEntry` is a `FileItem` (or a string path that's promoted to a `FileItem` on load). The `description` is a human-readable label for the preset list.
|
`ContextFileEntry` is a `FileItem` (or a string path that's promoted to a `FileItem` on load). The `description` is a human-readable label for the preset list.
|
||||||
@@ -170,16 +170,16 @@ class ContextPreset:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def build_discussion_section(history: list[Any]) -> str:
|
def build_discussion_section(history: list[Any]) -> str:
|
||||||
sections = []
|
sections = []
|
||||||
for i, entry in enumerate(history, start=1):
|
for i, entry in enumerate(history, start=1):
|
||||||
if isinstance(entry, dict):
|
if isinstance(entry, dict):
|
||||||
role = entry.get("role", "Unknown")
|
role = entry.get("role", "Unknown")
|
||||||
content = entry.get("content", "").strip()
|
content = entry.get("content", "").strip()
|
||||||
text = f"{role}: {content}"
|
text = f"{role}: {content}"
|
||||||
else:
|
else:
|
||||||
text = str(entry).strip()
|
text = str(entry).strip()
|
||||||
sections.append(f"### Discussion Excerpt {i}\n\n{text}")
|
sections.append(f"### Discussion Excerpt {i}\n\n{text}")
|
||||||
return "\n\n---\n\n".join(sections)
|
return "\n\n---\n\n".join(sections)
|
||||||
```
|
```
|
||||||
|
|
||||||
The section handles *both* legacy `list[str]` (e.g. `["User: ...", "AI: ..."]`) and the new `list[dict]` shape (`[{"role": ..., "content": ...}, ...]`). The dict shape is what's persisted by `_flush_disc_entries_to_project` (per `app_controller.py:3225-3240`) and what's stored in the new format.
|
The section handles *both* legacy `list[str]` (e.g. `["User: ...", "AI: ..."]`) and the new `list[dict]` shape (`[{"role": ..., "content": ...}, ...]`). The dict shape is what's persisted by `_flush_disc_entries_to_project` (per `app_controller.py:3225-3240`) and what's stored in the new format.
|
||||||
@@ -231,7 +231,7 @@ For Tier 3, `force_full` is treated as a *focus flag*:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
if is_focus or tier == 3 or force_full:
|
if is_focus or tier == 3 or force_full:
|
||||||
# full content, no skeleton
|
# full content, no skeleton
|
||||||
```
|
```
|
||||||
|
|
||||||
So a `force_full=True` file in a Tier 3 worker context is treated as a focus file and rendered in full.
|
So a `force_full=True` file in a Tier 3 worker context is treated as a focus file and rendered in full.
|
||||||
@@ -244,8 +244,8 @@ So a `force_full=True` file in a Tier 3 worker context is treated as a focus fil
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
for item in file_items:
|
for item in file_items:
|
||||||
if not item.get("auto_aggregate", True): continue
|
if not item.get("auto_aggregate", True): continue
|
||||||
# ... build section
|
# ... build section
|
||||||
```
|
```
|
||||||
|
|
||||||
Use case: the file is in the `files` list for the AI's *awareness* (e.g. "you can read it via `read_file`") but should not be inlined. The file's `mtime` and `view_mode` are still tracked; the file is *omitted* from the rendered markdown.
|
Use case: the file is in the `files` list for the AI's *awareness* (e.g. "you can read it via `read_file`") but should not be inlined. The file's `mtime` and `view_mode` are still tracked; the file is *omitted* from the rendered markdown.
|
||||||
@@ -384,7 +384,7 @@ For very large codebases (1000+ files), the bottleneck is the tree-sitter parsin
|
|||||||
- **FileItem schema:** `src/project_files.py:FileItem` (moved out of `src/models.py`)
|
- **FileItem schema:** `src/project_files.py:FileItem` (moved out of `src/models.py`)
|
||||||
- **ContextPreset schema:** `src/context_presets.py:ContextPreset` (moved out of `src/models.py`)
|
- **ContextPreset schema:** `src/context_presets.py:ContextPreset` (moved out of `src/models.py`)
|
||||||
- **ContextPresetManager:** `src/context_presets.py` (30 lines)
|
- **ContextPresetManager:** `src/context_presets.py` (30 lines)
|
||||||
- **AI client consumption:** `src/ai_client.py:_send_<provider>` × 8 (gemini, anthropic, gemini_cli, deepseek, minimax, qwen, grok, llama), see `guide_ai_client.md`
|
- **AI client consumption:** `src/ai_client.py:_send_<provider>` × 8 (gemini, anthropic, deepseek, minimax, qwen, grok, llama), see `guide_ai_client.md`
|
||||||
- **Tier 3 worker consumption:** `src/multi_agent_conductor.py:run_worker_lifecycle`, see `guide_multi_agent_conductor.md`
|
- **Tier 3 worker consumption:** `src/multi_agent_conductor.py:run_worker_lifecycle`, see `guide_multi_agent_conductor.md`
|
||||||
- **Per-file curation features:** `guide_context_curation.md` (Fuzzy Anchors, AST Inspector, Granular AST Control)
|
- **Per-file curation features:** `guide_context_curation.md` (Fuzzy Anchors, AST Inspector, Granular AST Control)
|
||||||
- **Cache strategy:** `guide_architecture.md §"Cache Hit Strategy"`, `guide_ai_client.md §"Caching"`
|
- **Cache strategy:** `guide_architecture.md §"Cache Hit Strategy"`, `guide_ai_client.md §"Caching"`
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ The dataclass definitions, `DEFAULT_TOOL_CATEGORIES`, the `__getattr__` shim, an
|
|||||||
```python
|
```python
|
||||||
from src.mma import TrackMetadata
|
from src.mma import TrackMetadata
|
||||||
|
|
||||||
Metadata = TrackMetadata # legacy class name re-export
|
Metadata = TrackMetadata # legacy class name re-export
|
||||||
|
|
||||||
def __getattr__(name: str) -> Any:
|
def __getattr__(name: str) -> Any:
|
||||||
if name == "PROVIDERS":
|
if name == "PROVIDERS":
|
||||||
from src import ai_client
|
from src import ai_client
|
||||||
return ai_client.PROVIDERS
|
return ai_client.PROVIDERS
|
||||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ The old "one registry to look at" goal is now achieved by **per-system files**.
|
|||||||
|
|
||||||
| Constant | Current location | Notes |
|
| Constant | Current location | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `PROVIDERS` | `src/ai_client.py` (re-exported by `src/models.py` via lazy `__getattr__`) | `List[str]` of 8 providers: `gemini`, `anthropic`, `gemini_cli`, `deepseek`, `minimax`, `qwen`, `grok`, `llama` |
|
| `PROVIDERS` | `src/ai_client.py` (re-exported by `src/models.py` via lazy `__getattr__`) | `List[str]` of 7 providers: `gemini`, `anthropic`, `gemini_cli`, `deepseek`, `minimax`, `qwen`, `grok`, `llama` |
|
||||||
| `DEFAULT_TOOL_CATEGORIES` | `src/ai_client.py` | The canonical grouping of the MCP tool registry for the UI's category filter |
|
| `DEFAULT_TOOL_CATEGORIES` | `src/ai_client.py` | The canonical grouping of the MCP tool registry for the UI's category filter |
|
||||||
| Tool names (formerly `AGENT_TOOL_NAMES`) | `src/mcp_tool_specs.py:_REGISTRY` + `mcp_tool_specs.tool_names()` | 45 tools. Re-exported as `mcp_client.TOOL_NAMES` for backward compat |
|
| Tool names (formerly `AGENT_TOOL_NAMES`) | `src/mcp_tool_specs.py:_REGISTRY` + `mcp_tool_specs.tool_names()` | 45 tools. Re-exported as `mcp_client.TOOL_NAMES` for backward compat |
|
||||||
| `DEFAULT_TIER_PERSONAS` | `src/mma_prompts.py` | MMA tier → default persona mapping |
|
| `DEFAULT_TIER_PERSONAS` | `src/mma_prompts.py` | MMA tier → default persona mapping |
|
||||||
|
|||||||
+58
-58
@@ -29,13 +29,13 @@ Defined in `tests/conftest.py`, this session-scoped fixture manages the lifecycl
|
|||||||
```python
|
```python
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def live_gui(request) -> Generator["_LiveGuiHandle", None, None]:
|
def live_gui(request) -> Generator["_LiveGuiHandle", None, None]:
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"],
|
["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"],
|
||||||
stdout=log_file, stderr=log_file, text=True,
|
stdout=log_file, stderr=log_file, text=True,
|
||||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0
|
||||||
)
|
)
|
||||||
# ... (readiness polling + xdist coordination) ...
|
# ... (readiness polling + xdist coordination) ...
|
||||||
yield _LiveGuiHandle(process, gui_script, workspace=temp_workspace)
|
yield _LiveGuiHandle(process, gui_script, workspace=temp_workspace)
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`-u` flag**: Disables output buffering for real-time log capture.
|
- **`-u` flag**: Disables output buffering for real-time log capture.
|
||||||
@@ -45,13 +45,13 @@ def live_gui(request) -> Generator["_LiveGuiHandle", None, None]:
|
|||||||
**Readiness polling:**
|
**Readiness polling:**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
max_retries = 15 # seconds
|
max_retries = 15 # seconds
|
||||||
while time.time() - start_time < max_retries:
|
while time.time() - start_time < max_retries:
|
||||||
response = requests.get("http://127.0.0.1:8999/status", timeout=0.5)
|
response = requests.get("http://127.0.0.1:8999/status", timeout=0.5)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
ready = True; break
|
ready = True; break
|
||||||
if process.poll() is not None: break # Process died early
|
if process.poll() is not None: break # Process died early
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
```
|
```
|
||||||
|
|
||||||
Polls `GET /status` every 500ms for up to 15 seconds. Checks `process.poll()` each iteration to detect early crashes (avoids waiting the full timeout if the GUI exits). Pre-check: tests if port 8999 is already occupied.
|
Polls `GET /status` every 500ms for up to 15 seconds. Checks `process.poll()` each iteration to detect early crashes (avoids waiting the full timeout if the GUI exits). Pre-check: tests if port 8999 is already occupied.
|
||||||
@@ -62,11 +62,11 @@ Polls `GET /status` every 500ms for up to 15 seconds. Checks `process.poll()` ea
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
finally:
|
finally:
|
||||||
client = ApiHookClient()
|
client = ApiHookClient()
|
||||||
client.reset_session() # Clean GUI state before killing
|
client.reset_session() # Clean GUI state before killing
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
kill_process_tree(process.pid)
|
kill_process_tree(process.pid)
|
||||||
log_file.close()
|
log_file.close()
|
||||||
```
|
```
|
||||||
|
|
||||||
Sends `reset_session()` via `ApiHookClient` before killing to prevent stale state files.
|
Sends `reset_session()` via `ApiHookClient` before killing to prevent stale state files.
|
||||||
@@ -91,9 +91,9 @@ Sends `reset_session()` via `ApiHookClient` before killing to prevent stale stat
|
|||||||
```python
|
```python
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def reset_ai_client() -> Generator[None, None, None]:
|
def reset_ai_client() -> Generator[None, None, None]:
|
||||||
ai_client.reset_session()
|
ai_client.reset_session()
|
||||||
ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
|
ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
|
||||||
yield
|
yield
|
||||||
```
|
```
|
||||||
|
|
||||||
Runs automatically before every test. Resets the `ai_client` module state and defaults to a safe model, preventing state pollution between tests.
|
Runs automatically before every test. Resets the `ai_client` module state and defaults to a safe model, preventing state pollution between tests.
|
||||||
@@ -103,9 +103,9 @@ Runs automatically before every test. Resets the `ai_client` module state and de
|
|||||||
```python
|
```python
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def isolate_workspace(tmp_path_factory, monkeypatch) -> Generator[None, None, None]:
|
def isolate_workspace(tmp_path_factory, monkeypatch) -> Generator[None, None, None]:
|
||||||
# Redirects the path resolution layer to a temp directory
|
# Redirects the path resolution layer to a temp directory
|
||||||
# Prevents tests from writing to the user's actual project
|
# Prevents tests from writing to the user's actual project
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
This autouse fixture ensures every test runs against an isolated `tmp_path` workspace. It `monkeypatch`-es `src.paths` so that any code path resolving a project directory (e.g., `manual_slop.toml` lookup, conductor directory resolution, log directory) is redirected to a fresh temp directory per test. Without this, tests could mutate the user's actual `manual_slop.toml` or conductor tracks directory.
|
This autouse fixture ensures every test runs against an isolated `tmp_path` workspace. It `monkeypatch`-es `src.paths` so that any code path resolving a project directory (e.g., `manual_slop.toml` lookup, conductor directory resolution, log directory) is redirected to a fresh temp directory per test. Without this, tests could mutate the user's actual `manual_slop.toml` or conductor tracks directory.
|
||||||
@@ -117,8 +117,8 @@ This is the primary mechanism for satisfying the **Artifact Isolation** rule in
|
|||||||
```python
|
```python
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def reset_paths() -> Generator[None, None, None]:
|
def reset_paths() -> Generator[None, None, None]:
|
||||||
# Forces `src/paths.py` to re-resolve from environment / config on next access
|
# Forces `src/paths.py` to re-resolve from environment / config on next access
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
Pairs with `isolate_workspace` to fully reset the path subsystem. After a test that creates a project config, the next test gets a clean slate.
|
Pairs with `isolate_workspace` to fully reset the path subsystem. After a test that creates a project config, the next test gets a clean slate.
|
||||||
@@ -147,11 +147,11 @@ Structured diagnostic logging for test telemetry:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class VerificationLogger:
|
class VerificationLogger:
|
||||||
def __init__(self, test_name: str, script_name: str):
|
def __init__(self, test_name: str, script_name: str):
|
||||||
self.logs_dir = Path(f"logs/test/{datetime.now().strftime('%Y%m%d_%H%M%S')}")
|
self.logs_dir = Path(f"logs/test/{datetime.now().strftime('%Y%m%d_%H%M%S')}")
|
||||||
|
|
||||||
def log_state(self, field: str, before: Any, after: Any, delta: Any = None)
|
def log_state(self, field: str, before: Any, after: Any, delta: Any = None)
|
||||||
def finalize(self, description: str, status: str, result_msg: str)
|
def finalize(self, description: str, status: str, result_msg: str)
|
||||||
```
|
```
|
||||||
|
|
||||||
Output format: fixed-width column table (`Field | Before | After | Delta`) written to `logs/test/<timestamp>/<script_name>.txt`. Dual output: file + tagged stdout lines for CI visibility.
|
Output format: fixed-width column table (`Field | Before | After | Delta`) written to `logs/test/<timestamp>/<script_name>.txt`. Dual output: file + tagged stdout lines for CI visibility.
|
||||||
@@ -191,12 +191,12 @@ Enters an epic description and triggers planning. The GUI invokes the LLM (which
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
for _ in range(60):
|
for _ in range(60):
|
||||||
status = client.get_mma_status()
|
status = client.get_mma_status()
|
||||||
if status.get('pending_mma_spawn_approval'): client.click('btn_approve_spawn')
|
if status.get('pending_mma_spawn_approval'): client.click('btn_approve_spawn')
|
||||||
elif status.get('pending_mma_step_approval'): client.click('btn_approve_mma_step')
|
elif status.get('pending_mma_step_approval'): client.click('btn_approve_mma_step')
|
||||||
elif status.get('pending_tool_approval'): client.click('btn_approve_tool')
|
elif status.get('pending_tool_approval'): client.click('btn_approve_tool')
|
||||||
if status.get('proposed_tracks') and len(status['proposed_tracks']) > 0: break
|
if status.get('proposed_tracks') and len(status['proposed_tracks']) > 0: break
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
```
|
```
|
||||||
|
|
||||||
The **approval automation** is a critical pattern repeated in every polling loop. The MMA engine has three approval gates:
|
The **approval automation** is a critical pattern repeated in every polling loop. The MMA engine has three approval gates:
|
||||||
@@ -235,9 +235,9 @@ Polls until `mma_status == 'running'` or `'done'`. Continues auto-approving all
|
|||||||
```python
|
```python
|
||||||
streams = status.get('mma_streams', {})
|
streams = status.get('mma_streams', {})
|
||||||
if any("Tier 3" in k for k in streams.keys()):
|
if any("Tier 3" in k for k in streams.keys()):
|
||||||
tier3_key = [k for k in streams.keys() if "Tier 3" in k][0]
|
tier3_key = [k for k in streams.keys() if "Tier 3" in k][0]
|
||||||
if "SUCCESS: Mock Tier 3 worker" in streams[tier3_key]:
|
if "SUCCESS: Mock Tier 3 worker" in streams[tier3_key]:
|
||||||
streams_found = True
|
streams_found = True
|
||||||
```
|
```
|
||||||
|
|
||||||
Verifies that `mma_streams` contains a key with "Tier 3" and the value contains the exact mock output string.
|
Verifies that `mma_streams` contains a key with "Tier 3" and the value contains the exact mock output string.
|
||||||
@@ -262,16 +262,16 @@ A fake Gemini CLI executable that replaces the real `gemini` binary during integ
|
|||||||
**Input mechanism:**
|
**Input mechanism:**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
prompt = sys.stdin.read() # Primary: prompt via stdin
|
prompt = sys.stdin.read() # Primary: prompt via stdin
|
||||||
sys.argv # Secondary: management command detection
|
sys.argv # Secondary: management command detection
|
||||||
os.environ.get('GEMINI_CLI_HOOK_CONTEXT') # Tertiary: environment variable
|
os.environ.get('GEMINI_CLI_HOOK_CONTEXT') # Tertiary: environment variable
|
||||||
```
|
```
|
||||||
|
|
||||||
**Management command bypass:**
|
**Management command bypass:**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
if len(sys.argv) > 1 and sys.argv[1] in ["mcp", "extensions", "skills", "hooks"]:
|
if len(sys.argv) > 1 and sys.argv[1] in ["mcp", "extensions", "skills", "hooks"]:
|
||||||
return # Silent exit
|
return # Silent exit
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response routing** — keyword matching on stdin content:
|
**Response routing** — keyword matching on stdin content:
|
||||||
@@ -390,22 +390,22 @@ The headless service uses the **Remote Confirmation Protocol** for HITL: when an
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class ASTParser:
|
class ASTParser:
|
||||||
def __init__(self, language: str = "python"):
|
def __init__(self, language: str = "python"):
|
||||||
self.language = tree_sitter.Language(tree_sitter_python.language())
|
self.language = tree_sitter.Language(tree_sitter_python.language())
|
||||||
self.parser = tree_sitter.Parser(self.language)
|
self.parser = tree_sitter.Parser(self.language)
|
||||||
|
|
||||||
def parse(self, code: str) -> tree_sitter.Tree
|
def parse(self, code: str) -> tree_sitter.Tree
|
||||||
def get_skeleton(self, code: str, path: str = "") -> str
|
def get_skeleton(self, code: str, path: str = "") -> str
|
||||||
def get_curated_view(self, code: str, path: str = "") -> str
|
def get_curated_view(self, code: str, path: str = "") -> str
|
||||||
def get_targeted_view(self, code: str, symbols: List[str], path: str = "") -> str
|
def get_targeted_view(self, code: str, symbols: List[str], path: str = "") -> str
|
||||||
```
|
```
|
||||||
|
|
||||||
**`get_skeleton` algorithm:**
|
**`get_skeleton` algorithm:**
|
||||||
1. Parse code to tree-sitter AST.
|
1. Parse code to tree-sitter AST.
|
||||||
2. Walk all `function_definition` nodes.
|
2. Walk all `function_definition` nodes.
|
||||||
3. For each body (`block` node):
|
3. For each body (`block` node):
|
||||||
- If first non-comment child is a docstring: preserve docstring, replace rest with `...`.
|
- If first non-comment child is a docstring: preserve docstring, replace rest with `...`.
|
||||||
- Otherwise: replace entire body with `...`.
|
- Otherwise: replace entire body with `...`.
|
||||||
4. Apply edits in reverse byte order (maintains valid offsets).
|
4. Apply edits in reverse byte order (maintains valid offsets).
|
||||||
|
|
||||||
**`get_curated_view` algorithm:**
|
**`get_curated_view` algorithm:**
|
||||||
@@ -428,10 +428,10 @@ Token-efficient structural descriptions without AI calls:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
_SUMMARISERS: dict[str, Callable] = {
|
_SUMMARISERS: dict[str, Callable] = {
|
||||||
".py": _summarise_python, # imports, classes, methods, functions, constants
|
".py": _summarise_python, # imports, classes, methods, functions, constants
|
||||||
".toml": _summarise_toml, # table keys + array lengths
|
".toml": _summarise_toml, # table keys + array lengths
|
||||||
".md": _summarise_markdown, # h1-h3 headings
|
".md": _summarise_markdown, # h1-h3 headings
|
||||||
".ini": _summarise_generic, # line count + preview
|
".ini": _summarise_generic, # line count + preview
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -455,8 +455,8 @@ functions: summarise_file, build_summary_markdown
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class CodeOutliner:
|
class CodeOutliner:
|
||||||
def __init__(self) -> None: ...
|
def __init__(self) -> None: ...
|
||||||
def outline(self, code: str) -> str: ...
|
def outline(self, code: str) -> str: ...
|
||||||
|
|
||||||
def get_outline(path: Path, code: str) -> str: ...
|
def get_outline(path: Path, code: str) -> str: ...
|
||||||
```
|
```
|
||||||
|
|||||||
+66
-66
@@ -11,9 +11,9 @@ The AI's ability to interact with the filesystem is mediated by a three-layer se
|
|||||||
### Global State
|
### Global State
|
||||||
|
|
||||||
```python
|
```python
|
||||||
_allowed_paths: set[Path] = set() # Explicit file allowlist (resolved absolutes)
|
_allowed_paths: set[Path] = set() # Explicit file allowlist (resolved absolutes)
|
||||||
_base_dirs: set[Path] = set() # Directory roots for containment checks
|
_base_dirs: set[Path] = set() # Directory roots for containment checks
|
||||||
_primary_base_dir: Path | None = None # Used for resolving relative paths
|
_primary_base_dir: Path | None = None # Used for resolving relative paths
|
||||||
perf_monitor_callback: Optional[Callable[[], dict[str, Any]]] = None
|
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 |
|
| Tool | Parameters | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `read_file` | `path` | UTF-8 file content extraction |
|
| `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. |
|
| `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) |
|
| `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) |
|
| `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:
|
**AST-based read tools** follow this pattern:
|
||||||
```python
|
```python
|
||||||
def py_get_skeleton(path: str) -> str:
|
def py_get_skeleton(path: str) -> str:
|
||||||
p, err = _resolve_and_check(path)
|
p, err = _resolve_and_check(path)
|
||||||
if err: return err
|
if err: return err
|
||||||
if not p.exists(): return f"ERROR: file not found: {path}"
|
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}"
|
if not p.is_file() or p.suffix != ".py": return f"ERROR: not a python file: {path}"
|
||||||
from file_cache import ASTParser
|
from file_cache import ASTParser
|
||||||
code = p.read_text(encoding="utf-8")
|
code = p.read_text(encoding="utf-8")
|
||||||
parser = ASTParser("python")
|
parser = ASTParser("python")
|
||||||
return parser.get_skeleton(code)
|
return parser.get_skeleton(code)
|
||||||
```
|
```
|
||||||
|
|
||||||
**AST-based write tools** use stdlib `ast` (not tree-sitter) to locate symbols, then delegate to `set_file_slice`:
|
**AST-based write tools** use stdlib `ast` (not tree-sitter) to locate symbols, then delegate to `set_file_slice`:
|
||||||
```python
|
```python
|
||||||
def py_update_definition(path: str, name: str, new_content: str) -> str:
|
def py_update_definition(path: str, name: str, new_content: str) -> str:
|
||||||
p, err = _resolve_and_check(path)
|
p, err = _resolve_and_check(path)
|
||||||
if err: return err
|
if err: return err
|
||||||
code = p.read_text(encoding="utf-8").lstrip(chr(0xFEFF)) # Strip BOM
|
code = p.read_text(encoding="utf-8").lstrip(chr(0xFEFF)) # Strip BOM
|
||||||
tree = ast.parse(code)
|
tree = ast.parse(code)
|
||||||
node = _get_symbol_node(tree, name) # Walks AST for matching node
|
node = _get_symbol_node(tree, name) # Walks AST for matching node
|
||||||
if not node: return f"ERROR: could not find definition '{name}'"
|
if not node: return f"ERROR: could not find definition '{name}'"
|
||||||
start = getattr(node, "lineno")
|
start = getattr(node, "lineno")
|
||||||
end = getattr(node, "end_lineno")
|
end = getattr(node, "end_lineno")
|
||||||
return set_file_slice(path, start, end, new_content)
|
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.
|
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
|
```python
|
||||||
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||||
"""Dispatch an MCP tool call asynchronously."""
|
"""Dispatch an MCP tool call asynchronously."""
|
||||||
return await asyncio.to_thread(dispatch, tool_name, tool_input)
|
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:
|
In `ai_client.py`, multiple tool calls within a single AI turn are executed in parallel:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def _execute_tool_calls_concurrently(calls, base_dir, ...):
|
async def _execute_tool_calls_concurrently(calls, base_dir, ...):
|
||||||
tasks = []
|
tasks = []
|
||||||
for fc in calls:
|
for fc in calls:
|
||||||
tasks.append(_execute_single_tool_call_async(name, args, ...))
|
tasks.append(_execute_single_tool_call_async(name, args, ...))
|
||||||
results = await asyncio.gather(*tasks)
|
results = await asyncio.gather(*tasks)
|
||||||
return results
|
return results
|
||||||
```
|
```
|
||||||
|
|
||||||
This significantly reduces latency when the AI makes multiple independent file reads in a single turn.
|
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
|
```python
|
||||||
class HookServerInstance(ThreadingHTTPServer):
|
class HookServerInstance(ThreadingHTTPServer):
|
||||||
app: Any # Reference to main App instance
|
app: Any # Reference to main App instance
|
||||||
|
|
||||||
class HookHandler(BaseHTTPRequestHandler):
|
class HookHandler(BaseHTTPRequestHandler):
|
||||||
# Accesses self.server.app for all state
|
# Accesses self.server.app for all state
|
||||||
|
|
||||||
class HookServer:
|
class HookServer:
|
||||||
app: Any
|
app: Any
|
||||||
port: int = 8999
|
port: int = 8999
|
||||||
server: HookServerInstance | None
|
server: HookServerInstance | None
|
||||||
thread: threading.Thread | 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.
|
**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
|
```python
|
||||||
{
|
{
|
||||||
"mma_status": str, # "idle" | "planning" | "executing" | "done"
|
"mma_status": str, # "idle" | "planning" | "executing" | "done"
|
||||||
"ai_status": str, # "idle" | "sending..." | etc.
|
"ai_status": str, # "idle" | "sending..." | etc.
|
||||||
"active_tier": str | None,
|
"active_tier": str | None,
|
||||||
"active_track": str, # Track ID or raw value
|
"active_track": str, # Track ID or raw value
|
||||||
"active_tickets": list, # Serialized ticket dicts
|
"active_tickets": list, # Serialized ticket dicts
|
||||||
"mma_step_mode": bool,
|
"mma_step_mode": bool,
|
||||||
"pending_tool_approval": bool, # _pending_ask_dialog
|
"pending_tool_approval": bool, # _pending_ask_dialog
|
||||||
"pending_mma_step_approval": bool, # _pending_mma_approval is not None
|
"pending_mma_step_approval": bool, # _pending_mma_approval is not None
|
||||||
"pending_mma_spawn_approval": bool, # _pending_mma_spawn is not None
|
"pending_mma_spawn_approval": bool, # _pending_mma_spawn is not None
|
||||||
"pending_approval": bool, # Backward compat: step OR tool
|
"pending_approval": bool, # Backward compat: step OR tool
|
||||||
"pending_spawn": bool, # Alias for spawn approval
|
"pending_spawn": bool, # Alias for spawn approval
|
||||||
"tracks": list,
|
"tracks": list,
|
||||||
"proposed_tracks": list,
|
"proposed_tracks": list,
|
||||||
"mma_streams": dict, # {stream_id: output_text}
|
"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
|
```python
|
||||||
{
|
{
|
||||||
"thinking": bool, # ai_status in ["sending...", "running powershell..."]
|
"thinking": bool, # ai_status in ["sending...", "running powershell..."]
|
||||||
"live": bool, # ai_status in ["running powershell...", "fetching url...", ...]
|
"live": bool, # ai_status in ["running powershell...", "fetching url...", ...]
|
||||||
"prior": bool, # app.is_viewing_prior_session
|
"prior": bool, # app.is_viewing_prior_session
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -340,7 +340,7 @@ The counterpart `/api/ask/respond`:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
class ApiHookClient:
|
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
|
### Connection Methods
|
||||||
@@ -400,21 +400,21 @@ Tool calls are executed concurrently within a single AI turn using `asyncio.gath
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str:
|
||||||
"""
|
"""
|
||||||
Dispatch an MCP tool call by name asynchronously.
|
Dispatch an MCP tool call by name asynchronously.
|
||||||
Returns the result as a string.
|
Returns the result as a string.
|
||||||
"""
|
"""
|
||||||
# Run blocking I/O bound tools in a thread to allow parallel execution
|
# Run blocking I/O bound tools in a thread to allow parallel execution
|
||||||
return await asyncio.to_thread(dispatch, tool_name, tool_input)
|
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()`:
|
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
|
```python
|
||||||
results = await asyncio.gather(
|
results = await asyncio.gather(
|
||||||
async_dispatch("read_file", {"path": "src/module_a.py"}),
|
async_dispatch("read_file", {"path": "src/module_a.py"}),
|
||||||
async_dispatch("read_file", {"path": "src/module_b.py"}),
|
async_dispatch("read_file", {"path": "src/module_b.py"}),
|
||||||
async_dispatch("get_file_summary", {"path": "src/module_c.py"}),
|
async_dispatch("get_file_summary", {"path": "src/module_c.py"}),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -453,13 +453,13 @@ Summary:
|
|||||||
|
|
||||||
```
|
```
|
||||||
logs/sessions/<session_id>/
|
logs/sessions/<session_id>/
|
||||||
comms.log # JSON-L: every API interaction (direction, kind, payload)
|
comms.log # JSON-L: every API interaction (direction, kind, payload)
|
||||||
toolcalls.log # Markdown: sequential tool invocation records
|
toolcalls.log # Markdown: sequential tool invocation records
|
||||||
apihooks.log # API hook invocations
|
apihooks.log # API hook invocations
|
||||||
clicalls.log # JSON-L: CLI subprocess details (command, stdin, stdout, stderr, latency)
|
clicalls.log # JSON-L: CLI subprocess details (command, stdin, stdout, stderr, latency)
|
||||||
|
|
||||||
scripts/generated/
|
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
|
### Logging Functions
|
||||||
|
|||||||
Reference in New Issue
Block a user