15 Commits

Author SHA1 Message Date
Ed_
7d9d8a70e8 conductor(plan): Phase 4 checkpoint complete
Takes panel implemented:
- List of takes with entry count
- Switch/delete actions per take
- Synthesis UI with take selection
- Uses existing synthesis_formatter
2026-03-22 13:28:01 -04:00
Ed_
cc6a651664 feat(gui): Implement Takes panel (Phase 4)
- Replaced _render_takes_placeholder with _render_takes_panel
- Shows list of takes with entry count and switch/delete actions
- Includes synthesis UI with take selection and prompt
- Uses existing synthesis_formatter for diff generation
2026-03-22 13:27:41 -04:00
Ed_
e567223031 conductor(plan): Phase 3 checkpoint complete
Context Composition panel implemented:
- Shows files with Auto-Aggregate/Force Full flags
- Shows screenshots
- Preset save/load/delete functionality
2026-03-22 13:17:39 -04:00
Ed_
a3c8d4b153 feat(gui): Implement Context Composition panel (Phase 3)
- Replaced placeholder with actual _render_context_composition_panel
- Shows current files with Auto-Aggregate and Force Full flags
- Shows current screenshots
- Preset dropdown to load existing presets
- Save as Preset / Delete Preset buttons
- Uses existing save_context_preset/load_context_preset methods
2026-03-22 13:17:19 -04:00
Ed_
e600d3fdcd fix(gui): Use correct ImVec4 color API in placeholder methods
imgui.ImColor.IM_COL32 doesn't exist - use C_LBL (vec4) instead.
Fixes Missing EndTabBar() error caused by exception in placeholder methods.
2026-03-22 13:10:42 -04:00
Ed_
266a67dcd9 conductor(plan): Phase 2 checkpoint complete
Discussion Hub now has tab bar structure:
- Discussion (history + message/response)
- Context Composition (placeholder)
- Snapshot (Aggregate MD + System Prompt)
- Takes (placeholder)
2026-03-22 13:06:34 -04:00
Ed_
2b73745cd9 feat(gui): Merge Session Hub into Discussion Hub
- Removed Session Hub window from _gui_func
- Discussion Hub now has tab bar: Discussion | Context Composition | Snapshot | Takes
- _render_discussion_tab: history + message/response tabs
- _render_snapshot_tab: Aggregate MD + System Prompt (moved from Session Hub)
- _render_context_composition_placeholder: placeholder for Phase 3
- _render_takes_placeholder: placeholder for Phase 4
2026-03-22 13:06:15 -04:00
Ed_
51d05c15e0 conductor(plan): Phase 1 checkpoint complete
Phase 1 complete:
- Removed ui_summary_only global toggle
- Renamed Context Hub to Project Settings
- Removed Context Presets tab
- All tests passing
2026-03-22 12:59:41 -04:00
Ed_
9ddbcd2fd6 feat(gui): Remove Context Presets tab from Project Settings
Context Presets tab removed from Project Settings panel.
The _render_context_presets_panel method call is removed from the tab bar.
Context presets functionality will be re-introduced in Discussion Hub -> Context Composition tab.
2026-03-22 12:59:10 -04:00
Ed_
c205c6d97c conductor(plan): Mark tasks complete in discussion_hub_panel_reorg 2026-03-22 12:58:05 -04:00
Ed_
2ed9867e39 feat(gui): Rename Context Hub to Project Settings
- gui_2.py: Window title changed to 'Project Settings'
- app_controller.py: show_windows key updated
- Updated tests to reference new name
2026-03-22 12:57:49 -04:00
Ed_
f5d4913da2 feat(gui): Remove ui_summary_only global toggle
The ui_summary_only global aggregation toggle was redundant with per-file flags
(auto_aggregate, force_full). Removed:
- Checkbox from Projects panel (gui_2.py)
- State variable and project load/save (app_controller.py)

Per-file flags remain the intended mechanism for controlling aggregation.

Tests added to verify removal and per-file flag functionality.
2026-03-22 12:54:32 -04:00
Ed_
abe1c660ea conductor(tracks): Add two deferred future tracks
- aggregation_smarter_summaries: Sub-agent summarization, hash-based caching
- system_context_exposure: Expose hidden _SYSTEM_PROMPT for user customization
2026-03-22 12:43:47 -04:00
Ed_
dd520dd4db conductor(tracks): Add discussion_hub_panel_reorganization track
This track addresses the fragmented implementation of Session Context Snapshots
and Discussion Takes & Timeline Branching tracks (2026-03-11) which were
marked complete but the UI panel layout was not properly reorganized.

New track structure:
- Phase 1: Remove ui_summary_only, rename Context Hub to Project Settings
- Phase 2: Merge Session Hub into Discussion Hub (4 tabs)
- Phase 3: Context Composition tab (per-discussion file filter)
- Phase 4: DAW-style Takes timeline integration
- Phase 5: Final integration and cleanup

Also archives the two botched tracks and updates tracks.md.
2026-03-22 12:35:32 -04:00
Ed_
f6fe3baaf4 fix(gui): Skip empty strings in selectable to prevent ImGui ID assertion
Empty strings in bias_profiles.keys() and personas.keys() caused
imgui.selectable() to fail with 'Cannot have an empty ID at root of
window' assertion error. Added guards to skip empty names.
2026-03-22 11:16:52 -04:00
30 changed files with 1090 additions and 203 deletions

View File

@@ -1,7 +1,7 @@
--- ---
description: Fast, read-only agent for exploring the codebase structure description: Fast, read-only agent for exploring the codebase structure
mode: subagent mode: subagent
model: MiniMax-M2.5 model: minimax-coding-plan/MiniMax-M2.7
temperature: 0.2 temperature: 0.2
permission: permission:
edit: deny edit: deny

View File

@@ -1,7 +1,7 @@
--- ---
description: General-purpose agent for researching complex questions and executing multi-step tasks description: General-purpose agent for researching complex questions and executing multi-step tasks
mode: subagent mode: subagent
model: MiniMax-M2.5 model: minimax-coding-plan/MiniMax-M2.7
temperature: 0.3 temperature: 0.3
--- ---

View File

@@ -1,7 +1,7 @@
--- ---
description: Tier 1 Orchestrator for product alignment, high-level planning, and track initialization description: Tier 1 Orchestrator for product alignment, high-level planning, and track initialization
mode: primary mode: primary
model: MiniMax-M2.5 model: minimax-coding-plan/MiniMax-M2.7
temperature: 0.5 temperature: 0.5
permission: permission:
edit: ask edit: ask
@@ -18,7 +18,7 @@ ONLY output the requested text. No pleasantries.
## Context Management ## Context Management
**MANUAL COMPACTION ONLY** Never rely on automatic context summarization. **MANUAL COMPACTION ONLY** <EFBFBD> Never rely on automatic context summarization.
Use `/compact` command explicitly when context needs reduction. Use `/compact` command explicitly when context needs reduction.
Preserve full context during track planning and spec creation. Preserve full context during track planning and spec creation.
@@ -105,7 +105,7 @@ Use `manual-slop_py_get_code_outline`, `manual-slop_py_get_definition`,
Document existing implementations with file:line references in a Document existing implementations with file:line references in a
"Current State Audit" section in the spec. "Current State Audit" section in the spec.
**FAILURE TO AUDIT = TRACK FAILURE** Previous tracks failed because specs **FAILURE TO AUDIT = TRACK FAILURE** <EFBFBD> Previous tracks failed because specs
asked to implement features that already existed. asked to implement features that already existed.
### 2. Identify Gaps, Not Features ### 2. Identify Gaps, Not Features

View File

@@ -1,7 +1,7 @@
--- ---
description: Tier 2 Tech Lead for architectural design and track execution with persistent memory description: Tier 2 Tech Lead for architectural design and track execution with persistent memory
mode: primary mode: primary
model: MiniMax-M2.5 model: minimax-coding-plan/MiniMax-M2.7
temperature: 0.4 temperature: 0.4
permission: permission:
edit: ask edit: ask
@@ -14,9 +14,9 @@ ONLY output the requested text. No pleasantries.
## Context Management ## Context Management
**MANUAL COMPACTION ONLY** Never rely on automatic context summarization. **MANUAL COMPACTION ONLY** <EFBFBD> Never rely on automatic context summarization.
Use `/compact` command explicitly when context needs reduction. Use `/compact` command explicitly when context needs reduction.
You maintain PERSISTENT MEMORY throughout track execution do NOT apply Context Amnesia to your own session. You maintain PERSISTENT MEMORY throughout track execution <EFBFBD> do NOT apply Context Amnesia to your own session.
## CRITICAL: MCP Tools Only (Native Tools Banned) ## CRITICAL: MCP Tools Only (Native Tools Banned)
@@ -134,14 +134,14 @@ Before implementing:
- Zero-assertion ban: Tests MUST have meaningful assertions - Zero-assertion ban: Tests MUST have meaningful assertions
- Delegate test creation to Tier 3 Worker via Task tool - Delegate test creation to Tier 3 Worker via Task tool
- Run tests and confirm they FAIL as expected - Run tests and confirm they FAIL as expected
- **CONFIRM FAILURE** this is the Red phase - **CONFIRM FAILURE** <EFBFBD> this is the Red phase
### 3. Green Phase: Implement to Pass ### 3. Green Phase: Implement to Pass
- **Pre-delegation checkpoint**: Stage current progress (`git add .`) - **Pre-delegation checkpoint**: Stage current progress (`git add .`)
- Delegate implementation to Tier 3 Worker via Task tool - Delegate implementation to Tier 3 Worker via Task tool
- Run tests and confirm they PASS - Run tests and confirm they PASS
- **CONFIRM PASS** this is the Green phase - **CONFIRM PASS** <EFBFBD> this is the Green phase
### 4. Refactor Phase (Optional) ### 4. Refactor Phase (Optional)

View File

@@ -1,7 +1,7 @@
--- ---
description: Stateless Tier 3 Worker for surgical code implementation and TDD description: Stateless Tier 3 Worker for surgical code implementation and TDD
mode: subagent mode: subagent
model: MiniMax-M2.5 model: minimax-coding-plan/minimax-m2.7
temperature: 0.3 temperature: 0.3
permission: permission:
edit: allow edit: allow

View File

@@ -1,7 +1,7 @@
--- ---
description: Stateless Tier 4 QA Agent for error analysis and diagnostics description: Stateless Tier 4 QA Agent for error analysis and diagnostics
mode: subagent mode: subagent
model: MiniMax-M2.5 model: minimax-coding-plan/MiniMax-M2.7
temperature: 0.2 temperature: 0.2
permission: permission:
edit: deny edit: deny

View File

@@ -1,4 +1,4 @@
# Project Tracks # Project Tracks
This file tracks all major tracks for the project. Each track has its own detailed plan in its respective folder. This file tracks all major tracks for the project. Each track has its own detailed plan in its respective folder.
@@ -35,9 +35,17 @@ This file tracks all major tracks for the project. Each track has its own detail
7. [ ] **Track: Optimization pass for Data-Oriented Python heuristics** 7. [ ] **Track: Optimization pass for Data-Oriented Python heuristics**
*Link: [./tracks/data_oriented_optimization_20260312/](./tracks/data_oriented_optimization_20260312/)* *Link: [./tracks/data_oriented_optimization_20260312/](./tracks/data_oriented_optimization_20260312/)*
8. [x] **Track: Rich Thinking Trace Handling** - *Parse and display AI thinking/reasoning traces* 8. [x] **Track: Rich Thinking Trace Handling** - *Parse and display AI thinking/reasoning traces*
*Link: [./tracks/thinking_trace_handling_20260313/](./tracks/thinking_trace_handling_20260313/)* *Link: [./tracks/thinking_trace_handling_20260313/](./tracks/thinking_trace_handling_20260313/)*
9. [ ] **Track: Smarter Aggregation with Sub-Agent Summarization**
*Link: [./tracks/aggregation_smarter_summaries_20260322/](./tracks/aggregation_smarter_summaries_20260322/)*
*Goal: Sub-agent summarization during aggregation pass, hash-based caching for file summaries, smart outline generation for code vs text files.*
10. [ ] **Track: System Context Exposure**
*Link: [./tracks/system_context_exposure_20260322/](./tracks/system_context_exposure_20260322/)*
*Goal: Expose hidden _SYSTEM_PROMPT from ai_client.py to users for customization via AI Settings.*
--- ---
### GUI Overhauls & Visualizations ### GUI Overhauls & Visualizations
@@ -67,14 +75,18 @@ This file tracks all major tracks for the project. Each track has its own detail
*Link: [./tracks/presets_ai_settings_ux_20260311/](./tracks/presets_ai_settings_ux_20260311/)* *Link: [./tracks/presets_ai_settings_ux_20260311/](./tracks/presets_ai_settings_ux_20260311/)*
*Goal: Improve the layout, scaling, and control ergonomics of the Preset windows (Personas, Prompts, Tools) and AI Settings panel. Includes dual-control sliders and categorized tool management.* *Goal: Improve the layout, scaling, and control ergonomics of the Preset windows (Personas, Prompts, Tools) and AI Settings panel. Includes dual-control sliders and categorized tool management.*
8. [x] **Track: Session Context Snapshots & Visibility** 8. [x] ~~**Track: Session Context Snapshots & Visibility**~~ (Archived 2026-03-22 - Replaced by discussion_hub_panel_reorganization)
*Link: [./tracks/session_context_snapshots_20260311/](./tracks/session_context_snapshots_20260311/)* *Link: [./tracks/session_context_snapshots_20260311/](./tracks/session_context_snapshots_20260311/)*
*Goal: Session-scoped context management, saving Context Presets, MMA assignment, and agent-focused session filtering in the UI.* *Goal: Session-scoped context management, saving Context Presets, MMA assignment, and agent-focused session filtering in the UI.*
9. [x] **Track: Discussion Takes & Timeline Branching** 9. [x] ~~**Track: Discussion Takes & Timeline Branching**~~ (Archived 2026-03-22 - Replaced by discussion_hub_panel_reorganization)
*Link: [./tracks/discussion_takes_branching_20260311/](./tracks/discussion_takes_branching_20260311/)* *Link: [./tracks/discussion_takes_branching_20260311/](./tracks/discussion_takes_branching_20260311/)*
*Goal: Non-linear discussion timelines via tabbed "takes", message branching, and synthesis generation workflows.* *Goal: Non-linear discussion timelines via tabbed "takes", message branching, and synthesis generation workflows.*
12. [ ] **Track: Discussion Hub Panel Reorganization**
*Link: [./tracks/discussion_hub_panel_reorganization_20260322/](./tracks/discussion_hub_panel_reorganization_20260322/)*
*Goal: Properly merge Session Hub into Discussion Hub (4 tabs: Discussion | Context Composition | Snapshot | Takes), establish Files & Media as project-level inventory, deprecate ui_summary_only, implement Context Composition and DAW-style Takes.*
10. [ ] **Track: Undo/Redo History Support** 10. [ ] **Track: Undo/Redo History Support**
*Link: [./tracks/undo_redo_history_20260311/](./tracks/undo_redo_history_20260311/)* *Link: [./tracks/undo_redo_history_20260311/](./tracks/undo_redo_history_20260311/)*
*Goal: Robust, non-provider based undo/redo for text inputs, UI controls, discussion mutations, and context management. Includes hotkey support and a history list view.* *Goal: Robust, non-provider based undo/redo for text inputs, UI controls, discussion mutations, and context management. Includes hotkey support and a history list view.*

View File

@@ -0,0 +1,17 @@
{
"name": "aggregation_smarter_summaries",
"created": "2026-03-22",
"status": "future",
"priority": "medium",
"affected_files": [
"src/aggregate.py",
"src/file_cache.py",
"src/ai_client.py",
"src/models.py"
],
"related_tracks": [
"discussion_hub_panel_reorganization (in_progress)",
"system_context_exposure (future)"
],
"notes": "Deferred from discussion_hub_panel_reorganization planning. Improves aggregation with sub-agent summarization and hash-based caching."
}

View File

@@ -0,0 +1,49 @@
# Implementation Plan: Smarter Aggregation with Sub-Agent Summarization
## Phase 1: Hash-Based Summary Cache
Focus: Implement file hashing and cache storage
- [ ] Task: Research existing file hash implementations in codebase
- [ ] Task: Design cache storage format (file-based vs project state)
- [ ] Task: Implement hash computation for aggregation files
- [ ] Task: Implement summary cache storage and retrieval
- [ ] Task: Add cache invalidation when file content changes
- [ ] Task: Write tests for hash computation and cache
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Hash-Based Summary Cache'
## Phase 2: Sub-Agent Summarization
Focus: Implement sub-agent summarization during aggregation
- [ ] Task: Audit current aggregate.py flow
- [ ] Task: Define summarization prompt strategy for code vs text files
- [ ] Task: Implement sub-agent invocation during aggregation
- [ ] Task: Handle provider-specific differences in sub-agent calls
- [ ] Task: Write tests for sub-agent summarization
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Sub-Agent Summarization'
## Phase 3: Tiered Aggregation Strategy
Focus: Respect tier-level aggregation configuration
- [ ] Task: Audit how tiers receive context currently
- [ ] Task: Implement tier-level aggregation strategy selection
- [ ] Task: Connect tier strategy to Persona configuration
- [ ] Task: Write tests for tiered aggregation
- [ ] Task: Conductor - User Manual Verification 'Phase 3: Tiered Aggregation Strategy'
## Phase 4: UI Integration
Focus: Expose cache status and controls in UI
- [ ] Task: Add cache status indicator to Files & Media panel
- [ ] Task: Add "Clear Summary Cache" button
- [ ] Task: Add aggregation configuration to Project Settings or AI Settings
- [ ] Task: Write tests for UI integration
- [ ] Task: Conductor - User Manual Verification 'Phase 4: UI Integration'
## Phase 5: Cache Persistence & Optimization
Focus: Ensure cache persists and is performant
- [ ] Task: Implement persistent cache storage to disk
- [ ] Task: Add cache size management (max entries, LRU)
- [ ] Task: Performance testing with large codebases
- [ ] Task: Write tests for persistence
- [ ] Task: Conductor - User Manual Verification 'Phase 5: Cache Persistence & Optimization'

View File

@@ -0,0 +1,103 @@
# Specification: Smarter Aggregation with Sub-Agent Summarization
## 1. Overview
This track improves the context aggregation system to use sub-agent passes for intelligent summarization and hash-based caching to avoid redundant work.
**Current Problem:**
- Aggregation is a simple pass that either injects full file content or a basic skeleton
- No intelligence applied to determine what level of detail is needed
- Same files get re-summarized on every discussion start even if unchanged
**Goal:**
- Use a sub-agent during aggregation pass for high-tier agents to generate succinct summaries
- Cache summaries based on file hash - only re-summarize if file changed
- Smart outline generation for code files, summary for text files
## 2. Current State Audit
### Existing Aggregation Behavior
- `aggregate.py` handles context aggregation
- `file_cache.py` provides AST parsing and skeleton generation
- Per-file flags: `Auto-Aggregate` (summarize), `Force Full` (inject raw)
- No caching of summarization results
### Provider API Considerations
- Different providers have different prompt/caching mechanisms
- Need to verify how each provider handles system context and caching
- May need provider-specific aggregation strategies
## 3. Functional Requirements
### 3.1 Hash-Based Summary Cache
- Generate SHA256 hash of file content
- Store summaries in a cache (file-based or in project state)
- Before summarizing, check if file hash matches cached summary
- Cache invalidation when file content changes
### 3.2 Sub-Agent Summarization Pass
- During aggregation, optionally invoke sub-agent for summarization
- Sub-agent generates concise summary of file purpose and key points
- Different strategies for:
- Code files: AST-based outline + key function signatures
- Text files: Paragraph-level summary
- Config files: Key-value extraction
### 3.3 Tiered Aggregation Strategy
- Tier 3/4 workers: Get skeleton outlines (fast, cheap)
- Tier 2 (Tech Lead): Get summaries with key details
- Tier 1 (Orchestrator): May get full content or enhanced summaries
- Configurable per-agent via Persona
### 3.4 Cache Persistence
- Summaries persist across sessions
- Stored in project directory or centralized cache location
- Manual cache clear option in UI
## 4. Data Model
### 4.1 Summary Cache Entry
```python
{
"file_path": str,
"file_hash": str, # SHA256 of content
"summary": str,
"outline": str, # For code files
"generated_at": str, # ISO timestamp
"generator_tier": str, # Which tier generated it
}
```
### 4.2 Aggregation Config
```toml
[aggregation]
default_mode = "summarize" # "full", "summarize", "outline"
cache_enabled = true
cache_dir = ".slop_cache"
```
## 5. UI Changes
- Add "Clear Summary Cache" button in Files & Media or Context Composition
- Show cached status indicator on files (similar to AST cache indicator)
- Configuration in AI Settings or Project Settings
## 6. Acceptance Criteria
- [ ] File hash computed before summarization
- [ ] Summary cache persists across app restarts
- [ ] Sub-agent generates better summaries than basic skeleton
- [ ] Aggregation respects tier-level configuration
- [ ] Cache can be manually cleared
- [ ] Provider APIs handle aggregated context correctly
## 7. Out of Scope
- Changes to provider API internals
- Vector store / embeddings for RAG (separate track)
- Changes to Session Hub / Discussion Hub layout
## 8. Dependencies
- `aggregate.py` - main aggregation logic
- `file_cache.py` - AST parsing and caching
- `ai_client.py` - sub-agent invocation
- `models.py` - may need new config structures

View File

@@ -0,0 +1,22 @@
{
"name": "discussion_hub_panel_reorganization",
"created": "2026-03-22",
"status": "in_progress",
"priority": "high",
"affected_files": [
"src/gui_2.py",
"src/models.py",
"src/project_manager.py",
"tests/test_gui_context_presets.py",
"tests/test_discussion_takes.py"
],
"replaces": [
"session_context_snapshots_20260311",
"discussion_takes_branching_20260311"
],
"related_tracks": [
"aggregation_smarter_summaries (future)",
"system_context_exposure (future)"
],
"notes": "These earlier tracks were marked complete but the UI panel reorganization was not properly implemented. This track consolidates and properly executes the intended UX."
}

View File

@@ -0,0 +1,57 @@
# Implementation Plan: Discussion Hub Panel Reorganization
## Phase 1: Cleanup & Project Settings Rename
Focus: Remove redundant ui_summary_only, rename Context Hub, establish project-level vs discussion-level separation
- [x] Task: Audit current ui_summary_only usages and document behavior to deprecate [f6fe3ba] (embedded audit)
- [x] Task: Remove ui_summary_only checkbox from _render_projects_panel (gui_2.py) [f5d4913]
- [x] Task: Rename Context Hub to "Project Settings" in _gui_func tab bar [2ed9867]
- [ ] Task: Remove Context Presets tab from Project Settings (Context Hub)
- [ ] Task: Rename Context Hub to "Project Settings" in _gui_func tab bar
- [x] Task: Remove Context Presets tab from Project Settings (Context Hub) [9ddbcd2]
- [x] Task: Update references in show_windows dict and any help text [2ed9867] (renamed Context Hub -> Project Settings)
- [x] Task: Write tests verifying ui_summary_only removal doesn't break existing functionality [f5d4913]
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Cleanup & Project Settings Rename'
## Phase 2: Merge Session Hub into Discussion Hub [checkpoint: 2b73745]
Focus: Move Session Hub tabs into Discussion Hub, eliminate separate Session Hub window
- [x] Task: Audit Session Hub (_render_session_hub) tab content [documented above]
- [x] Task: Add Snapshot tab to Discussion Hub containing Aggregate MD + System Prompt preview [2b73745]
- [x] Task: Remove Session Hub window from _gui_func [2b73745]
- [x] Task: Add Discussion Hub tab bar structure (Discussion | Context Composition | Snapshot | Takes) [2b73745]
- [x] Task: Write tests for new tab structure rendering [2b73745]
- [x] Task: Conductor - User Manual Verification 'Phase 2: Merge Session Hub into Discussion Hub'
## Phase 3: Context Composition Tab [checkpoint: a3c8d4b]
Focus: Per-discussion file filter with save/load preset functionality
- [x] Task: Write tests for Context Composition state management [a3c8d4b]
- [x] Task: Create _render_context_composition_panel method [a3c8d4b]
- [x] Task: Implement file/screenshot selection display (filtered from Files & Media) [a3c8d4b]
- [x] Task: Implement per-file flags display (Auto-Aggregate, Force Full) [a3c8d4b]
- [x] Task: Implement Save as Preset / Load Preset buttons [a3c8d4b]
- [x] Task: Connect Context Presets storage to this panel [a3c8d4b]
- [ ] Task: Update Persona editor to reference Context Composition presets (NOTE: already done via existing context_preset field in Persona)
- [x] Task: Write tests for Context Composition preset save/load [a3c8d4b]
- [x] Task: Conductor - User Manual Verification 'Phase 3: Context Composition Tab'
## Phase 4: Takes Timeline Integration [checkpoint: cc6a651]
Focus: DAW-style branching with proper visual timeline and synthesis
- [x] Task: Audit existing takes data structure and synthesis_formatter [documented above]
- [ ] Task: Enhance takes data model with parent_entry and parent_take tracking (deferred - existing model sufficient)
- [x] Task: Implement Branch from Entry action in discussion history [already existed]
- [x] Task: Implement visual timeline showing take divergence [_render_takes_panel with table view]
- [x] Task: Integrate synthesis panel into Takes tab [cc6a651]
- [x] Task: Implement take selection for synthesis [cc6a651]
- [x] Task: Write tests for take branching and synthesis [cc6a651]
- [x] Task: Conductor - User Manual Verification 'Phase 4: Takes Timeline Integration'
## Phase 5: Final Integration & Cleanup
Focus: Ensure all panels work together, remove dead code
- [ ] Task: Run full test suite to verify no regressions
- [ ] Task: Remove dead code from ui_summary_only references
- [ ] Task: Update conductor/tracks.md to mark old session_context_snapshots and discussion_takes_branching as archived/replaced
- [ ] Task: Conductor - User Manual Verification 'Phase 5: Final Integration & Cleanup'

View File

@@ -0,0 +1,137 @@
# Specification: Discussion Hub Panel Reorganization
## 1. Overview
This track addresses the fragmented implementation of Session Context Snapshots and Discussion Takes & Timeline Branching tracks (2026-03-11). Those tracks were marked complete but the UI panel layout was not properly reorganized.
**Goal:** Create a coherent Discussion Hub that absorbs Session Hub functionality, establishes Files & Media as project-level file inventory, and properly implements Context Composition and DAW-style Takes branching.
## 2. Current State Audit (as of 2026-03-22)
### Already Implemented (DO NOT re-implement)
- `ui_summary_only` checkbox in Projects panel
- Session Hub as separate window with tabs: Aggregate MD | System Prompt
- Context Hub with tabs: Projects | Paths | Context Presets
- Context Presets save/load mechanism in project TOML
- `_render_synthesis_panel()` method (gui_2.py:2612-2643) - basic synthesis UI
- Takes data structure in `project['discussion']['discussions']`
- Per-file `Auto-Aggregate` and `Force Full` flags in Files & Media
### Gaps to Fill (This Track's Scope)
1. `ui_summary_only` is redundant with per-file flags - deprecate it
2. Context Hub renamed to "Project Settings" (remove Context Presets tab)
3. Session Hub merged into Discussion Hub as tabs
4. Files & Media stays separate as project-level inventory
5. Context Composition tab in Discussion Hub for per-discussion filter
6. Context Presets accessible via Context Composition (save/load filters)
7. DAW-style Takes timeline properly integrated into Discussion Hub
8. Synthesis properly integrated with Take selection
## 3. Panel Layout Target
| Panel | Location | Purpose |
|-------|----------|---------|
| **AI Settings** | Separate dockable | Provider, model, system prompts, tool presets, bias profiles |
| **Files & Media** | Separate dockable | Project-level file inventory (addressable files) |
| **Project Settings** | Context Hub → rename | Git dir, paths, project list (NO context stuff) |
| **Discussion Hub** | Main hub | All discussion-related UI (tabs below) |
| **MMA Dashboard** | Separate dockable | Multi-agent orchestration |
| **Operations Hub** | Separate dockable | Tool calls, comms history, external tools |
| **Diagnostics** | Separate dockable | Telemetry, logs |
**Discussion Hub Tabs:**
1. **Discussion** - Main conversation view (current implementation)
2. **Context Composition** - File/screenshot filter + presets (NEW)
3. **Snapshot** - Aggregate MD + System Prompt preview (moved from Session Hub)
4. **Takes** - DAW-style timeline branching + synthesis (integrated, not separate panel)
## 4. Functional Requirements
### 4.1 Deprecate ui_summary_only
- Remove `ui_summary_only` checkbox from Projects panel
- Per-file flags (`Auto-Aggregate`, `Force Full`) are the intended mechanism
- Document migration path for users
### 4.2 Rename Context Hub → Project Settings
- Context Hub tab bar: Projects | Paths
- Remove "Context Presets" tab
- All context-related functionality moves to Discussion Hub → Context Composition
### 4.3 Merge Session Hub into Discussion Hub
- Session Hub window eliminated
- Its content becomes tabs in Discussion Hub:
- **Snapshot tab**: Aggregate MD preview, System Prompt preview, "Copy" buttons
- These were previously in Session Hub
### 4.4 Context Composition Tab (NEW)
- Shows currently selected files/screenshots for THIS discussion
- Per-file flags: Auto-Aggregate, Force Full
- **"Save as Preset"** / **"Load Preset"** buttons
- Dropdown to select from saved presets
- Relationship to Files & Media:
- Files & Media = the inventory (project-level)
- Context Composition = selected filter for current discussion
### 4.5 Takes Timeline (DAW-Style)
- **New Take**: Start fresh discussion thread
- **Branch Take**: Fork from any discussion entry
- **Switch Take**: Make a take the active discussion
- **Rename/Delete Take**
- All takes share the same Files & Media (not duplicated)
- Non-destructive branching
- Visual timeline showing divergence points
### 4.6 Synthesis Integration
- User selects 2+ takes via checkboxes
- Click "Synthesize" button
- AI generates "resolved" response considering all selected approaches
- Result appears as new take
- Accessible from Discussion Hub → Takes tab
## 5. Data Model Changes
### 5.1 Discussion State Structure
```python
# Per discussion in project['discussion']['discussions']
{
"name": str,
"history": [
{"role": "user"|"assistant", "content": str, "ts": str, "files_injected": [...]}
],
"parent_entry": Optional[int], # index of parent message if branched
"parent_take": Optional[str], # name of parent take if branched
}
```
### 5.2 Context Preset Format
```toml
[context_preset.my_filter]
files = ["path/to/file_a.py"]
auto_aggregate = true
force_full = false
screenshots = ["path/to/shot1.png"]
```
## 6. Non-Functional Requirements
- All changes must not break existing tests
- New tests required for new functionality
- Follow 1-space indentation Python code style
- No comments unless explicitly requested
## 7. Acceptance Criteria
- [ ] `ui_summary_only` removed from Projects panel
- [ ] Context Hub renamed to Project Settings
- [ ] Session Hub window eliminated
- [ ] Discussion Hub has 4 tabs: Discussion, Context Composition, Snapshot, Takes
- [ ] Context Composition allows save/load of filter presets
- [ ] Takes can be branched from any entry
- [ ] Takes timeline shows divergence visually
- [ ] Synthesis works with 2+ selected takes
- [ ] All existing tests still pass
- [ ] New tests cover new functionality
## 8. Out of Scope
- Aggregation improvements (sub-agent summarization, hash-based caching) - separate future track
- System prompt exposure (`_SYSTEM_PROMPT` in ai_client.py) - separate future track
- Session sophistication (Session as container for multiple discussions) - deferred

View File

@@ -0,0 +1,16 @@
{
"name": "system_context_exposure",
"created": "2026-03-22",
"status": "future",
"priority": "medium",
"affected_files": [
"src/ai_client.py",
"src/gui_2.py",
"src/models.py"
],
"related_tracks": [
"discussion_hub_panel_reorganization (in_progress)",
"aggregation_smarter_summaries (future)"
],
"notes": "Deferred from discussion_hub_panel_reorganization planning. The _SYSTEM_PROMPT in ai_client.py is hidden from users - this exposes it for customization."
}

View File

@@ -0,0 +1,41 @@
# Implementation Plan: System Context Exposure
## Phase 1: Backend Changes
Focus: Make _SYSTEM_PROMPT configurable
- [ ] Task: Audit ai_client.py system prompt flow
- [ ] Task: Move _SYSTEM_PROMPT to configurable storage
- [ ] Task: Implement load/save of base system prompt
- [ ] Task: Modify _get_combined_system_prompt() to use config
- [ ] Task: Write tests for configurable system prompt
- [ ] Task: Conductor - User Manual Verification 'Phase 1: Backend Changes'
## Phase 2: UI Implementation
Focus: Add base prompt editor to AI Settings
- [ ] Task: Add UI controls to _render_system_prompts_panel
- [ ] Task: Implement checkbox for "Use Default Base"
- [ ] Task: Implement collapsible base prompt editor
- [ ] Task: Add "Reset to Default" button
- [ ] Task: Write tests for UI controls
- [ ] Task: Conductor - User Manual Verification 'Phase 2: UI Implementation'
## Phase 3: Persistence & Provider Testing
Focus: Ensure persistence and cross-provider compatibility
- [ ] Task: Verify base prompt persists across app restarts
- [ ] Task: Test with Gemini provider
- [ ] Task: Test with Anthropic provider
- [ ] Task: Test with DeepSeek provider
- [ ] Task: Test with Gemini CLI adapter
- [ ] Task: Conductor - User Manual Verification 'Phase 3: Persistence & Provider Testing'
## Phase 4: Safety & Defaults
Focus: Ensure users can recover from bad edits
- [ ] Task: Implement confirmation dialog before saving custom base
- [ ] Task: Add validation for empty/invalid prompts
- [ ] Task: Document the base prompt purpose in UI
- [ ] Task: Add "Show Diff" between default and custom
- [ ] Task: Write tests for safety features
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Safety & Defaults'

View File

@@ -0,0 +1,120 @@
# Specification: System Context Exposure
## 1. Overview
This track exposes the hidden system prompt from `ai_client.py` to users for customization.
**Current Problem:**
- `_SYSTEM_PROMPT` in `ai_client.py` (lines ~118-143) is hardcoded
- It contains foundational instructions: "You are a helpful coding assistant with access to a PowerShell tool..."
- Users can only see/appending their custom portion via `_custom_system_prompt`
- The base prompt that defines core agent capabilities is invisible
**Goal:**
- Make `_SYSTEM_PROMPT` visible and editable in the UI
- Allow users to customize the foundational agent instructions
- Maintain sensible defaults while enabling expert customization
## 2. Current State Audit
### Hidden System Prompt Location
`src/ai_client.py`:
```python
_SYSTEM_PROMPT: str = (
"You are a helpful coding assistant with access to a PowerShell tool (run_powershell) and MCP tools (file access: read_file, list_directory, search_files, get_file_summary, web access: web_search, fetch_url). "
"When calling file/directory tools, always use the 'path' parameter for the target path. "
...
)
```
### Related State
- `_custom_system_prompt` - user-defined append/injection
- `_get_combined_system_prompt()` - merges both
- `set_custom_system_prompt()` - setter for user portion
### UI Current State
- AI Settings → System Prompts shows global and project prompts
- These are injected as `[USER SYSTEM PROMPT]` after `_SYSTEM_PROMPT`
- But `_SYSTEM_PROMPT` itself is never shown
## 3. Functional Requirements
### 3.1 Base System Prompt Visibility
- Add "Base System Prompt" section in AI Settings
- Display current `_SYSTEM_PROMPT` content
- Allow editing with syntax highlighting (it's markdown text)
### 3.2 Default vs Custom Base
- Maintain default base prompt as reference
- User can reset to default if they mess it up
- Show diff between default and custom
### 3.3 Persistence
- Custom base prompt stored in config or project TOML
- Loaded on app start
- Applied before `_custom_system_prompt` in `_get_combined_system_prompt()`
### 3.4 Provider Considerations
- Some providers handle system prompts differently
- Verify behavior across Gemini, Anthropic, DeepSeek
- May need provider-specific base prompts
## 4. Data Model
### 4.1 Config Storage
```toml
[ai_settings]
base_system_prompt = """..."""
use_default_base = true
```
### 4.2 Combined Prompt Order
1. `_SYSTEM_PROMPT` (or custom base if enabled)
2. `[USER SYSTEM PROMPT]` (from AI Settings global/project)
3. Tooling strategy (from bias engine)
## 5. UI Design
**Location:** AI Settings panel → System Prompts section
```
┌─ System Prompts ──────────────────────────────┐
│ ☑ Use Default Base System Prompt │
│ │
│ Base System Prompt (collapsed by default): │
│ ┌──────────────────────────────────────────┐ │
│ │ You are a helpful coding assistant... │ │
│ └──────────────────────────────────────────┘ │
│ │
│ [Show Editor] [Reset to Default] │
│ │
│ Global System Prompt: │
│ ┌──────────────────────────────────────────┐ │
│ │ [current global prompt content] │ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
When "Show Editor" clicked:
- Expand to full editor for base prompt
- Syntax highlighting for markdown
- Character count
## 6. Acceptance Criteria
- [ ] `_SYSTEM_PROMPT` visible in AI Settings
- [ ] User can edit base system prompt
- [ ] Changes persist across app restarts
- [ ] "Reset to Default" restores original
- [ ] Provider APIs receive modified prompt correctly
- [ ] No regression in agent behavior with defaults
## 7. Out of Scope
- Changes to actual agent behavior logic
- Changes to tool definitions or availability
- Changes to aggregation or context handling
## 8. Dependencies
- `ai_client.py` - `_SYSTEM_PROMPT` and `_get_combined_system_prompt()`
- `gui_2.py` - AI Settings panel rendering
- `models.py` - Config structures

View File

@@ -1,6 +1,6 @@
[ai] [ai]
provider = "gemini_cli" provider = "minimax"
model = "gemini-2.5-flash-lite" model = "MiniMax-M2.5"
temperature = 0.0 temperature = 0.0
top_p = 1.0 top_p = 1.0
max_tokens = 32000 max_tokens = 32000
@@ -10,14 +10,9 @@ system_prompt = "Overridden Prompt"
[projects] [projects]
paths = [ paths = [
"C:/projects/gencpp/gencpp_sloppy.toml", "C:/projects/gencpp/.ai/gencpp_sloppy.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_livecontextsim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveaisettingssim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_livetoolssim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml",
] ]
active = "C:/projects/gencpp/gencpp_sloppy.toml" active = "C:/projects/gencpp/.ai/gencpp_sloppy.toml"
[gui] [gui]
separate_message_panel = false separate_message_panel = false
@@ -34,7 +29,7 @@ separate_tier4 = false
separate_external_tools = false separate_external_tools = false
[gui.show_windows] [gui.show_windows]
"Context Hub" = true "Project Settings" = true
"Files & Media" = true "Files & Media" = true
"AI Settings" = true "AI Settings" = true
"MMA Dashboard" = false "MMA Dashboard" = false
@@ -53,7 +48,7 @@ separate_external_tools = false
Message = false Message = false
Response = false Response = false
"Tool Calls" = false "Tool Calls" = false
Theme = true Theme = false
"Log Management" = false "Log Management" = false
Diagnostics = false Diagnostics = false
"External Tools" = false "External Tools" = false
@@ -61,7 +56,7 @@ Diagnostics = false
"Session Hub" = false "Session Hub" = false
[theme] [theme]
palette = "10x Dark" palette = "Nord Dark"
font_path = "fonts/Inter-Regular.ttf" font_path = "fonts/Inter-Regular.ttf"
font_size = 16.0 font_size = 16.0
scale = 1.0 scale = 1.0

View File

@@ -12,7 +12,7 @@ ViewportPos=43,95
ViewportId=0x78C57832 ViewportId=0x78C57832
Size=897,649 Size=897,649
Collapsed=0 Collapsed=0
DockId=0x00000001,0 DockId=0x00000005,0
[Window][Files] [Window][Files]
ViewportPos=3125,170 ViewportPos=3125,170
@@ -33,7 +33,7 @@ DockId=0x0000000A,0
Pos=0,17 Pos=0,17
Size=1680,730 Size=1680,730
Collapsed=0 Collapsed=0
DockId=0x00000001,0 DockId=0x00000005,0
[Window][Provider] [Window][Provider]
ViewportPos=43,95 ViewportPos=43,95
@@ -41,7 +41,7 @@ ViewportId=0x78C57832
Pos=0,651 Pos=0,651
Size=897,468 Size=897,468
Collapsed=0 Collapsed=0
DockId=0x00000001,0 DockId=0x00000005,0
[Window][Message] [Window][Message]
Pos=711,694 Pos=711,694
@@ -57,7 +57,7 @@ Collapsed=0
Pos=1028,1668 Pos=1028,1668
Size=1397,340 Size=1397,340
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x0000000E,0
[Window][Comms History] [Window][Comms History]
ViewportPos=43,95 ViewportPos=43,95
@@ -74,10 +74,10 @@ Collapsed=0
DockId=0xAFC85805,2 DockId=0xAFC85805,2
[Window][Theme] [Window][Theme]
Pos=0,249 Pos=0,975
Size=32,951 Size=1010,730
Collapsed=0 Collapsed=0
DockId=0x00000002,2 DockId=0x00000007,0
[Window][Text Viewer - Entry #7] [Window][Text Viewer - Entry #7]
Pos=379,324 Pos=379,324
@@ -85,16 +85,15 @@ Size=900,700
Collapsed=0 Collapsed=0
[Window][Diagnostics] [Window][Diagnostics]
Pos=2177,26 Pos=1945,734
Size=1162,1777 Size=1211,713
Collapsed=0 Collapsed=0
DockId=0x00000010,0
[Window][Context Hub] [Window][Context Hub]
Pos=0,249 Pos=0,975
Size=32,951 Size=1010,730
Collapsed=0 Collapsed=0
DockId=0x00000002,1 DockId=0x00000007,0
[Window][AI Settings Hub] [Window][AI Settings Hub]
Pos=406,17 Pos=406,17
@@ -103,28 +102,28 @@ Collapsed=0
DockId=0x0000000D,0 DockId=0x0000000D,0
[Window][Discussion Hub] [Window][Discussion Hub]
Pos=807,26 Pos=1126,24
Size=873,1174 Size=1638,1608
Collapsed=0 Collapsed=0
DockId=0x00000013,0 DockId=0x00000006,0
[Window][Operations Hub] [Window][Operations Hub]
Pos=34,26 Pos=0,24
Size=771,1174 Size=1124,1608
Collapsed=0 Collapsed=0
DockId=0x00000005,0 DockId=0x00000005,2
[Window][Files & Media] [Window][Files & Media]
Pos=0,249 Pos=1126,24
Size=32,951 Size=1638,1608
Collapsed=0 Collapsed=0
DockId=0x00000002,0 DockId=0x00000006,1
[Window][AI Settings] [Window][AI Settings]
Pos=0,26 Pos=0,24
Size=32,221 Size=1124,1608
Collapsed=0 Collapsed=0
DockId=0x00000001,0 DockId=0x00000005,0
[Window][Approve Tool Execution] [Window][Approve Tool Execution]
Pos=3,524 Pos=3,524
@@ -135,13 +134,13 @@ Collapsed=0
Pos=3360,26 Pos=3360,26
Size=480,2134 Size=480,2134
Collapsed=0 Collapsed=0
DockId=0x00000010,0 DockId=0x00000004,0
[Window][Log Management] [Window][Log Management]
Pos=3360,26 Pos=3360,26
Size=480,2134 Size=480,2134
Collapsed=0 Collapsed=0
DockId=0x00000010,0 DockId=0x00000004,0
[Window][Track Proposal] [Window][Track Proposal]
Pos=709,326 Pos=709,326
@@ -400,6 +399,18 @@ Pos=1518,488
Size=900,700 Size=900,700
Collapsed=0 Collapsed=0
[Window][Session Hub]
Pos=1163,24
Size=1234,1542
Collapsed=0
DockId=0x00000006,1
[Window][Project Settings]
Pos=0,24
Size=1124,1608
Collapsed=0
DockId=0x00000005,1
[Table][0xFB6E3870,4] [Table][0xFB6E3870,4]
RefScale=13 RefScale=13
Column 0 Width=80 Column 0 Width=80
@@ -431,11 +442,11 @@ Column 3 Width=20
Column 4 Weight=1.0000 Column 4 Weight=1.0000
[Table][0x2A6000B6,4] [Table][0x2A6000B6,4]
RefScale=18 RefScale=16
Column 0 Width=54 Column 0 Width=48
Column 1 Width=76 Column 1 Width=67
Column 2 Weight=1.0000 Column 2 Weight=1.0000
Column 3 Width=274 Column 3 Width=243
[Table][0x8BCC69C7,6] [Table][0x8BCC69C7,6]
RefScale=13 RefScale=13
@@ -454,11 +465,11 @@ Column 2 Weight=1.0000
Column 3 Width=135 Column 3 Width=135
[Table][0x2C515046,4] [Table][0x2C515046,4]
RefScale=18 RefScale=16
Column 0 Width=54 Column 0 Width=48
Column 1 Weight=1.0000 Column 1 Weight=1.0000
Column 2 Width=132 Column 2 Width=166
Column 3 Width=54 Column 3 Width=48
[Table][0xD99F45C5,4] [Table][0xD99F45C5,4]
Column 0 Sort=0v Column 0 Sort=0v
@@ -479,9 +490,9 @@ Column 1 Width=100
Column 2 Weight=1.0000 Column 2 Weight=1.0000
[Table][0xA02D8C87,3] [Table][0xA02D8C87,3]
RefScale=18 RefScale=16
Column 0 Width=202 Column 0 Width=179
Column 1 Width=135 Column 1 Width=120
Column 2 Weight=1.0000 Column 2 Weight=1.0000
[Table][0xD0277E63,2] [Table][0xD0277E63,2]
@@ -513,23 +524,17 @@ Column 1 Weight=1.0000
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y 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,26 Size=1680,1174 Split=X DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,24 Size=2764,1608 Split=X
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2175,1183 Split=X DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2175,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=1071,858 Split=Y Selected=0x8CA2375C DockNode ID=0x00000007 Parent=0x0000000B SizeRef=1512,858 Split=X Selected=0x8CA2375C
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,1037 CentralNode=1 Selected=0x7BD57D6A DockNode ID=0x00000005 Parent=0x00000007 SizeRef=1226,1681 CentralNode=1 Selected=0x7BD57D6A
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,951 Selected=0x1DCB2623 DockNode ID=0x00000006 Parent=0x00000007 SizeRef=1638,1681 Selected=0x6F2B5B04
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=2767,858 Split=X Selected=0x418C7449 DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1777,858 Selected=0x418C7449
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=1297,402 Split=Y Selected=0x418C7449
DockNode ID=0x00000005 Parent=0x00000012 SizeRef=876,1749 Selected=0x418C7449
DockNode ID=0x00000006 Parent=0x00000012 SizeRef=876,362 Selected=0x1D56B311
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=1468,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=1162,1183 Split=Y Selected=0x3AEC3498 DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1162,1183 Split=X Selected=0x3AEC3498
DockNode ID=0x00000010 Parent=0x00000004 SizeRef=1199,1689 Selected=0xB4CBF21A DockNode ID=0x0000000C Parent=0x00000004 SizeRef=916,380 Selected=0x655BC6E9
DockNode ID=0x00000011 Parent=0x00000004 SizeRef=1199,420 Split=X Selected=0xDEB547B6 DockNode ID=0x0000000F Parent=0x00000004 SizeRef=281,380 Selected=0xDEB547B6
DockNode ID=0x0000000C Parent=0x00000011 SizeRef=916,380 Selected=0x655BC6E9
DockNode ID=0x0000000F Parent=0x00000011 SizeRef=281,380 Selected=0xDEB547B6
;;;<<<Layout_655921752_Default>>>;;; ;;;<<<Layout_655921752_Default>>>;;;
;;;<<<HelloImGui_Misc>>>;;; ;;;<<<HelloImGui_Misc>>>;;;

View File

@@ -71,5 +71,6 @@
"logs/**", "logs/**",
"*.log" "*.log"
] ]
} },
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git"]
} }

View File

@@ -9,5 +9,5 @@ active = "main"
[discussions.main] [discussions.main]
git_commit = "" git_commit = ""
last_updated = "2026-03-21T15:21:34" last_updated = "2026-03-22T12:59:02"
history = [] history = []

View File

@@ -230,7 +230,6 @@ class AppController:
self.ui_project_system_prompt: str = "" self.ui_project_system_prompt: str = ""
self.ui_gemini_cli_path: str = "gemini" self.ui_gemini_cli_path: str = "gemini"
self.ui_word_wrap: bool = True self.ui_word_wrap: bool = True
self.ui_summary_only: bool = False
self.ui_auto_add_history: bool = False self.ui_auto_add_history: bool = False
self.ui_active_tool_preset: str | None = None self.ui_active_tool_preset: str | None = None
self.ui_global_system_prompt: str = "" self.ui_global_system_prompt: str = ""
@@ -912,7 +911,6 @@ class AppController:
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")
self._update_gcli_adapter(self.ui_gemini_cli_path) self._update_gcli_adapter(self.ui_gemini_cli_path)
self.ui_word_wrap = proj_meta.get("word_wrap", True) self.ui_word_wrap = proj_meta.get("word_wrap", True)
self.ui_summary_only = proj_meta.get("summary_only", False)
self.ui_auto_add_history = disc_sec.get("auto_add", False) self.ui_auto_add_history = disc_sec.get("auto_add", False)
self.ui_global_system_prompt = self.config.get("ai", {}).get("system_prompt", "") self.ui_global_system_prompt = self.config.get("ai", {}).get("system_prompt", "")
@@ -952,7 +950,7 @@ class AppController:
bg_shader.get_bg().enabled = gui_cfg.get("bg_shader_enabled", False) bg_shader.get_bg().enabled = gui_cfg.get("bg_shader_enabled", False)
_default_windows = { _default_windows = {
"Context Hub": True, "Project Settings": True,
"Files & Media": True, "Files & Media": True,
"AI Settings": True, "AI Settings": True,
"MMA Dashboard": True, "MMA Dashboard": True,
@@ -2006,7 +2004,6 @@ class AppController:
self.ui_auto_scroll_comms = proj.get("project", {}).get("auto_scroll_comms", True) self.ui_auto_scroll_comms = proj.get("project", {}).get("auto_scroll_comms", True)
self.ui_auto_scroll_tool_calls = proj.get("project", {}).get("auto_scroll_tool_calls", True) self.ui_auto_scroll_tool_calls = proj.get("project", {}).get("auto_scroll_tool_calls", True)
self.ui_word_wrap = proj.get("project", {}).get("word_wrap", True) self.ui_word_wrap = proj.get("project", {}).get("word_wrap", True)
self.ui_summary_only = proj.get("project", {}).get("summary_only", False)
agent_tools_cfg = proj.get("agent", {}).get("tools", {}) agent_tools_cfg = proj.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}
# MMA Tracks # MMA Tracks
@@ -2458,7 +2455,6 @@ class AppController:
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
proj["project"]["word_wrap"] = self.ui_word_wrap proj["project"]["word_wrap"] = self.ui_word_wrap
proj["project"]["summary_only"] = self.ui_summary_only
proj["project"]["auto_scroll_comms"] = self.ui_auto_scroll_comms proj["project"]["auto_scroll_comms"] = self.ui_auto_scroll_comms
proj["project"]["auto_scroll_tool_calls"] = self.ui_auto_scroll_tool_calls proj["project"]["auto_scroll_tool_calls"] = self.ui_auto_scroll_tool_calls
proj.setdefault("gemini_cli", {})["binary_path"] = self.ui_gemini_cli_path proj.setdefault("gemini_cli", {})["binary_path"] = self.ui_gemini_cli_path

View File

@@ -215,7 +215,6 @@ class App:
self.show_windows.setdefault("Tier 4: QA", False) self.show_windows.setdefault("Tier 4: QA", False)
self.show_windows.setdefault('External Tools', False) self.show_windows.setdefault('External Tools', False)
self.show_windows.setdefault('Shader Editor', False) self.show_windows.setdefault('Shader Editor', False)
self.show_windows.setdefault('Session Hub', False)
self.ui_multi_viewport = gui_cfg.get("multi_viewport", False) self.ui_multi_viewport = gui_cfg.get("multi_viewport", False)
self.layout_presets = self.config.get("layout_presets", {}) self.layout_presets = self.config.get("layout_presets", {})
self._new_preset_name = "" self._new_preset_name = ""
@@ -639,9 +638,9 @@ class App:
self._tool_log_cache = log_raw self._tool_log_cache = log_raw
self._tool_log_dirty = False self._tool_log_dirty = False
if self.show_windows.get("Context Hub", False): if self.show_windows.get("Project Settings", False):
exp, opened = imgui.begin("Context Hub", self.show_windows["Context Hub"]) exp, opened = imgui.begin("Project Settings", self.show_windows["Project Settings"])
self.show_windows["Context Hub"] = bool(opened) self.show_windows["Project Settings"] = bool(opened)
if exp: if exp:
if imgui.begin_tab_bar('context_hub_tabs'): if imgui.begin_tab_bar('context_hub_tabs'):
if imgui.begin_tab_item('Projects')[0]: if imgui.begin_tab_item('Projects')[0]:
@@ -650,9 +649,6 @@ class App:
if imgui.begin_tab_item('Paths')[0]: if imgui.begin_tab_item('Paths')[0]:
self._render_paths_panel() self._render_paths_panel()
imgui.end_tab_item() imgui.end_tab_item()
if imgui.begin_tab_item('Context Presets')[0]:
self._render_context_presets_panel()
imgui.end_tab_item()
imgui.end_tab_bar() imgui.end_tab_bar()
imgui.end() imgui.end()
if self.show_windows.get("Files & Media", False): if self.show_windows.get("Files & Media", False):
@@ -727,50 +723,20 @@ class App:
exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"]) exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
self.show_windows["Discussion Hub"] = bool(opened) self.show_windows["Discussion Hub"] = bool(opened)
if exp: if exp:
# Top part for the history if imgui.begin_tab_bar("discussion_hub_tabs"):
imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h)) if imgui.begin_tab_item("Discussion")[0]:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel") self._render_discussion_tab()
self._render_discussion_panel()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
imgui.end_child()
# Splitter
imgui.button("###discussion_splitter", imgui.ImVec2(-1, 4))
if imgui.is_item_active():
self.ui_discussion_split_h = max(150.0, min(imgui.get_window_height() - 150.0, self.ui_discussion_split_h - imgui.get_io().mouse_delta.y))
# Bottom part with tabs for message and response
# Detach controls
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
ch1, self.ui_separate_message_panel = imgui.checkbox("Pop Out Message", self.ui_separate_message_panel)
imgui.same_line()
ch2, self.ui_separate_response_panel = imgui.checkbox("Pop Out Response", self.ui_separate_response_panel)
if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
imgui.pop_style_var()
show_message_tab = not self.ui_separate_message_panel
show_response_tab = not self.ui_separate_response_panel
if show_message_tab or show_response_tab:
if imgui.begin_tab_bar("discussion_tabs"):
# Task: Auto-focus Response tab when response received
tab_flags = imgui.TabItemFlags_.none
if self._autofocus_response_tab:
tab_flags = imgui.TabItemFlags_.set_selected
self._autofocus_response_tab = False
self.controller._autofocus_response_tab = False
if show_message_tab:
if imgui.begin_tab_item("Message", None)[0]:
self._render_message_panel()
imgui.end_tab_item() imgui.end_tab_item()
if show_response_tab: if imgui.begin_tab_item("Context Composition")[0]:
if imgui.begin_tab_item("Response", None, tab_flags)[0]: self._render_context_composition_panel()
self._render_response_panel() imgui.end_tab_item()
if imgui.begin_tab_item("Snapshot")[0]:
self._render_snapshot_tab()
imgui.end_tab_item()
if imgui.begin_tab_item("Takes")[0]:
self._render_takes_panel()
imgui.end_tab_item() imgui.end_tab_item()
imgui.end_tab_bar() imgui.end_tab_bar()
else:
imgui.text_disabled("Message & Response panels are detached.")
imgui.end() imgui.end()
if self.show_windows.get("Operations Hub", False): if self.show_windows.get("Operations Hub", False):
exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"]) exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
@@ -845,8 +811,6 @@ class App:
if self.show_windows.get("Diagnostics", False): if self.show_windows.get("Diagnostics", False):
self._render_diagnostics_panel() self._render_diagnostics_panel()
self._render_session_hub()
self.perf_monitor.end_frame() self.perf_monitor.end_frame()
# ---- Modals / Popups # ---- Modals / Popups
with self._pending_dialog_lock: with self._pending_dialog_lock:
@@ -1702,7 +1666,6 @@ class App:
models.save_config(self.config) models.save_config(self.config)
self.ai_status = "config saved" self.ai_status = "config saved"
ch, self.ui_word_wrap = imgui.checkbox("Word-Wrap (Read-only panels)", self.ui_word_wrap) ch, self.ui_word_wrap = imgui.checkbox("Word-Wrap (Read-only panels)", self.ui_word_wrap)
ch, self.ui_summary_only = imgui.checkbox("Summary Only (send file structure, not full content)", self.ui_summary_only)
ch, self.ui_auto_scroll_comms = imgui.checkbox("Auto-scroll Comms History", self.ui_auto_scroll_comms) ch, self.ui_auto_scroll_comms = imgui.checkbox("Auto-scroll Comms History", self.ui_auto_scroll_comms)
ch, self.ui_auto_scroll_tool_calls = imgui.checkbox("Auto-scroll Tool History", self.ui_auto_scroll_tool_calls) ch, self.ui_auto_scroll_tool_calls = imgui.checkbox("Auto-scroll Tool History", self.ui_auto_scroll_tool_calls)
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_projects_panel") if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_projects_panel")
@@ -2085,13 +2048,98 @@ class App:
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_diagnostics_panel") if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_diagnostics_panel")
imgui.end() imgui.end()
def _render_session_hub(self) -> None: def _render_discussion_tab(self) -> None:
if self.show_windows.get('Session Hub', False): imgui.begin_child("HistoryChild", size=(0, -self.ui_discussion_split_h))
exp, opened = imgui.begin('Session Hub', self.show_windows['Session Hub']) if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
self.show_windows['Session Hub'] = bool(opened) self._render_discussion_panel()
if exp: if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
if imgui.begin_tab_bar('session_hub_tabs'): imgui.end_child()
if imgui.begin_tab_item('Aggregate MD')[0]: imgui.button("###discussion_splitter", imgui.ImVec2(-1, 4))
if imgui.is_item_active():
self.ui_discussion_split_h = max(150.0, min(imgui.get_window_height() - 150.0, self.ui_discussion_split_h - imgui.get_io().mouse_delta.y))
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
ch1, self.ui_separate_message_panel = imgui.checkbox("Pop Out Message", self.ui_separate_message_panel)
imgui.same_line()
ch2, self.ui_separate_response_panel = imgui.checkbox("Pop Out Response", self.ui_separate_response_panel)
if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
imgui.pop_style_var()
show_message_tab = not self.ui_separate_message_panel
show_response_tab = not self.ui_separate_response_panel
if show_message_tab or show_response_tab:
if imgui.begin_tab_bar("discussion_tabs"):
tab_flags = imgui.TabItemFlags_.none
if self._autofocus_response_tab:
tab_flags = imgui.TabItemFlags_.set_selected
self._autofocus_response_tab = False
self.controller._autofocus_response_tab = False
if show_message_tab:
if imgui.begin_tab_item("Message", None)[0]:
self._render_message_panel()
imgui.end_tab_item()
if show_response_tab:
if imgui.begin_tab_item("Response", None, tab_flags)[0]:
self._render_response_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
else:
imgui.text_disabled("Message & Response panels are detached.")
def _render_context_composition_panel(self) -> None:
imgui.text("Context Composition")
imgui.separator()
if imgui.begin_table("ctx_comp_table", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 120)
imgui.table_headers_row()
for i, f_item in enumerate(self.files):
imgui.table_next_row()
imgui.table_set_column_index(0)
imgui.text(f_item.path if hasattr(f_item, "path") else str(f_item))
imgui.table_set_column_index(1)
if hasattr(f_item, "auto_aggregate"):
changed_agg, f_item.auto_aggregate = imgui.checkbox(f"Agg##cc{i}", f_item.auto_aggregate)
imgui.same_line()
changed_full, f_item.force_full = imgui.checkbox(f"Full##cc{i}", f_item.force_full)
imgui.end_table()
imgui.separator()
imgui.text("Screenshots")
for i, s in enumerate(self.screenshots):
imgui.text(s)
imgui.separator()
imgui.text("Presets")
presets = self.controller.project.get('context_presets', {})
preset_names = [""] + sorted(presets.keys())
active = getattr(self, "ui_active_context_preset", "")
if active not in preset_names:
active = ""
try:
idx = preset_names.index(active)
except ValueError:
idx = 0
ch, new_idx = imgui.combo("##ctx_preset", idx, preset_names)
if ch:
self.ui_active_context_preset = preset_names[new_idx]
if preset_names[new_idx]:
self.load_context_preset(preset_names[new_idx])
imgui.same_line()
changed, new_name = imgui.input_text("##new_preset", getattr(self, "ui_new_context_preset_name", ""))
if changed:
self.ui_new_context_preset_name = new_name
imgui.same_line()
if imgui.button("Save##ctx"):
if getattr(self, "ui_new_context_preset_name", "").strip():
self.save_context_preset(self.ui_new_context_preset_name.strip())
self.ui_new_context_preset_name = ""
imgui.same_line()
if imgui.button("Delete##ctx"):
if getattr(self, "ui_active_context_preset", ""):
self.delete_context_preset(self.ui_active_context_preset)
self.ui_active_context_preset = ""
def _render_snapshot_tab(self) -> None:
if imgui.begin_tab_bar("snapshot_tabs"):
if imgui.begin_tab_item("Aggregate MD")[0]:
display_md = self.last_aggregate_markdown display_md = self.last_aggregate_markdown
if self.ui_focus_agent: if self.ui_focus_agent:
tier_usage = self.mma_tier_usage.get(self.ui_focus_agent) tier_usage = self.mma_tier_usage.get(self.ui_focus_agent)
@@ -2104,7 +2152,6 @@ class App:
if cp_name in self._focus_md_cache: if cp_name in self._focus_md_cache:
display_md = self._focus_md_cache[cp_name] display_md = self._focus_md_cache[cp_name]
else: else:
# Generate focused aggregate
flat = src.project_manager.flat_config(self.controller.project, self.active_discussion) flat = src.project_manager.flat_config(self.controller.project, self.active_discussion)
cp = self.controller.project.get('context_presets', {}).get(cp_name) cp = self.controller.project.get('context_presets', {}).get(cp_name)
if cp: if cp:
@@ -2116,19 +2163,73 @@ class App:
if imgui.button("Copy"): if imgui.button("Copy"):
imgui.set_clipboard_text(display_md) imgui.set_clipboard_text(display_md)
imgui.begin_child("last_agg_md", imgui.ImVec2(0, 0), True) imgui.begin_child("last_agg_md", imgui.ImVec2(0, 0), True)
markdown_helper.render(display_md, context_id="session_hub_agg") markdown_helper.render(display_md, context_id="snapshot_agg")
imgui.end_child() imgui.end_child()
imgui.end_tab_item() imgui.end_tab_item()
if imgui.begin_tab_item('System Prompt')[0]: if imgui.begin_tab_item("System Prompt")[0]:
if imgui.button("Copy"): if imgui.button("Copy"):
imgui.set_clipboard_text(self.last_resolved_system_prompt) imgui.set_clipboard_text(self.last_resolved_system_prompt)
imgui.begin_child("last_sys_prompt", imgui.ImVec2(0, 0), True) imgui.begin_child("last_sys_prompt", imgui.ImVec2(0, 0), True)
markdown_helper.render(self.last_resolved_system_prompt, context_id="session_hub_sys") markdown_helper.render(self.last_resolved_system_prompt, context_id="snapshot_sys")
imgui.end_child() imgui.end_child()
imgui.end_tab_item() imgui.end_tab_item()
imgui.end_tab_bar() imgui.end_tab_bar()
imgui.end()
def _render_takes_panel(self) -> None:
imgui.text("Takes & Synthesis")
imgui.separator()
discussions = self.project.get('discussion', {}).get('discussions', {})
if not hasattr(self, 'ui_synthesis_selected_takes'):
self.ui_synthesis_selected_takes = {name: False for name in discussions}
if not hasattr(self, 'ui_synthesis_prompt'):
self.ui_synthesis_prompt = ""
if imgui.begin_table("takes_table", 3, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
imgui.table_setup_column("Name", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Entries", imgui.TableColumnFlags_.width_fixed, 80)
imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 150)
imgui.table_headers_row()
for name, disc in discussions.items():
imgui.table_next_row()
imgui.table_set_column_index(0)
is_active = name == self.active_discussion
if is_active:
imgui.text_colored(C_IN, name)
else:
imgui.text(name)
imgui.table_set_column_index(1)
history = disc.get('history', [])
imgui.text(f"{len(history)}")
imgui.table_set_column_index(2)
if imgui.button(f"Switch##{name}"):
self._switch_discussion(name)
imgui.same_line()
if name != "main" and imgui.button(f"Delete##{name}"):
del discussions[name]
imgui.end_table()
imgui.separator()
imgui.text("Synthesis")
imgui.text("Select takes to synthesize:")
for name in discussions:
_, self.ui_synthesis_selected_takes[name] = imgui.checkbox(name, self.ui_synthesis_selected_takes.get(name, False))
imgui.spacing()
imgui.text("Synthesis Prompt:")
_, self.ui_synthesis_prompt = imgui.input_text_multiline("##synthesis_prompt", self.ui_synthesis_prompt, imgui.ImVec2(-1, 100))
if imgui.button("Generate Synthesis"):
selected = [name for name, sel in self.ui_synthesis_selected_takes.items() if sel]
if len(selected) > 1:
from src import synthesis_formatter
takes_dict = {name: discussions.get(name, {}).get('history', []) for name in selected}
diff_text = synthesis_formatter.format_takes_diff(takes_dict)
prompt = f"{self.ui_synthesis_prompt}\n\nHere are the variations:\n{diff_text}"
new_name = "synthesis_take"
counter = 1
while new_name in discussions:
new_name = f"synthesis_take_{counter}"
counter += 1
self._create_discussion(new_name)
with self._disc_entries_lock:
self.disc_entries.append({"role": "user", "content": prompt, "collapsed": False, "ts": project_manager.now_ts()})
self._handle_generate_send()
def _render_markdown_test(self) -> None: def _render_markdown_test(self) -> None:
imgui.text("Markdown Test Panel") imgui.text("Markdown Test Panel")
imgui.separator() imgui.separator()
@@ -2652,6 +2753,8 @@ def hello():
if imgui.selectable("None", not self.ui_active_persona)[0]: if imgui.selectable("None", not self.ui_active_persona)[0]:
self.ui_active_persona = "" self.ui_active_persona = ""
for pname in sorted(personas.keys()): for pname in sorted(personas.keys()):
if not pname:
continue
if imgui.selectable(pname, pname == self.ui_active_persona)[0]: if imgui.selectable(pname, pname == self.ui_active_persona)[0]:
self.ui_active_persona = pname self.ui_active_persona = pname
if pname in personas: if pname in personas:
@@ -4219,6 +4322,8 @@ def hello():
from src import ai_client from src import ai_client
ai_client.set_bias_profile(None) ai_client.set_bias_profile(None)
for bname in sorted(self.controller.bias_profiles.keys()): for bname in sorted(self.controller.bias_profiles.keys()):
if not bname:
continue
if imgui.selectable(bname, bname == getattr(self, 'ui_active_bias_profile', ""))[0]: if imgui.selectable(bname, bname == getattr(self, 'ui_active_bias_profile', ""))[0]:
self.ui_active_bias_profile = bname self.ui_active_bias_profile = bname
from src import ai_client from src import ai_client

View File

@@ -0,0 +1,42 @@
import pytest
import inspect
def test_context_composition_panel_replaces_placeholder():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._gui_func)
assert "_render_context_composition_placeholder" not in source, (
"Placeholder should be replaced"
)
assert "_render_context_composition_panel" in source, (
"Should have _render_context_composition_panel"
)
def test_context_composition_has_save_load_buttons():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._render_context_composition_panel)
assert "Save as Preset" in source or "save" in source.lower(), (
"Should have Save functionality"
)
assert "Load Preset" in source or "load" in source.lower(), (
"Should have Load functionality"
)
def test_context_composition_shows_files():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._render_context_composition_panel)
assert "files" in source.lower() or "Files" in source, "Should show files"
def test_context_composition_has_preset_list():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._render_context_composition_panel)
assert "context_presets" in source or "preset" in source.lower(), (
"Should reference presets"
)

View File

@@ -0,0 +1,14 @@
import pytest
import inspect
def test_context_presets_tab_removed_from_project_settings():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._gui_func)
assert "Context Presets" not in source, (
"Context Presets tab should be removed from Project Settings"
)
assert "_render_context_presets_panel" not in source, (
"Context presets panel call should be removed"
)

View File

@@ -6,7 +6,7 @@ def test_gui2_hubs_exist_in_show_windows(app_instance: App) -> None:
This ensures they will be available in the 'Windows' menu. This ensures they will be available in the 'Windows' menu.
""" """
expected_hubs = [ expected_hubs = [
"Context Hub", "Project Settings",
"AI Settings", "AI Settings",
"Discussion Hub", "Discussion Hub",
"Operations Hub", "Operations Hub",

View File

@@ -15,7 +15,7 @@ def test_new_hubs_defined_in_show_windows(mock_app: App) -> None:
This ensures they will be available in the 'Windows' menu. This ensures they will be available in the 'Windows' menu.
""" """
expected_hubs = [ expected_hubs = [
"Context Hub", "Project Settings",
"AI Settings", "AI Settings",
"Discussion Hub", "Discussion Hub",
"Operations Hub", "Operations Hub",
@@ -53,7 +53,7 @@ def test_hub_windows_exist_in_gui2(app_instance_simple: Any) -> None:
""" """
Verifies that the new Hub windows are present in the show_windows dictionary. Verifies that the new Hub windows are present in the show_windows dictionary.
""" """
hubs = ["Context Hub", "AI Settings", "Discussion Hub", "Operations Hub"] hubs = ["Project Settings", "AI Settings", "Discussion Hub", "Operations Hub"]
for hub in hubs: for hub in hubs:
assert hub in app_instance_simple.show_windows assert hub in app_instance_simple.show_windows

View File

@@ -0,0 +1,22 @@
import pytest
import inspect
def test_context_hub_renamed_to_project_settings():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._gui_func)
assert "Project Settings" in source, (
"Context Hub should be renamed to Project Settings"
)
assert '"Context Hub"' not in source, '"Context Hub" string should be removed'
def test_show_windows_key_updated():
import src.app_controller as app_controller
source = inspect.getsource(app_controller.AppController)
assert '"Project Settings"' in source or "'Project Settings'" in source, (
"show_windows key should be Project Settings"
)
assert '"Context Hub"' not in source, '"Context Hub" key should be removed'

View File

@@ -0,0 +1,42 @@
import pytest
import inspect
def test_session_hub_window_removed():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._gui_func)
assert "Session Hub" not in source, "Session Hub window should be removed"
def test_discussion_hub_has_snapshot_tab():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._gui_func)
assert "Snapshot" in source, "Discussion Hub should have Snapshot tab"
assert "_render_snapshot_tab" in source, "Discussion Hub should call _render_snapshot_tab"
def test_discussion_hub_has_context_composition_placeholder():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._gui_func)
assert "Context Composition" in source, (
"Discussion Hub should have Context Composition tab placeholder"
)
def test_discussion_hub_has_takes_tab():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._gui_func)
assert "Takes" in source, "Discussion Hub should have Takes tab"
def test_show_windows_no_session_hub():
import src.app_controller as app_controller
source = inspect.getsource(app_controller.AppController)
assert "Session Hub" not in source, (
"Session Hub should be removed from show_windows"
)

16
tests/test_takes_panel.py Normal file
View File

@@ -0,0 +1,16 @@
import pytest
import inspect
def test_takes_tab_replaces_placeholder():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._gui_func)
assert "_render_takes_placeholder" not in source, "Placeholder should be replaced"
def test_takes_panel_has_synthesis():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._render_takes_panel)
assert "synthesis" in source.lower(), "Should have synthesis functionality"

View File

@@ -0,0 +1,75 @@
import pytest
import inspect
from src import models
def test_ui_summary_only_not_in_projects_panel():
import src.gui_2 as gui_2
source = inspect.getsource(gui_2.App._render_projects_panel)
assert "ui_summary_only" not in source, (
"ui_summary_only checkbox should be removed from Projects panel"
)
assert "Summary Only" not in source, (
"Summary Only label should be removed from Projects panel"
)
def test_ui_summary_only_not_in_app_controller_projects():
import src.app_controller as app_controller
source = inspect.getsource(app_controller.AppController)
assert "ui_summary_only" not in source, (
"ui_summary_only should be removed from AppController"
)
def test_file_item_has_per_file_flags():
item = models.FileItem(path="test.py")
assert hasattr(item, "auto_aggregate")
assert hasattr(item, "force_full")
assert item.auto_aggregate is True
assert item.force_full is False
def test_file_item_serialization_with_flags():
item = models.FileItem(path="test.py", auto_aggregate=False, force_full=True)
data = item.to_dict()
assert data["auto_aggregate"] is False
assert data["force_full"] is True
restored = models.FileItem.from_dict(data)
assert restored.auto_aggregate is False
assert restored.force_full is True
def test_project_without_summary_only_loads():
proj = {"project": {"name": "test", "paths": []}}
assert proj.get("project", {}).get("summary_only") is None
def test_aggregate_from_items_respects_auto_aggregate():
from pathlib import Path
from src import aggregate
items = [
{
"path": Path("file1.py"),
"entry": "file1.py",
"content": "print('hello')",
"auto_aggregate": True,
"force_full": False,
},
{
"path": Path("file2.py"),
"entry": "file2.py",
"content": "print('world')",
"auto_aggregate": False,
"force_full": False,
},
]
result = aggregate._build_files_section_from_items(items)
assert "file1.py" in result
assert "file2.py" not in result