more adjustments
This commit is contained in:
@@ -33,7 +33,7 @@ For deep implementation details when planning or implementing tracks, consult `d
|
||||
- **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.
|
||||
- **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.
|
||||
- **Track-Scoped State Management:** Segregates discussion history and task progress into per-track state files. Supports **Project-Specific Conductor Directories**, allowing projects to define their own conductor path in `manual_slop.toml` (`[conductor].dir`) 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).
|
||||
|
||||
- **Programmable Execution State machine:** Governing the transition between "Auto-Queue" (autonomous worker spawning) and "Step Mode" (explicit manual approval for each task transition).
|
||||
@@ -60,7 +60,7 @@ For deep implementation details when planning or implementing tracks, consult `d
|
||||
- **Dedicated Diagnostics Hub:** Consolidates real-time telemetry (FPS, CPU, Frame Time) and transient system warnings into a standalone **Diagnostics panel**, providing deep visibility into application health without polluting the discussion history.
|
||||
- **Improved MMA Observability:** Enhances sub-agent logging by injecting precise ticket IDs and descriptive roles into communication metadata, enabling granular filtering and tracking of parallel worker activities within the Comms History.
|
||||
- **In-Depth Toolset Access:** MCP-like file exploration, URL fetching, search, and dynamic context aggregation embedded within a multi-viewport Dear PyGui/ImGui interface.
|
||||
- **Integrated Workspace:** A consolidated Hub-based layout (Context, AI Settings, Discussion, Operations) designed for expert multi-monitor workflows. Features **GUI-Based Path Configuration** within the Context Hub, allowing users to view and edit system paths (conductor, logs, scripts) with real-time resolution source tracking (default, env, or config).
|
||||
- **Integrated Workspace:** A consolidated Hub-based layout (Context, AI Settings, Discussion, Operations) designed for expert multi-monitor workflows. Features **GUI-Based Path Configuration** within the Context Hub, allowing users to view and edit system paths (conductor, logs, scripts) with real-time resolution source tracking (default, env, or config). Changes are applied immediately at runtime without requiring an application restart.
|
||||
- **Session Analysis:** Ability to load and visualize historical session logs with a dedicated tinted "Prior Session" viewing mode.
|
||||
- **Structured Log Taxonomy:** Automated session-based log organization into configurable directories (defaulting to `logs/sessions/`). Includes a dedicated GUI panel for monitoring and manual whitelisting. Features an intelligent heuristic-based pruner that automatically cleans up insignificant logs older than 24 hours while preserving valuable sessions.
|
||||
- **Clean Project Root:** Enforces a "Cruft-Free Root" policy by organizing core implementation into a `src/` directory and redirecting all temporary test data, configurations, and AI-generated artifacts to `tests/artifacts/`.
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
- **ai_style_formatter.py:** Custom Python formatter specifically designed to enforce 1-space indentation and ultra-compact whitespace to minimize token consumption.
|
||||
|
||||
- **src/paths.py:** Centralized module for path resolution. Supports project-specific conductor directory overrides via project TOML (`[conductor].dir`), enabling isolated track management per project. All paths are resolved to absolute objects. Provides **Path Resolution Metadata**, exposing the source of each resolved path (default, environment variable, or configuration file) for high-fidelity GUI display. Path configuration (logs, conductor, scripts) can also be configured via `config.toml` or environment variables, eliminating hardcoded filesystem dependencies.
|
||||
- **src/paths.py:** Centralized module for path resolution. Supports project-specific conductor directory overrides via project TOML (`[conductor].dir`), enabling isolated track management per project. If not specified, conductor paths default to `./conductor` relative to each project's TOML file. All paths are resolved to absolute objects. Provides **Path Resolution Metadata**, exposing the source of each resolved path (default, environment variable, or configuration file) for high-fidelity GUI display. Supports **Runtime Re-Resolution** via `reset_resolved()`, allowing path changes to be applied immediately without an application restart. Path configuration (logs, scripts) can also be configured via `config.toml` or environment variables, eliminating hardcoded filesystem dependencies.
|
||||
|
||||
- **src/presets.py:** Implements `PresetManager` for high-performance CRUD operations on system prompt presets stored in TOML format (`presets.toml`, `project_presets.toml`). Supports dynamic path resolution and scope-based inheritance.
|
||||
|
||||
|
||||
22
config.toml
22
config.toml
@@ -1,12 +1,12 @@
|
||||
[ai]
|
||||
provider = "gemini_cli"
|
||||
model = "gemini-2.5-flash-lite"
|
||||
temperature = 0.85
|
||||
provider = "minimax"
|
||||
model = "MiniMax-M2.5"
|
||||
temperature = 0.0
|
||||
top_p = 1.0
|
||||
max_tokens = 1024
|
||||
max_tokens = 32000
|
||||
history_trunc_limit = 900000
|
||||
active_preset = ""
|
||||
system_prompt = "Overridden Prompt"
|
||||
active_preset = "Default"
|
||||
system_prompt = ""
|
||||
|
||||
[projects]
|
||||
paths = [
|
||||
@@ -17,7 +17,7 @@ paths = [
|
||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
|
||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml",
|
||||
]
|
||||
active = "C:\\projects\\manual_slop\\tests\\artifacts\\live_gui_workspace\\manual_slop.toml"
|
||||
active = "C:/projects/gencpp/gencpp_sloppy.toml"
|
||||
|
||||
[gui]
|
||||
separate_message_panel = false
|
||||
@@ -38,7 +38,7 @@ separate_tier4 = false
|
||||
"AI Settings" = true
|
||||
"MMA Dashboard" = true
|
||||
"Task DAG" = true
|
||||
"Usage Analytics" = true
|
||||
"Usage Analytics" = false
|
||||
"Tier 1" = false
|
||||
"Tier 2" = false
|
||||
"Tier 3" = false
|
||||
@@ -54,7 +54,6 @@ Response = false
|
||||
"Tool Calls" = false
|
||||
Theme = true
|
||||
"Log Management" = true
|
||||
Diagnostics = false
|
||||
|
||||
[theme]
|
||||
palette = "Nord Dark"
|
||||
@@ -69,3 +68,8 @@ max_workers = 4
|
||||
|
||||
[headless]
|
||||
api_key = "test-secret-key"
|
||||
|
||||
[paths]
|
||||
conductor_dir = "C:\\projects\\gencpp\\.ai\\conductor"
|
||||
logs_dir = "C:\\projects\\manual_slop\\logs"
|
||||
scripts_dir = "C:\\projects\\manual_slop\\scripts"
|
||||
|
||||
@@ -54,8 +54,8 @@ Size=1111,224
|
||||
Collapsed=0
|
||||
|
||||
[Window][Tool Calls]
|
||||
Pos=790,1483
|
||||
Size=876,654
|
||||
Pos=855,1482
|
||||
Size=1014,655
|
||||
Collapsed=0
|
||||
DockId=0x00000006,0
|
||||
|
||||
@@ -74,8 +74,8 @@ Collapsed=0
|
||||
DockId=0xAFC85805,2
|
||||
|
||||
[Window][Theme]
|
||||
Pos=0,1786
|
||||
Size=676,351
|
||||
Pos=0,1212
|
||||
Size=853,925
|
||||
Collapsed=0
|
||||
DockId=0x00000002,2
|
||||
|
||||
@@ -91,8 +91,8 @@ Collapsed=0
|
||||
DockId=0x00000010,2
|
||||
|
||||
[Window][Context Hub]
|
||||
Pos=0,1786
|
||||
Size=676,351
|
||||
Pos=0,1212
|
||||
Size=853,925
|
||||
Collapsed=0
|
||||
DockId=0x00000002,1
|
||||
|
||||
@@ -103,26 +103,26 @@ Collapsed=0
|
||||
DockId=0x0000000D,0
|
||||
|
||||
[Window][Discussion Hub]
|
||||
Pos=1668,22
|
||||
Size=915,2115
|
||||
Pos=1871,22
|
||||
Size=949,2115
|
||||
Collapsed=0
|
||||
DockId=0x00000013,0
|
||||
|
||||
[Window][Operations Hub]
|
||||
Pos=678,22
|
||||
Size=988,2115
|
||||
Pos=855,22
|
||||
Size=1014,2115
|
||||
Collapsed=0
|
||||
DockId=0x00000005,0
|
||||
|
||||
[Window][Files & Media]
|
||||
Pos=0,1786
|
||||
Size=676,351
|
||||
Pos=0,1212
|
||||
Size=853,925
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][AI Settings]
|
||||
Pos=0,22
|
||||
Size=676,1762
|
||||
Size=853,1188
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
|
||||
@@ -132,14 +132,14 @@ Size=416,325
|
||||
Collapsed=0
|
||||
|
||||
[Window][MMA Dashboard]
|
||||
Pos=2585,22
|
||||
Size=1255,2115
|
||||
Pos=2822,22
|
||||
Size=1018,2115
|
||||
Collapsed=0
|
||||
DockId=0x00000010,0
|
||||
|
||||
[Window][Log Management]
|
||||
Pos=2585,22
|
||||
Size=1255,2115
|
||||
Pos=2822,22
|
||||
Size=1018,2115
|
||||
Collapsed=0
|
||||
DockId=0x00000010,1
|
||||
|
||||
@@ -333,8 +333,8 @@ Size=967,499
|
||||
Collapsed=0
|
||||
|
||||
[Window][Usage Analytics]
|
||||
Pos=2641,1719
|
||||
Size=1199,418
|
||||
Pos=2822,1716
|
||||
Size=1018,421
|
||||
Collapsed=0
|
||||
DockId=0x0000000F,0
|
||||
|
||||
@@ -384,11 +384,11 @@ Column 3 Width=20
|
||||
Column 4 Weight=1.0000
|
||||
|
||||
[Table][0x2A6000B6,4]
|
||||
RefScale=24
|
||||
Column 0 Width=72
|
||||
Column 1 Width=106
|
||||
RefScale=14
|
||||
Column 0 Width=42
|
||||
Column 1 Width=61
|
||||
Column 2 Weight=1.0000
|
||||
Column 3 Width=180
|
||||
Column 3 Width=105
|
||||
|
||||
[Table][0x8BCC69C7,6]
|
||||
RefScale=13
|
||||
@@ -407,11 +407,11 @@ Column 2 Weight=1.0000
|
||||
Column 3 Width=105
|
||||
|
||||
[Table][0x2C515046,4]
|
||||
RefScale=24
|
||||
Column 0 Width=73
|
||||
RefScale=14
|
||||
Column 0 Width=42
|
||||
Column 1 Weight=1.0000
|
||||
Column 2 Width=181
|
||||
Column 3 Width=72
|
||||
Column 2 Width=105
|
||||
Column 3 Width=42
|
||||
|
||||
[Table][0xD99F45C5,4]
|
||||
Column 0 Sort=0v
|
||||
@@ -467,18 +467,18 @@ DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,22 Size=3840,2115 Split=X
|
||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2583,1183 Split=X
|
||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2820,1183 Split=X
|
||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=676,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,1759 CentralNode=1 Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,351 Selected=0x1DCB2623
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1905,858 Split=X Selected=0x418C7449
|
||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=988,402 Split=Y Selected=0x418C7449
|
||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=853,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,1188 CentralNode=1 Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,925 Selected=0xF4139CA2
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1965,858 Split=X Selected=0x418C7449
|
||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=1014,402 Split=Y Selected=0x418C7449
|
||||
DockNode ID=0x00000005 Parent=0x00000012 SizeRef=876,1455 Selected=0x418C7449
|
||||
DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,654 Selected=0x1D56B311
|
||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=915,402 Selected=0x6F2B5B04
|
||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=949,402 Selected=0x6F2B5B04
|
||||
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1255,1183 Split=Y Selected=0x3AEC3498
|
||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1018,1183 Split=Y Selected=0x3AEC3498
|
||||
DockNode ID=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0x2C0206CE
|
||||
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Split=X Selected=0xDEB547B6
|
||||
DockNode ID=0x0000000C Parent=0x00000011 SizeRef=916,380 Selected=0x655BC6E9
|
||||
|
||||
@@ -2416,3 +2416,49 @@ PROMPT:
|
||||
role: tool
|
||||
Here are the results: {"content": "done"}
|
||||
------------------
|
||||
--- MOCK INVOKED ---
|
||||
ARGS: ['tests/mock_gemini_cli.py']
|
||||
PROMPT:
|
||||
PATH: Epic Initialization — please produce tracks
|
||||
------------------
|
||||
--- MOCK INVOKED ---
|
||||
ARGS: ['tests/mock_gemini_cli.py']
|
||||
PROMPT:
|
||||
Please generate the implementation tickets for this track.
|
||||
------------------
|
||||
--- MOCK INVOKED ---
|
||||
ARGS: ['tests/mock_gemini_cli.py']
|
||||
PROMPT:
|
||||
Please read test.txt
|
||||
You are assigned to Ticket T1.
|
||||
Task Description: do something
|
||||
------------------
|
||||
--- MOCK INVOKED ---
|
||||
ARGS: ['tests/mock_gemini_cli.py']
|
||||
PROMPT:
|
||||
role: tool
|
||||
Here are the results: {"content": "done"}
|
||||
------------------
|
||||
--- MOCK INVOKED ---
|
||||
ARGS: ['tests/mock_gemini_cli.py']
|
||||
PROMPT:
|
||||
PATH: Epic Initialization — please produce tracks
|
||||
------------------
|
||||
--- MOCK INVOKED ---
|
||||
ARGS: ['tests/mock_gemini_cli.py']
|
||||
PROMPT:
|
||||
Please generate the implementation tickets for this track.
|
||||
------------------
|
||||
--- MOCK INVOKED ---
|
||||
ARGS: ['tests/mock_gemini_cli.py']
|
||||
PROMPT:
|
||||
Please read test.txt
|
||||
You are assigned to Ticket T1.
|
||||
Task Description: do something
|
||||
------------------
|
||||
--- MOCK INVOKED ---
|
||||
ARGS: ['tests/mock_gemini_cli.py']
|
||||
PROMPT:
|
||||
role: tool
|
||||
Here are the results: {"content": "done"}
|
||||
------------------
|
||||
|
||||
@@ -9,5 +9,5 @@ active = "main"
|
||||
|
||||
[discussions.main]
|
||||
git_commit = ""
|
||||
last_updated = "2026-03-11T23:45:09"
|
||||
last_updated = "2026-03-12T18:41:55"
|
||||
history = []
|
||||
|
||||
@@ -852,7 +852,6 @@ class AppController:
|
||||
self.ui_separate_tier4 = False
|
||||
self.config = models.load_config()
|
||||
path_info = paths.get_full_path_info()
|
||||
self.ui_conductor_dir = str(path_info['conductor_dir']['path'])
|
||||
self.ui_logs_dir = str(path_info['logs_dir']['path'])
|
||||
self.ui_scripts_dir = str(path_info['scripts_dir']['path'])
|
||||
theme.load_from_config(self.config)
|
||||
@@ -890,6 +889,7 @@ class AppController:
|
||||
self.ui_shots_base_dir = self.project.get("screenshots", {}).get("base_dir", ".")
|
||||
proj_meta = self.project.get("project", {})
|
||||
self.ui_project_git_dir = proj_meta.get("git_dir", "")
|
||||
self.ui_project_conductor_dir = self.project.get('conductor', {}).get('dir', 'conductor')
|
||||
self.ui_project_main_context = proj_meta.get("main_context", "")
|
||||
self.ui_project_system_prompt = proj_meta.get("system_prompt", "")
|
||||
self.ui_gemini_cli_path = self.project.get("gemini_cli", {}).get("binary_path", "gemini")
|
||||
@@ -962,7 +962,7 @@ class AppController:
|
||||
agent_tools_cfg = self.project.get("agent", {}).get("tools", {})
|
||||
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES}
|
||||
label = self.project.get("project", {}).get("name", "")
|
||||
session_logger.open_session(label=label)
|
||||
session_logger.reset_session(label=label)
|
||||
# Trigger auto-start of MCP servers
|
||||
self.event_queue.put('refresh_external_mcps', None)
|
||||
|
||||
@@ -2295,6 +2295,7 @@ class AppController:
|
||||
proj["screenshots"]["paths"] = self.screenshots
|
||||
proj.setdefault("project", {})
|
||||
proj["project"]["git_dir"] = self.ui_project_git_dir
|
||||
proj.setdefault("conductor", {})["dir"] = self.ui_project_conductor_dir
|
||||
proj["project"]["system_prompt"] = self.ui_project_system_prompt
|
||||
proj["project"]["main_context"] = self.ui_project_main_context
|
||||
proj["project"]["active_preset"] = self.ui_project_preset_name
|
||||
|
||||
18
src/gui_2.py
18
src/gui_2.py
@@ -1419,6 +1419,15 @@ class App:
|
||||
r.destroy()
|
||||
if d: self.ui_output_dir = d
|
||||
imgui.separator()
|
||||
imgui.text("Conductor Directory")
|
||||
ch, self.ui_project_conductor_dir = imgui.input_text("##cond_dir", self.ui_project_conductor_dir)
|
||||
imgui.same_line()
|
||||
if imgui.button("Browse##cond"):
|
||||
r = hide_tk_root()
|
||||
d = filedialog.askdirectory(title="Select Conductor Directory")
|
||||
r.destroy()
|
||||
if d: self.ui_project_conductor_dir = d
|
||||
imgui.separator()
|
||||
imgui.text("Project Files")
|
||||
imgui.begin_child("proj_files", imgui.ImVec2(0, 150), True)
|
||||
for i, pp in enumerate(self.project_paths):
|
||||
@@ -1473,7 +1482,6 @@ class App:
|
||||
|
||||
def _save_paths(self):
|
||||
self.config["paths"] = {
|
||||
"conductor_dir": self.ui_conductor_dir,
|
||||
"logs_dir": self.ui_logs_dir,
|
||||
"scripts_dir": self.ui_scripts_dir
|
||||
}
|
||||
@@ -1482,7 +1490,8 @@ class App:
|
||||
shutil.copy(cfg_path, str(cfg_path) + ".bak")
|
||||
models.save_config(self.config)
|
||||
paths.reset_resolved()
|
||||
self.ai_status = "paths saved - restart required"
|
||||
self.init_state()
|
||||
self.ai_status = 'paths applied and session reset'
|
||||
|
||||
def _render_paths_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_paths_panel")
|
||||
@@ -1491,10 +1500,6 @@ class App:
|
||||
imgui.text_colored(C_IN, "System Path Configuration")
|
||||
imgui.separator()
|
||||
|
||||
if self.ai_status == "paths saved - restart required":
|
||||
imgui.text_colored(vec4(255, 50, 50), "Restart required for path changes to take effect.")
|
||||
imgui.separator()
|
||||
|
||||
def render_path_field(label: str, attr: str, key: str, tooltip: str):
|
||||
info = path_info.get(key, {'source': 'unknown'})
|
||||
imgui.text(label)
|
||||
@@ -1513,7 +1518,6 @@ class App:
|
||||
r.destroy()
|
||||
if d: setattr(self, attr, d)
|
||||
|
||||
render_path_field("Conductor Directory", "ui_conductor_dir", "conductor_dir", "Base directory for implementation tracks and project state.")
|
||||
render_path_field("Logs Directory", "ui_logs_dir", "logs_dir", "Directory where session JSON-L logs and artifacts are stored.")
|
||||
render_path_field("Scripts Directory", "ui_scripts_dir", "scripts_dir", "Directory for AI-generated PowerShell scripts.")
|
||||
|
||||
|
||||
39
src/paths.py
39
src/paths.py
@@ -6,13 +6,11 @@ This module provides centralized path resolution for all configurable paths in t
|
||||
|
||||
Environment Variables:
|
||||
SLOP_CONFIG: Path to config.toml
|
||||
SLOP_CONDUCTOR_DIR: Path to conductor directory
|
||||
SLOP_LOGS_DIR: Path to logs directory
|
||||
SLOP_SCRIPTS_DIR: Path to generated scripts directory
|
||||
|
||||
Configuration (config.toml):
|
||||
[paths]
|
||||
conductor_dir = "conductor"
|
||||
logs_dir = "logs/sessions"
|
||||
scripts_dir = "scripts/generated"
|
||||
|
||||
@@ -27,8 +25,8 @@ Path Functions:
|
||||
|
||||
Resolution Order:
|
||||
1. Check project-specific manual_slop.toml (for conductor paths)
|
||||
2. Check environment variable
|
||||
3. Check config.toml [paths] section
|
||||
2. Check environment variable (for logs/scripts)
|
||||
3. Check config.toml [paths] section (for logs/scripts)
|
||||
4. Fall back to default
|
||||
|
||||
Usage:
|
||||
@@ -110,37 +108,15 @@ def _get_project_conductor_dir_from_toml(project_root: Path) -> Optional[Path]:
|
||||
return None
|
||||
|
||||
def get_conductor_dir(project_path: Optional[str] = None) -> Path:
|
||||
if project_path:
|
||||
if not project_path:
|
||||
# Fallback for legacy/tests, but we should avoid this
|
||||
return Path('conductor').resolve()
|
||||
|
||||
project_root = Path(project_path).resolve()
|
||||
p = _get_project_conductor_dir_from_toml(project_root)
|
||||
if p: return p
|
||||
|
||||
if "conductor_dir" not in _RESOLVED:
|
||||
# Check env and config
|
||||
root_dir = Path(__file__).resolve().parent.parent
|
||||
env_val = os.environ.get("SLOP_CONDUCTOR_DIR")
|
||||
if env_val:
|
||||
p = Path(env_val)
|
||||
if not p.is_absolute(): p = root_dir / p
|
||||
_RESOLVED["conductor_dir"] = p.resolve()
|
||||
else:
|
||||
try:
|
||||
with open(get_config_path(), "rb") as f:
|
||||
cfg = tomllib.load(f)
|
||||
if "paths" in cfg and "conductor_dir" in cfg["paths"]:
|
||||
p = Path(cfg["paths"]["conductor_dir"])
|
||||
if not p.is_absolute(): p = root_dir / p
|
||||
_RESOLVED["conductor_dir"] = p.resolve()
|
||||
except: pass
|
||||
|
||||
if "conductor_dir" in _RESOLVED:
|
||||
return _RESOLVED["conductor_dir"]
|
||||
|
||||
if project_path:
|
||||
return (Path(project_path).resolve() / "conductor").resolve()
|
||||
|
||||
root_dir = Path(__file__).resolve().parent.parent
|
||||
return (root_dir / "conductor").resolve()
|
||||
return (project_root / "conductor").resolve()
|
||||
|
||||
def get_logs_dir() -> Path:
|
||||
if "logs_dir" not in _RESOLVED:
|
||||
@@ -179,7 +155,6 @@ def _resolve_path_info(env_var: str, config_key: str, default: str) -> dict[str,
|
||||
|
||||
def get_full_path_info() -> dict[str, dict[str, Any]]:
|
||||
return {
|
||||
'conductor_dir': _resolve_path_info('SLOP_CONDUCTOR_DIR', 'conductor_dir', 'conductor'),
|
||||
'logs_dir': _resolve_path_info('SLOP_LOGS_DIR', 'logs_dir', 'logs/sessions'),
|
||||
'scripts_dir': _resolve_path_info('SLOP_SCRIPTS_DIR', 'scripts_dir', 'scripts/generated')
|
||||
}
|
||||
|
||||
@@ -112,6 +112,11 @@ def close_session() -> None:
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not update auto-whitelist on close: {e}")
|
||||
|
||||
def reset_session(label: Optional[str] = None) -> None:
|
||||
"""Closes the current session and opens a new one with the given label."""
|
||||
close_session()
|
||||
open_session(label)
|
||||
|
||||
def log_api_hook(method: str, path: str, payload: str) -> None:
|
||||
"""Log an API hook invocation."""
|
||||
if _api_fh is None:
|
||||
|
||||
@@ -11,6 +11,9 @@ class MockApp:
|
||||
self.config = {"paths": {}}
|
||||
self.ai_status = ""
|
||||
|
||||
def init_state(self):
|
||||
pass
|
||||
|
||||
from src.gui_2 import App
|
||||
_save_paths = App._save_paths
|
||||
|
||||
@@ -20,7 +23,8 @@ def test_save_paths():
|
||||
with patch('src.models.save_config') as mock_save, \
|
||||
patch('shutil.copy') as mock_copy, \
|
||||
patch('src.paths.get_config_path') as mock_get_cfg, \
|
||||
patch('src.paths.reset_resolved') as mock_reset:
|
||||
patch('src.paths.reset_resolved') as mock_reset, \
|
||||
patch.object(MockApp, 'init_state') as mock_init:
|
||||
|
||||
mock_get_cfg.return_value = MagicMock()
|
||||
mock_get_cfg.return_value.exists.return_value = True
|
||||
@@ -29,8 +33,9 @@ def test_save_paths():
|
||||
mock_app._save_paths()
|
||||
|
||||
# Verify config update
|
||||
assert mock_app.config['paths']['conductor_dir'] == '/new/conductor'
|
||||
assert 'conductor_dir' not in mock_app.config['paths']
|
||||
mock_save.assert_called_once()
|
||||
mock_copy.assert_called_once()
|
||||
assert 'restart required' in mock_app.ai_status
|
||||
assert 'applied' in mock_app.ai_status
|
||||
mock_reset.assert_called_once()
|
||||
mock_init.assert_called_once()
|
||||
|
||||
@@ -9,25 +9,15 @@ def reset_paths():
|
||||
yield
|
||||
paths.reset_resolved()
|
||||
|
||||
def test_default_paths():
|
||||
def test_default_paths(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("SLOP_CONFIG", str(tmp_path / "non_existent.toml"))
|
||||
root_dir = Path(paths.__file__).resolve().parent.parent
|
||||
assert paths.get_conductor_dir() == root_dir / "conductor"
|
||||
assert paths.get_logs_dir() == root_dir / "logs/sessions"
|
||||
assert paths.get_scripts_dir() == root_dir / "scripts/generated"
|
||||
# config path should now be an absolute path relative to src/paths.py
|
||||
assert paths.get_config_path() == root_dir / "config.toml"
|
||||
assert paths.get_tracks_dir() == root_dir / "conductor/tracks"
|
||||
assert paths.get_archive_dir() == root_dir / "conductor/archive"
|
||||
# config path should be what we set in env
|
||||
assert paths.get_config_path() == tmp_path / "non_existent.toml"
|
||||
|
||||
def test_env_var_overrides(tmp_path, monkeypatch):
|
||||
root_dir = Path(paths.__file__).resolve().parent.parent
|
||||
|
||||
# Relative env var (resolved against root_dir)
|
||||
monkeypatch.setenv("SLOP_CONDUCTOR_DIR", "custom_conductor")
|
||||
assert paths.get_conductor_dir() == (root_dir / "custom_conductor").resolve()
|
||||
|
||||
paths.reset_resolved()
|
||||
|
||||
# Absolute env var
|
||||
abs_logs = (tmp_path / "abs_logs").resolve()
|
||||
monkeypatch.setenv("SLOP_LOGS_DIR", str(abs_logs))
|
||||
@@ -38,14 +28,12 @@ def test_config_overrides(tmp_path, monkeypatch):
|
||||
config_file = tmp_path / "custom_config.toml"
|
||||
content = """
|
||||
[paths]
|
||||
conductor_dir = "cfg_conductor"
|
||||
logs_dir = "cfg_logs"
|
||||
scripts_dir = "cfg_scripts"
|
||||
"""
|
||||
config_file.write_text(content)
|
||||
monkeypatch.setenv("SLOP_CONFIG", str(config_file))
|
||||
|
||||
assert paths.get_conductor_dir() == (root_dir / "cfg_conductor").resolve()
|
||||
assert paths.get_logs_dir() == root_dir / "cfg_logs"
|
||||
assert paths.get_scripts_dir() == root_dir / "cfg_scripts"
|
||||
|
||||
@@ -54,11 +42,20 @@ def test_precedence(tmp_path, monkeypatch):
|
||||
config_file = tmp_path / "custom_config.toml"
|
||||
content = """
|
||||
[paths]
|
||||
conductor_dir = "cfg_conductor"
|
||||
logs_dir = "cfg_logs"
|
||||
"""
|
||||
config_file.write_text(content)
|
||||
monkeypatch.setenv("SLOP_CONFIG", str(config_file))
|
||||
monkeypatch.setenv("SLOP_CONDUCTOR_DIR", "env_conductor")
|
||||
monkeypatch.setenv("SLOP_LOGS_DIR", "env_logs")
|
||||
|
||||
# Env var should take precedence over config
|
||||
assert paths.get_conductor_dir() == (root_dir / "env_conductor").resolve()
|
||||
assert paths.get_logs_dir() == (root_dir / "env_logs").resolve()
|
||||
|
||||
def test_conductor_dir_project_relative(tmp_path):
|
||||
# Should default to tmp_path/conductor
|
||||
project_path = str(tmp_path)
|
||||
base = (tmp_path / 'conductor').resolve()
|
||||
assert paths.get_conductor_dir(project_path) == base
|
||||
assert paths.get_tracks_dir(project_path) == base / "tracks"
|
||||
assert paths.get_archive_dir(project_path) == base / "archive"
|
||||
assert paths.get_track_state_dir("test_track", project_path) == base / "tracks" / "test_track"
|
||||
|
||||
@@ -57,32 +57,3 @@ def test_get_all_tracks_project_specific(tmp_path):
|
||||
tracks = project_manager.get_all_tracks(str(project_root))
|
||||
assert len(tracks) == 1
|
||||
assert tracks[0]["title"] == "Test Track"
|
||||
|
||||
def test_get_all_tracks_global_fallback(tmp_path):
|
||||
paths.reset_resolved()
|
||||
|
||||
# Create a directory without manual_slop.toml
|
||||
empty_dir = tmp_path / "empty_project"
|
||||
empty_dir.mkdir()
|
||||
|
||||
# Setup a fake global conductor
|
||||
global_conductor = tmp_path / "global_conductor"
|
||||
global_conductor.mkdir()
|
||||
global_tracks = global_conductor / "tracks"
|
||||
global_tracks.mkdir()
|
||||
|
||||
track_dir = global_tracks / "global_track"
|
||||
track_dir.mkdir()
|
||||
with open(track_dir / "metadata.json", "w") as f:
|
||||
json.dump({"id": "global_track", "title": "Global Track"}, f)
|
||||
|
||||
# Override global conductor dir via env var
|
||||
os.environ["SLOP_CONDUCTOR_DIR"] = str(global_conductor)
|
||||
try:
|
||||
paths.reset_resolved()
|
||||
# Pass project_path pointing to a dir without TOML
|
||||
tracks = project_manager.get_all_tracks(str(empty_dir))
|
||||
# paths.get_conductor_dir(str(empty_dir)) should fall back to global
|
||||
assert any(t["id"] == "global_track" for t in tracks)
|
||||
finally:
|
||||
del os.environ["SLOP_CONDUCTOR_DIR"]
|
||||
|
||||
51
tests/test_session_logger_reset.py
Normal file
51
tests/test_session_logger_reset.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import pytest
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
from src import session_logger
|
||||
import time
|
||||
|
||||
@pytest.fixture
|
||||
def temp_logs(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Generator[Path, None, None]:
|
||||
# Ensure closed before starting
|
||||
session_logger.close_session()
|
||||
monkeypatch.setattr(session_logger, "_comms_fh", None)
|
||||
|
||||
log_dir = tmp_path / "logs"
|
||||
scripts_dir = tmp_path / "scripts" / "generated"
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
scripts_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
from src import paths
|
||||
monkeypatch.setattr(paths, "get_logs_dir", lambda: log_dir)
|
||||
monkeypatch.setattr(paths, "get_scripts_dir", lambda: scripts_dir)
|
||||
|
||||
yield log_dir
|
||||
# Cleanup: Close handles if open
|
||||
session_logger.close_session()
|
||||
|
||||
def test_reset_session(temp_logs: Path) -> None:
|
||||
# 1. Open first session
|
||||
label1 = "label1"
|
||||
session_logger.open_session(label=label1)
|
||||
|
||||
subdirs = [d for d in temp_logs.iterdir() if d.is_dir()]
|
||||
assert len(subdirs) == 1
|
||||
session1_dir = subdirs[0]
|
||||
assert session1_dir.name.endswith(f"_{label1}")
|
||||
assert session_logger._comms_fh is not None
|
||||
|
||||
# 2. Reset to second session
|
||||
time.sleep(1.1)
|
||||
|
||||
label2 = "label2"
|
||||
session_logger.reset_session(label=label2)
|
||||
|
||||
subdirs = sorted([d for d in temp_logs.iterdir() if d.is_dir()], key=lambda x: x.name)
|
||||
assert len(subdirs) == 2
|
||||
session2_dir = subdirs[1]
|
||||
assert session2_dir.name.endswith(f"_{label2}")
|
||||
assert session_logger._comms_fh is not None
|
||||
|
||||
# Verify new handle points to new dir
|
||||
assert str(session2_dir) in str(Path(session_logger._comms_fh.name).resolve())
|
||||
Reference in New Issue
Block a user