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.
|
- **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.
|
||||||
- **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).
|
**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).
|
||||||
@@ -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.
|
- **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.
|
- **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.
|
- **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.
|
- **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.
|
- **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/`.
|
- **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.
|
- **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.
|
- **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]
|
[ai]
|
||||||
provider = "gemini_cli"
|
provider = "minimax"
|
||||||
model = "gemini-2.5-flash-lite"
|
model = "MiniMax-M2.5"
|
||||||
temperature = 0.85
|
temperature = 0.0
|
||||||
top_p = 1.0
|
top_p = 1.0
|
||||||
max_tokens = 1024
|
max_tokens = 32000
|
||||||
history_trunc_limit = 900000
|
history_trunc_limit = 900000
|
||||||
active_preset = ""
|
active_preset = "Default"
|
||||||
system_prompt = "Overridden Prompt"
|
system_prompt = ""
|
||||||
|
|
||||||
[projects]
|
[projects]
|
||||||
paths = [
|
paths = [
|
||||||
@@ -17,7 +17,7 @@ paths = [
|
|||||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
|
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
|
||||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.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]
|
[gui]
|
||||||
separate_message_panel = false
|
separate_message_panel = false
|
||||||
@@ -38,7 +38,7 @@ separate_tier4 = false
|
|||||||
"AI Settings" = true
|
"AI Settings" = true
|
||||||
"MMA Dashboard" = true
|
"MMA Dashboard" = true
|
||||||
"Task DAG" = true
|
"Task DAG" = true
|
||||||
"Usage Analytics" = true
|
"Usage Analytics" = false
|
||||||
"Tier 1" = false
|
"Tier 1" = false
|
||||||
"Tier 2" = false
|
"Tier 2" = false
|
||||||
"Tier 3" = false
|
"Tier 3" = false
|
||||||
@@ -54,7 +54,6 @@ Response = false
|
|||||||
"Tool Calls" = false
|
"Tool Calls" = false
|
||||||
Theme = true
|
Theme = true
|
||||||
"Log Management" = true
|
"Log Management" = true
|
||||||
Diagnostics = false
|
|
||||||
|
|
||||||
[theme]
|
[theme]
|
||||||
palette = "Nord Dark"
|
palette = "Nord Dark"
|
||||||
@@ -69,3 +68,8 @@ max_workers = 4
|
|||||||
|
|
||||||
[headless]
|
[headless]
|
||||||
api_key = "test-secret-key"
|
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
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Tool Calls]
|
[Window][Tool Calls]
|
||||||
Pos=790,1483
|
Pos=855,1482
|
||||||
Size=876,654
|
Size=1014,655
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
DockId=0x00000006,0
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@ Collapsed=0
|
|||||||
DockId=0xAFC85805,2
|
DockId=0xAFC85805,2
|
||||||
|
|
||||||
[Window][Theme]
|
[Window][Theme]
|
||||||
Pos=0,1786
|
Pos=0,1212
|
||||||
Size=676,351
|
Size=853,925
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,2
|
DockId=0x00000002,2
|
||||||
|
|
||||||
@@ -91,8 +91,8 @@ Collapsed=0
|
|||||||
DockId=0x00000010,2
|
DockId=0x00000010,2
|
||||||
|
|
||||||
[Window][Context Hub]
|
[Window][Context Hub]
|
||||||
Pos=0,1786
|
Pos=0,1212
|
||||||
Size=676,351
|
Size=853,925
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000002,1
|
||||||
|
|
||||||
@@ -103,26 +103,26 @@ Collapsed=0
|
|||||||
DockId=0x0000000D,0
|
DockId=0x0000000D,0
|
||||||
|
|
||||||
[Window][Discussion Hub]
|
[Window][Discussion Hub]
|
||||||
Pos=1668,22
|
Pos=1871,22
|
||||||
Size=915,2115
|
Size=949,2115
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000013,0
|
DockId=0x00000013,0
|
||||||
|
|
||||||
[Window][Operations Hub]
|
[Window][Operations Hub]
|
||||||
Pos=678,22
|
Pos=855,22
|
||||||
Size=988,2115
|
Size=1014,2115
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,0
|
DockId=0x00000005,0
|
||||||
|
|
||||||
[Window][Files & Media]
|
[Window][Files & Media]
|
||||||
Pos=0,1786
|
Pos=0,1212
|
||||||
Size=676,351
|
Size=853,925
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000002,0
|
||||||
|
|
||||||
[Window][AI Settings]
|
[Window][AI Settings]
|
||||||
Pos=0,22
|
Pos=0,22
|
||||||
Size=676,1762
|
Size=853,1188
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000001,0
|
DockId=0x00000001,0
|
||||||
|
|
||||||
@@ -132,14 +132,14 @@ Size=416,325
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][MMA Dashboard]
|
[Window][MMA Dashboard]
|
||||||
Pos=2585,22
|
Pos=2822,22
|
||||||
Size=1255,2115
|
Size=1018,2115
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000010,0
|
DockId=0x00000010,0
|
||||||
|
|
||||||
[Window][Log Management]
|
[Window][Log Management]
|
||||||
Pos=2585,22
|
Pos=2822,22
|
||||||
Size=1255,2115
|
Size=1018,2115
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000010,1
|
DockId=0x00000010,1
|
||||||
|
|
||||||
@@ -333,8 +333,8 @@ Size=967,499
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Usage Analytics]
|
[Window][Usage Analytics]
|
||||||
Pos=2641,1719
|
Pos=2822,1716
|
||||||
Size=1199,418
|
Size=1018,421
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000F,0
|
DockId=0x0000000F,0
|
||||||
|
|
||||||
@@ -384,11 +384,11 @@ Column 3 Width=20
|
|||||||
Column 4 Weight=1.0000
|
Column 4 Weight=1.0000
|
||||||
|
|
||||||
[Table][0x2A6000B6,4]
|
[Table][0x2A6000B6,4]
|
||||||
RefScale=24
|
RefScale=14
|
||||||
Column 0 Width=72
|
Column 0 Width=42
|
||||||
Column 1 Width=106
|
Column 1 Width=61
|
||||||
Column 2 Weight=1.0000
|
Column 2 Weight=1.0000
|
||||||
Column 3 Width=180
|
Column 3 Width=105
|
||||||
|
|
||||||
[Table][0x8BCC69C7,6]
|
[Table][0x8BCC69C7,6]
|
||||||
RefScale=13
|
RefScale=13
|
||||||
@@ -407,11 +407,11 @@ Column 2 Weight=1.0000
|
|||||||
Column 3 Width=105
|
Column 3 Width=105
|
||||||
|
|
||||||
[Table][0x2C515046,4]
|
[Table][0x2C515046,4]
|
||||||
RefScale=24
|
RefScale=14
|
||||||
Column 0 Width=73
|
Column 0 Width=42
|
||||||
Column 1 Weight=1.0000
|
Column 1 Weight=1.0000
|
||||||
Column 2 Width=181
|
Column 2 Width=105
|
||||||
Column 3 Width=72
|
Column 3 Width=42
|
||||||
|
|
||||||
[Table][0xD99F45C5,4]
|
[Table][0xD99F45C5,4]
|
||||||
Column 0 Sort=0v
|
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=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,22 Size=3840,2115 Split=X
|
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=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=676,858 Split=Y Selected=0x8CA2375C
|
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=853,858 Split=Y Selected=0x8CA2375C
|
||||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,1759 CentralNode=1 Selected=0x7BD57D6A
|
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,1188 CentralNode=1 Selected=0x7BD57D6A
|
||||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,351 Selected=0x1DCB2623
|
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,925 Selected=0xF4139CA2
|
||||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1905,858 Split=X Selected=0x418C7449
|
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1965,858 Split=X Selected=0x418C7449
|
||||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=988,402 Split=Y 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=0x00000005 Parent=0x00000012 SizeRef=876,1455 Selected=0x418C7449
|
||||||
DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,654 Selected=0x1D56B311
|
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=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=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0x2C0206CE
|
||||||
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Split=X Selected=0xDEB547B6
|
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Split=X Selected=0xDEB547B6
|
||||||
DockNode ID=0x0000000C Parent=0x00000011 SizeRef=916,380 Selected=0x655BC6E9
|
DockNode ID=0x0000000C Parent=0x00000011 SizeRef=916,380 Selected=0x655BC6E9
|
||||||
|
|||||||
@@ -2416,3 +2416,49 @@ PROMPT:
|
|||||||
role: tool
|
role: tool
|
||||||
Here are the results: {"content": "done"}
|
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]
|
[discussions.main]
|
||||||
git_commit = ""
|
git_commit = ""
|
||||||
last_updated = "2026-03-11T23:45:09"
|
last_updated = "2026-03-12T18:41:55"
|
||||||
history = []
|
history = []
|
||||||
|
|||||||
@@ -852,7 +852,6 @@ class AppController:
|
|||||||
self.ui_separate_tier4 = False
|
self.ui_separate_tier4 = False
|
||||||
self.config = models.load_config()
|
self.config = models.load_config()
|
||||||
path_info = paths.get_full_path_info()
|
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_logs_dir = str(path_info['logs_dir']['path'])
|
||||||
self.ui_scripts_dir = str(path_info['scripts_dir']['path'])
|
self.ui_scripts_dir = str(path_info['scripts_dir']['path'])
|
||||||
theme.load_from_config(self.config)
|
theme.load_from_config(self.config)
|
||||||
@@ -890,6 +889,7 @@ class AppController:
|
|||||||
self.ui_shots_base_dir = self.project.get("screenshots", {}).get("base_dir", ".")
|
self.ui_shots_base_dir = self.project.get("screenshots", {}).get("base_dir", ".")
|
||||||
proj_meta = self.project.get("project", {})
|
proj_meta = self.project.get("project", {})
|
||||||
self.ui_project_git_dir = proj_meta.get("git_dir", "")
|
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_main_context = proj_meta.get("main_context", "")
|
||||||
self.ui_project_system_prompt = proj_meta.get("system_prompt", "")
|
self.ui_project_system_prompt = proj_meta.get("system_prompt", "")
|
||||||
self.ui_gemini_cli_path = self.project.get("gemini_cli", {}).get("binary_path", "gemini")
|
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", {})
|
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}
|
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", "")
|
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
|
# Trigger auto-start of MCP servers
|
||||||
self.event_queue.put('refresh_external_mcps', None)
|
self.event_queue.put('refresh_external_mcps', None)
|
||||||
|
|
||||||
@@ -2295,6 +2295,7 @@ class AppController:
|
|||||||
proj["screenshots"]["paths"] = self.screenshots
|
proj["screenshots"]["paths"] = self.screenshots
|
||||||
proj.setdefault("project", {})
|
proj.setdefault("project", {})
|
||||||
proj["project"]["git_dir"] = self.ui_project_git_dir
|
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"]["system_prompt"] = self.ui_project_system_prompt
|
||||||
proj["project"]["main_context"] = self.ui_project_main_context
|
proj["project"]["main_context"] = self.ui_project_main_context
|
||||||
proj["project"]["active_preset"] = self.ui_project_preset_name
|
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()
|
r.destroy()
|
||||||
if d: self.ui_output_dir = d
|
if d: self.ui_output_dir = d
|
||||||
imgui.separator()
|
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.text("Project Files")
|
||||||
imgui.begin_child("proj_files", imgui.ImVec2(0, 150), True)
|
imgui.begin_child("proj_files", imgui.ImVec2(0, 150), True)
|
||||||
for i, pp in enumerate(self.project_paths):
|
for i, pp in enumerate(self.project_paths):
|
||||||
@@ -1473,7 +1482,6 @@ class App:
|
|||||||
|
|
||||||
def _save_paths(self):
|
def _save_paths(self):
|
||||||
self.config["paths"] = {
|
self.config["paths"] = {
|
||||||
"conductor_dir": self.ui_conductor_dir,
|
|
||||||
"logs_dir": self.ui_logs_dir,
|
"logs_dir": self.ui_logs_dir,
|
||||||
"scripts_dir": self.ui_scripts_dir
|
"scripts_dir": self.ui_scripts_dir
|
||||||
}
|
}
|
||||||
@@ -1482,7 +1490,8 @@ class App:
|
|||||||
shutil.copy(cfg_path, str(cfg_path) + ".bak")
|
shutil.copy(cfg_path, str(cfg_path) + ".bak")
|
||||||
models.save_config(self.config)
|
models.save_config(self.config)
|
||||||
paths.reset_resolved()
|
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:
|
def _render_paths_panel(self) -> None:
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_paths_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_paths_panel")
|
||||||
@@ -1490,10 +1499,6 @@ class App:
|
|||||||
|
|
||||||
imgui.text_colored(C_IN, "System Path Configuration")
|
imgui.text_colored(C_IN, "System Path Configuration")
|
||||||
imgui.separator()
|
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):
|
def render_path_field(label: str, attr: str, key: str, tooltip: str):
|
||||||
info = path_info.get(key, {'source': 'unknown'})
|
info = path_info.get(key, {'source': 'unknown'})
|
||||||
@@ -1513,7 +1518,6 @@ class App:
|
|||||||
r.destroy()
|
r.destroy()
|
||||||
if d: setattr(self, attr, d)
|
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("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.")
|
render_path_field("Scripts Directory", "ui_scripts_dir", "scripts_dir", "Directory for AI-generated PowerShell scripts.")
|
||||||
|
|
||||||
|
|||||||
43
src/paths.py
43
src/paths.py
@@ -6,13 +6,11 @@ This module provides centralized path resolution for all configurable paths in t
|
|||||||
|
|
||||||
Environment Variables:
|
Environment Variables:
|
||||||
SLOP_CONFIG: Path to config.toml
|
SLOP_CONFIG: Path to config.toml
|
||||||
SLOP_CONDUCTOR_DIR: Path to conductor directory
|
|
||||||
SLOP_LOGS_DIR: Path to logs directory
|
SLOP_LOGS_DIR: Path to logs directory
|
||||||
SLOP_SCRIPTS_DIR: Path to generated scripts directory
|
SLOP_SCRIPTS_DIR: Path to generated scripts directory
|
||||||
|
|
||||||
Configuration (config.toml):
|
Configuration (config.toml):
|
||||||
[paths]
|
[paths]
|
||||||
conductor_dir = "conductor"
|
|
||||||
logs_dir = "logs/sessions"
|
logs_dir = "logs/sessions"
|
||||||
scripts_dir = "scripts/generated"
|
scripts_dir = "scripts/generated"
|
||||||
|
|
||||||
@@ -27,8 +25,8 @@ Path Functions:
|
|||||||
|
|
||||||
Resolution Order:
|
Resolution Order:
|
||||||
1. Check project-specific manual_slop.toml (for conductor paths)
|
1. Check project-specific manual_slop.toml (for conductor paths)
|
||||||
2. Check environment variable
|
2. Check environment variable (for logs/scripts)
|
||||||
3. Check config.toml [paths] section
|
3. Check config.toml [paths] section (for logs/scripts)
|
||||||
4. Fall back to default
|
4. Fall back to default
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@@ -110,37 +108,15 @@ def _get_project_conductor_dir_from_toml(project_root: Path) -> Optional[Path]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def get_conductor_dir(project_path: Optional[str] = None) -> Path:
|
def get_conductor_dir(project_path: Optional[str] = None) -> Path:
|
||||||
if project_path:
|
if not project_path:
|
||||||
project_root = Path(project_path).resolve()
|
# Fallback for legacy/tests, but we should avoid this
|
||||||
p = _get_project_conductor_dir_from_toml(project_root)
|
return Path('conductor').resolve()
|
||||||
if p: return p
|
|
||||||
|
|
||||||
if "conductor_dir" not in _RESOLVED:
|
project_root = Path(project_path).resolve()
|
||||||
# Check env and config
|
p = _get_project_conductor_dir_from_toml(project_root)
|
||||||
root_dir = Path(__file__).resolve().parent.parent
|
if p: return p
|
||||||
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 (project_root / "conductor").resolve()
|
||||||
return (Path(project_path).resolve() / "conductor").resolve()
|
|
||||||
|
|
||||||
root_dir = Path(__file__).resolve().parent.parent
|
|
||||||
return (root_dir / "conductor").resolve()
|
|
||||||
|
|
||||||
def get_logs_dir() -> Path:
|
def get_logs_dir() -> Path:
|
||||||
if "logs_dir" not in _RESOLVED:
|
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]]:
|
def get_full_path_info() -> dict[str, dict[str, Any]]:
|
||||||
return {
|
return {
|
||||||
'conductor_dir': _resolve_path_info('SLOP_CONDUCTOR_DIR', 'conductor_dir', 'conductor'),
|
|
||||||
'logs_dir': _resolve_path_info('SLOP_LOGS_DIR', 'logs_dir', 'logs/sessions'),
|
'logs_dir': _resolve_path_info('SLOP_LOGS_DIR', 'logs_dir', 'logs/sessions'),
|
||||||
'scripts_dir': _resolve_path_info('SLOP_SCRIPTS_DIR', 'scripts_dir', 'scripts/generated')
|
'scripts_dir': _resolve_path_info('SLOP_SCRIPTS_DIR', 'scripts_dir', 'scripts/generated')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ def close_session() -> None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not update auto-whitelist on close: {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:
|
def log_api_hook(method: str, path: str, payload: str) -> None:
|
||||||
"""Log an API hook invocation."""
|
"""Log an API hook invocation."""
|
||||||
if _api_fh is None:
|
if _api_fh is None:
|
||||||
|
|||||||
@@ -4,33 +4,38 @@ from src import paths
|
|||||||
|
|
||||||
# We mock App to avoid the heavy initialization logic
|
# We mock App to avoid the heavy initialization logic
|
||||||
class MockApp:
|
class MockApp:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ui_conductor_dir = '/mock/conductor'
|
self.ui_conductor_dir = '/mock/conductor'
|
||||||
self.ui_logs_dir = '/mock/logs'
|
self.ui_logs_dir = '/mock/logs'
|
||||||
self.ui_scripts_dir = '/mock/scripts'
|
self.ui_scripts_dir = '/mock/scripts'
|
||||||
self.config = {"paths": {}}
|
self.config = {"paths": {}}
|
||||||
self.ai_status = ""
|
self.ai_status = ""
|
||||||
|
|
||||||
from src.gui_2 import App
|
def init_state(self):
|
||||||
_save_paths = App._save_paths
|
pass
|
||||||
|
|
||||||
|
from src.gui_2 import App
|
||||||
|
_save_paths = App._save_paths
|
||||||
|
|
||||||
def test_save_paths():
|
def test_save_paths():
|
||||||
mock_app = MockApp()
|
mock_app = MockApp()
|
||||||
|
|
||||||
with patch('src.models.save_config') as mock_save, \
|
with patch('src.models.save_config') as mock_save, \
|
||||||
patch('shutil.copy') as mock_copy, \
|
patch('shutil.copy') as mock_copy, \
|
||||||
patch('src.paths.get_config_path') as mock_get_cfg, \
|
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
|
mock_get_cfg.return_value = MagicMock()
|
||||||
|
mock_get_cfg.return_value.exists.return_value = True
|
||||||
mock_app.ui_conductor_dir = '/new/conductor'
|
|
||||||
mock_app._save_paths()
|
mock_app.ui_conductor_dir = '/new/conductor'
|
||||||
|
mock_app._save_paths()
|
||||||
# Verify config update
|
|
||||||
assert mock_app.config['paths']['conductor_dir'] == '/new/conductor'
|
# Verify config update
|
||||||
mock_save.assert_called_once()
|
assert 'conductor_dir' not in mock_app.config['paths']
|
||||||
mock_copy.assert_called_once()
|
mock_save.assert_called_once()
|
||||||
assert 'restart required' in mock_app.ai_status
|
mock_copy.assert_called_once()
|
||||||
mock_reset.assert_called_once()
|
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
|
yield
|
||||||
paths.reset_resolved()
|
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
|
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_logs_dir() == root_dir / "logs/sessions"
|
||||||
assert paths.get_scripts_dir() == root_dir / "scripts/generated"
|
assert paths.get_scripts_dir() == root_dir / "scripts/generated"
|
||||||
# config path should now be an absolute path relative to src/paths.py
|
# config path should be what we set in env
|
||||||
assert paths.get_config_path() == root_dir / "config.toml"
|
assert paths.get_config_path() == tmp_path / "non_existent.toml"
|
||||||
assert paths.get_tracks_dir() == root_dir / "conductor/tracks"
|
|
||||||
assert paths.get_archive_dir() == root_dir / "conductor/archive"
|
|
||||||
|
|
||||||
def test_env_var_overrides(tmp_path, monkeypatch):
|
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
|
# Absolute env var
|
||||||
abs_logs = (tmp_path / "abs_logs").resolve()
|
abs_logs = (tmp_path / "abs_logs").resolve()
|
||||||
monkeypatch.setenv("SLOP_LOGS_DIR", str(abs_logs))
|
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"
|
config_file = tmp_path / "custom_config.toml"
|
||||||
content = """
|
content = """
|
||||||
[paths]
|
[paths]
|
||||||
conductor_dir = "cfg_conductor"
|
|
||||||
logs_dir = "cfg_logs"
|
logs_dir = "cfg_logs"
|
||||||
scripts_dir = "cfg_scripts"
|
scripts_dir = "cfg_scripts"
|
||||||
"""
|
"""
|
||||||
config_file.write_text(content)
|
config_file.write_text(content)
|
||||||
monkeypatch.setenv("SLOP_CONFIG", str(config_file))
|
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_logs_dir() == root_dir / "cfg_logs"
|
||||||
assert paths.get_scripts_dir() == root_dir / "cfg_scripts"
|
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"
|
config_file = tmp_path / "custom_config.toml"
|
||||||
content = """
|
content = """
|
||||||
[paths]
|
[paths]
|
||||||
conductor_dir = "cfg_conductor"
|
logs_dir = "cfg_logs"
|
||||||
"""
|
"""
|
||||||
config_file.write_text(content)
|
config_file.write_text(content)
|
||||||
monkeypatch.setenv("SLOP_CONFIG", str(config_file))
|
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
|
# 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"
|
||||||
|
|||||||
@@ -7,82 +7,53 @@ from src import paths
|
|||||||
from src import project_manager
|
from src import project_manager
|
||||||
|
|
||||||
def test_get_conductor_dir_default():
|
def test_get_conductor_dir_default():
|
||||||
paths.reset_resolved()
|
paths.reset_resolved()
|
||||||
# Should return absolute path to "conductor" in project root
|
# Should return absolute path to "conductor" in project root
|
||||||
expected = Path(__file__).resolve().parent.parent / "conductor"
|
expected = Path(__file__).resolve().parent.parent / "conductor"
|
||||||
assert paths.get_conductor_dir() == expected
|
assert paths.get_conductor_dir() == expected
|
||||||
|
|
||||||
def test_get_conductor_dir_project_specific_with_toml(tmp_path):
|
def test_get_conductor_dir_project_specific_with_toml(tmp_path):
|
||||||
paths.reset_resolved()
|
paths.reset_resolved()
|
||||||
project_root = tmp_path / "my_project"
|
project_root = tmp_path / "my_project"
|
||||||
project_root.mkdir()
|
project_root.mkdir()
|
||||||
|
|
||||||
# Create manual_slop.toml with custom conductor dir
|
# Create manual_slop.toml with custom conductor dir
|
||||||
toml_path = project_root / "manual_slop.toml"
|
toml_path = project_root / "manual_slop.toml"
|
||||||
config = {
|
config = {
|
||||||
"conductor": {
|
"conductor": {
|
||||||
"dir": "custom_tracks"
|
"dir": "custom_tracks"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
with open(toml_path, "wb") as f:
|
with open(toml_path, "wb") as f:
|
||||||
f.write(tomli_w.dumps(config).encode())
|
f.write(tomli_w.dumps(config).encode())
|
||||||
|
|
||||||
res = paths.get_conductor_dir(project_path=str(project_root))
|
res = paths.get_conductor_dir(project_path=str(project_root))
|
||||||
assert res == project_root / "custom_tracks"
|
assert res == project_root / "custom_tracks"
|
||||||
|
|
||||||
def test_get_all_tracks_project_specific(tmp_path):
|
def test_get_all_tracks_project_specific(tmp_path):
|
||||||
paths.reset_resolved()
|
paths.reset_resolved()
|
||||||
project_root = tmp_path / "my_project"
|
project_root = tmp_path / "my_project"
|
||||||
project_root.mkdir()
|
project_root.mkdir()
|
||||||
|
|
||||||
# Custom conductor dir
|
# Custom conductor dir
|
||||||
custom_dir = project_root / "my_conductor"
|
custom_dir = project_root / "my_conductor"
|
||||||
custom_dir.mkdir()
|
custom_dir.mkdir()
|
||||||
tracks_dir = custom_dir / "tracks"
|
tracks_dir = custom_dir / "tracks"
|
||||||
tracks_dir.mkdir()
|
tracks_dir.mkdir()
|
||||||
|
|
||||||
# Create a dummy track
|
# Create a dummy track
|
||||||
track_dir = tracks_dir / "test_track_20260312"
|
track_dir = tracks_dir / "test_track_20260312"
|
||||||
track_dir.mkdir()
|
track_dir.mkdir()
|
||||||
with open(track_dir / "metadata.json", "w") as f:
|
with open(track_dir / "metadata.json", "w") as f:
|
||||||
json.dump({"id": "test_track", "title": "Test Track"}, f)
|
json.dump({"id": "test_track", "title": "Test Track"}, f)
|
||||||
|
|
||||||
# Setup manual_slop.toml
|
# Setup manual_slop.toml
|
||||||
toml_path = project_root / "manual_slop.toml"
|
toml_path = project_root / "manual_slop.toml"
|
||||||
config = {"conductor": {"dir": "my_conductor"}}
|
config = {"conductor": {"dir": "my_conductor"}}
|
||||||
with open(toml_path, "wb") as f:
|
with open(toml_path, "wb") as f:
|
||||||
f.write(tomli_w.dumps(config).encode())
|
f.write(tomli_w.dumps(config).encode())
|
||||||
|
|
||||||
# project_manager.get_all_tracks(base_dir) should now find it
|
# project_manager.get_all_tracks(base_dir) should now find it
|
||||||
tracks = project_manager.get_all_tracks(str(project_root))
|
tracks = project_manager.get_all_tracks(str(project_root))
|
||||||
assert len(tracks) == 1
|
assert len(tracks) == 1
|
||||||
assert tracks[0]["title"] == "Test Track"
|
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