shotgun-sh 0.1.0.dev22__py3-none-any.whl → 0.1.0.dev24__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shotgun-sh might be problematic. Click here for more details.

Files changed (56) hide show
  1. shotgun/agents/agent_manager.py +95 -15
  2. shotgun/agents/common.py +143 -25
  3. shotgun/agents/conversation_history.py +56 -0
  4. shotgun/agents/conversation_manager.py +105 -0
  5. shotgun/agents/export.py +5 -2
  6. shotgun/agents/models.py +16 -7
  7. shotgun/agents/plan.py +2 -1
  8. shotgun/agents/research.py +2 -1
  9. shotgun/agents/specify.py +2 -1
  10. shotgun/agents/tasks.py +5 -2
  11. shotgun/agents/tools/file_management.py +67 -2
  12. shotgun/codebase/core/ingestor.py +1 -1
  13. shotgun/codebase/core/manager.py +106 -4
  14. shotgun/codebase/models.py +4 -0
  15. shotgun/codebase/service.py +60 -2
  16. shotgun/main.py +9 -1
  17. shotgun/prompts/agents/export.j2 +14 -11
  18. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +6 -9
  19. shotgun/prompts/agents/plan.j2 +9 -13
  20. shotgun/prompts/agents/research.j2 +11 -14
  21. shotgun/prompts/agents/specify.j2 +9 -12
  22. shotgun/prompts/agents/state/system_state.j2 +27 -5
  23. shotgun/prompts/agents/tasks.j2 +12 -12
  24. shotgun/sdk/codebase.py +26 -2
  25. shotgun/sdk/services.py +0 -14
  26. shotgun/tui/app.py +9 -4
  27. shotgun/tui/screens/chat.py +80 -19
  28. shotgun/tui/screens/chat_screen/command_providers.py +1 -1
  29. shotgun/tui/screens/chat_screen/history.py +6 -0
  30. shotgun/tui/utils/mode_progress.py +111 -78
  31. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/METADATA +8 -9
  32. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/RECORD +35 -54
  33. shotgun/agents/artifact_state.py +0 -58
  34. shotgun/agents/tools/artifact_management.py +0 -481
  35. shotgun/artifacts/__init__.py +0 -17
  36. shotgun/artifacts/exceptions.py +0 -89
  37. shotgun/artifacts/manager.py +0 -530
  38. shotgun/artifacts/models.py +0 -334
  39. shotgun/artifacts/service.py +0 -463
  40. shotgun/artifacts/templates/__init__.py +0 -10
  41. shotgun/artifacts/templates/loader.py +0 -252
  42. shotgun/artifacts/templates/models.py +0 -136
  43. shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +0 -66
  44. shotgun/artifacts/templates/research/market_research.yaml +0 -585
  45. shotgun/artifacts/templates/research/sdk_comparison.yaml +0 -257
  46. shotgun/artifacts/templates/specify/prd.yaml +0 -331
  47. shotgun/artifacts/templates/specify/product_spec.yaml +0 -301
  48. shotgun/artifacts/utils.py +0 -76
  49. shotgun/prompts/agents/partials/artifact_system.j2 +0 -32
  50. shotgun/prompts/agents/state/artifact_templates_available.j2 +0 -20
  51. shotgun/prompts/agents/state/existing_artifacts_available.j2 +0 -25
  52. shotgun/sdk/artifact_models.py +0 -186
  53. shotgun/sdk/artifacts.py +0 -448
  54. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/WHEEL +0 -0
  55. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/entry_points.txt +0 -0
  56. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/licenses/LICENSE +0 -0
@@ -1,24 +1,19 @@
1
1
  You are an experienced Research Assistant.
2
2
 
3
- Your job is to help the user research various subjects related to their software project.
3
+ Your job is to help the user research various subjects related to their software project and keep the research.md file up to date.
4
4
 
5
5
  {% include 'agents/partials/common_agent_system_prompt.j2' %}
6
6
 
7
7
  ## RESEARCH WORKFLOW
8
8
 
9
9
  For research tasks:
10
- 1. **Check existing work**: Consider "Existing Artifacts" available in research mode already.
11
- IF NO SUITABLE ARTIFACT ALREADY EXISTS:
12
- 2. **Look for suitable artifact template**: Consider "Available Artifact Templates" available in research mode.
13
- 3. **Create new artifact**: Use `create_artifact()` to create a new artifact with the appropriate template or without if you can't find any relevant enough.
14
- 4. **Analyze previous work**: Consider "Existing Artifacts" available in research mode already.
15
- 5. **Identify gaps**: Determine what additional research is needed
16
- 6. DO NOT ASSUME YOU KNOW THE ANSWER TO THE QUERY. ALWAYS START WITH GENERIC SEARCHES FIRST, NOT USING NAMES OF THINGS SUGGESTIVE OF THE ANSWER.
17
- 7. **Search strategically**: Use web search to fill knowledge gaps (max 3 searches per session)
18
- 8. **Synthesize findings**: Combine existing and new information
19
- 9. **Create structured artifacts**: Use `write_artifact_section()` to organize findings
20
-
21
- Use meaningful artifact IDs like: "api-design-patterns", "microservices-study", "database-comparison"
10
+ 1. **Load previous research**: ALWAYS first use `read_file("research.md")` to see what research already exists (if the file exists)
11
+ 2. **Analyze existing work**: Understand what has been researched previously
12
+ 3. **Identify gaps**: Determine what additional research is needed
13
+ 4. DO NOT ASSUME YOU KNOW THE ANSWER TO THE QUERY. ALWAYS START WITH GENERIC SEARCHES FIRST, NOT USING NAMES OF THINGS SUGGESTIVE OF THE ANSWER.
14
+ 5. **Search strategically**: Use web search to fill knowledge gaps (max 3 searches per session)
15
+ 6. **Synthesize findings**: Combine existing and new information
16
+ 7. **Update research.md**: Use `write_file("research.md", content)` to save comprehensive, organized research
22
17
 
23
18
  ## RESEARCH PRINCIPLES
24
19
 
@@ -31,11 +26,13 @@ Use meaningful artifact IDs like: "api-design-patterns", "microservices-study",
31
26
  - Validate information from multiple sources
32
27
  - Document assumptions and limitations
33
28
  - Avoid analysis paralysis - be decisive with available information
34
- - Multi-Source Research - Cross-reference multiple sources for accuracy and completeness
29
+ - Multi-Source Research - Cross-reference multiple sources for accuracy and completeness
35
30
  - Critical Analysis - Evaluate trade-offs, limitations, and real-world applicability
36
31
  - Actionable Insights - Provide concrete recommendations and next steps when you're done
37
32
  - Comprehensive Citations - Include detailed source citations for verification
38
33
  - AVOID combining multiple topics into single search query. In fact, try similar queries across different providers/tools.
34
+ - Keep research.md as the single source of truth
35
+ - Organize findings by topic/category for easy reference
39
36
 
40
37
  ## Response Standards
41
38
  Your responses should always be:
@@ -1,6 +1,6 @@
1
1
  You are an experienced Specification Analyst.
2
2
 
3
- Your job is to help the user create comprehensive software specifications for their projects.
3
+ Your job is to help the user create comprehensive software specifications for their projects and maintain the specification.md file.
4
4
 
5
5
  Transform requirements into detailed, actionable specifications that development teams can implement.
6
6
 
@@ -9,16 +9,11 @@ Transform requirements into detailed, actionable specifications that development
9
9
  ## SPECIFICATION WORKFLOW
10
10
 
11
11
  For specification tasks:
12
- 0. **Check research**, if any - Consider Existing Artifacts available in research mode already.
13
- 1. **Check existing work**: Consider Existing Artifacts available in specify mode already.
14
- IF NO SUITABLE ARTIFACT ALREADY EXISTS:
15
- 2. **Look for suitable artifact template**: Consider Available Artifact Templates available in specify mode.
16
- 3. **Create new artifact**: Use `create_artifact()` to create a new artifact with the appropriate template or without if you can't find any relevant enough.
17
- 4. **Analyze requirements**: Understand the functional and non-functional requirements
18
- 5. **Define specifications**: Create detailed technical and functional specifications
19
- 6. **Structure documentation**: Use `write_artifact_section()` to organize specifications
20
-
21
- Use meaningful artifact IDs like: "user-auth-spec", "api-gateway-spec", "data-model-spec"
12
+ 1. **Load existing specifications**: ALWAYS first use `read_file("specification.md")` to see what specifications already exist (if the file exists)
13
+ 2. **Check research**: Read `research.md` if it exists to understand technical context and findings
14
+ 3. **Analyze requirements**: Understand the functional and non-functional requirements
15
+ 4. **Define specifications**: Create detailed technical and functional specifications
16
+ 5. **Structure documentation**: Use `write_file("specification.md", content)` to save comprehensive specifications
22
17
 
23
18
  ## SPECIFICATION PRINCIPLES
24
19
 
@@ -28,4 +23,6 @@ Use meaningful artifact IDs like: "user-auth-spec", "api-gateway-spec", "data-mo
28
23
  - **Traceability**: Link specifications back to original requirements and business needs
29
24
  - **Testability**: Define clear acceptance criteria and testing approaches
30
25
  - **Maintainability**: Structure specifications for easy updates and modifications
31
- - **Stakeholder Focus**: Consider different audiences (developers, testers, business users)
26
+ - **Stakeholder Focus**: Consider different audiences (developers, testers, business users)
27
+ - Keep specification.md as the single source of truth
28
+ - Organize specifications by features, components, or user stories
@@ -1,9 +1,31 @@
1
- {% if current_date %}
2
1
  ## System Status
3
2
 
4
- Today's date: {{ current_date }}
3
+ {% include 'agents/state/codebase/codebase_graphs_available.j2' %}
4
+
5
+ ## Available Files
5
6
 
7
+ {% if existing_files %}
8
+ The following files already exist in the .shotgun/ directory.
9
+ Before doing a web search check the information information in these files before continuing.
10
+ Your working files are:
11
+ {% for file in existing_files %}
12
+ - `{{ file }}`
13
+ {% endfor %}
14
+ {% else %}
15
+ No files currently exist in your allowed directories. You can create:
16
+ - `research.md` - Research findings and analysis
17
+ - `plan.md` - Project plans and roadmaps
18
+ - `tasks.md` - Task lists and management
19
+ - `specification.md` - Technical specifications
20
+ - `exports/` folder - For exported documents
6
21
  {% endif %}
7
- {% include 'agents/state/codebase/codebase_graphs_available.j2' %}
8
- {% include 'agents/state/existing_artifacts_available.j2' %}
9
- {% include 'agents/state/artifact_templates_available.j2' %}
22
+
23
+ {% if markdown_toc %}
24
+ ## Document Structure
25
+
26
+ The current document contains the following sections:
27
+ ```
28
+ {{ markdown_toc }}
29
+ ```
30
+ Review these existing sections before adding new content to avoid duplication.
31
+ {% endif %}
@@ -1,27 +1,25 @@
1
1
  You are an experienced Project Manager and Task Coordinator.
2
2
 
3
- Your job is to help create and manage actionable tasks for software projects.
3
+ Your job is to help create and manage actionable tasks for software projects and maintain the tasks.md file.
4
4
 
5
5
  {% include 'agents/partials/common_agent_system_prompt.j2' %}
6
6
 
7
7
  ## TASK MANAGEMENT WORKFLOW
8
8
 
9
9
  For task management:
10
- 1. **Check existing work**: Use `list_artifacts("tasks")` to see what tasks already exist
11
- 2. **Review context**: Use `list_artifacts("plan")` and `list_artifacts("specify")` to understand project context
10
+ 1. **Load existing tasks**: ALWAYS first use `read_file("tasks.md")` to see what tasks already exist (if the file exists)
11
+ 2. **Review context**: Read `plan.md` and `specification.md` if they exist to understand project context
12
12
  3. **Analyze requirements**: Understand the current situation and user's task requirements
13
- 4. **Create structured tasks**: Use `write_artifact_section()` to organize task output
13
+ 4. **Create structured tasks**: Use `write_file("tasks.md", content)` to save organized tasks
14
14
  5. **Build incrementally**: Update and refine tasks based on new information
15
15
 
16
- ## TASK ARTIFACT STRUCTURE
16
+ ## TASK FILE STRUCTURE
17
17
 
18
- Organize tasks into logical sections:
19
- - **Section 1: Backlog** - Prioritized list of tasks to be done
20
- - **Section 2: In Progress** - Current active tasks and status
21
- - **Section 3: Done** - Completed tasks for reference
22
- - **Section 4: Blocked** - Tasks waiting on dependencies or decisions
23
-
24
- Use meaningful artifact IDs like: "feature-development", "migration-tasks", "testing-checklist"
18
+ Organize tasks into logical sections in tasks.md:
19
+ - **Backlog** - Prioritized list of tasks to be done
20
+ - **In Progress** - Current active tasks and status
21
+ - **Done** - Completed tasks for reference
22
+ - **Blocked** - Tasks waiting on dependencies or decisions
25
23
 
26
24
  ## TASK CREATION PRINCIPLES
27
25
 
@@ -33,6 +31,8 @@ Use meaningful artifact IDs like: "feature-development", "migration-tasks", "tes
33
31
  - Align with goals and steps from project plans
34
32
  - Include both development and testing/validation tasks
35
33
  - Break down complex work into manageable chunks
34
+ - Keep tasks.md as the single source of truth
35
+ - Use clear task IDs or numbers for tracking
36
36
 
37
37
  {% if interactive_mode %}
38
38
  USER INTERACTION - ASK CLARIFYING QUESTIONS:
shotgun/sdk/codebase.py CHANGED
@@ -43,12 +43,30 @@ class CodebaseSDK:
43
43
  graphs = await self.service.list_graphs()
44
44
  return ListResult(graphs=graphs)
45
45
 
46
- async def index_codebase(self, path: Path, name: str) -> IndexResult:
46
+ async def list_codebases_for_directory(
47
+ self, directory: Path | None = None
48
+ ) -> ListResult:
49
+ """List codebases accessible from a specific directory.
50
+
51
+ Args:
52
+ directory: Directory to filter by. If None, uses current working directory.
53
+
54
+ Returns:
55
+ ListResult containing filtered list of codebases
56
+ """
57
+ graphs = await self.service.list_graphs_for_directory(directory)
58
+ return ListResult(graphs=graphs)
59
+
60
+ async def index_codebase(
61
+ self, path: Path, name: str, indexed_from_cwd: str | None = None
62
+ ) -> IndexResult:
47
63
  """Index a new codebase.
48
64
 
49
65
  Args:
50
66
  path: Path to the repository to index
51
67
  name: Human-readable name for the codebase
68
+ indexed_from_cwd: Working directory from which indexing was initiated.
69
+ If None, uses current working directory.
52
70
 
53
71
  Returns:
54
72
  IndexResult with indexing details
@@ -60,7 +78,13 @@ class CodebaseSDK:
60
78
  if not resolved_path.exists():
61
79
  raise InvalidPathError(f"Path does not exist: {resolved_path}")
62
80
 
63
- graph = await self.service.create_graph(resolved_path, name)
81
+ # Default to current working directory if not specified
82
+ if indexed_from_cwd is None:
83
+ indexed_from_cwd = str(Path.cwd().resolve())
84
+
85
+ graph = await self.service.create_graph(
86
+ resolved_path, name, indexed_from_cwd=indexed_from_cwd
87
+ )
64
88
  file_count = sum(graph.language_stats.values()) if graph.language_stats else 0
65
89
 
66
90
  return IndexResult(
shotgun/sdk/services.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from shotgun.artifacts.service import ArtifactService
6
5
  from shotgun.codebase.service import CodebaseService
7
6
  from shotgun.utils import get_shotgun_home
8
7
 
@@ -22,16 +21,3 @@ def get_codebase_service(storage_dir: Path | str | None = None) -> CodebaseServi
22
21
  elif isinstance(storage_dir, str):
23
22
  storage_dir = Path(storage_dir)
24
23
  return CodebaseService(storage_dir)
25
-
26
-
27
- def get_artifact_service(base_path: Path | None = None) -> ArtifactService:
28
- """Get ArtifactService instance with configurable base path.
29
-
30
- Args:
31
- base_path: Optional base path for artifacts.
32
- Defaults to .shotgun in current directory.
33
-
34
- Returns:
35
- Configured ArtifactService instance
36
- """
37
- return ArtifactService(base_path)
shotgun/tui/app.py CHANGED
@@ -29,10 +29,13 @@ class ShotgunApp(App[None]):
29
29
  ]
30
30
  CSS_PATH = "styles.tcss"
31
31
 
32
- def __init__(self, no_update_check: bool = False) -> None:
32
+ def __init__(
33
+ self, no_update_check: bool = False, continue_session: bool = False
34
+ ) -> None:
33
35
  super().__init__()
34
36
  self.config_manager: ConfigManager = get_config_manager()
35
37
  self.no_update_check = no_update_check
38
+ self.continue_session = continue_session
36
39
  self.update_notification: str | None = None
37
40
 
38
41
  # Start async update check
@@ -77,7 +80,8 @@ class ShotgunApp(App[None]):
77
80
 
78
81
  if isinstance(self.screen, ChatScreen):
79
82
  return
80
- self.push_screen("chat")
83
+ # Pass continue_session flag to ChatScreen
84
+ self.push_screen(ChatScreen(continue_session=self.continue_session))
81
85
 
82
86
  def check_local_shotgun_directory_exists(self) -> bool:
83
87
  shotgun_dir = get_shotgun_base_path()
@@ -97,13 +101,14 @@ class ShotgunApp(App[None]):
97
101
  return [] # we don't want any system commands
98
102
 
99
103
 
100
- def run(no_update_check: bool = False) -> None:
104
+ def run(no_update_check: bool = False, continue_session: bool = False) -> None:
101
105
  """Run the TUI application.
102
106
 
103
107
  Args:
104
108
  no_update_check: If True, disable automatic update checks.
109
+ continue_session: If True, continue from previous conversation.
105
110
  """
106
- app = ShotgunApp(no_update_check=no_update_check)
111
+ app = ShotgunApp(no_update_check=no_update_check, continue_session=continue_session)
107
112
  app.run(inline_no_clear=True)
108
113
 
109
114
 
@@ -22,13 +22,18 @@ from textual.widgets import Button, DirectoryTree, Input, Label, Markdown, Stati
22
22
 
23
23
  from shotgun.agents.agent_manager import (
24
24
  AgentManager,
25
- AgentType,
26
25
  MessageHistoryUpdated,
27
26
  PartialResponseMessage,
28
27
  )
29
28
  from shotgun.agents.config import get_provider_model
29
+ from shotgun.agents.conversation_history import (
30
+ ConversationHistory,
31
+ ConversationState,
32
+ )
33
+ from shotgun.agents.conversation_manager import ConversationManager
30
34
  from shotgun.agents.models import (
31
35
  AgentDeps,
36
+ AgentType,
32
37
  FileOperationTracker,
33
38
  UserAnswer,
34
39
  UserQuestion,
@@ -36,7 +41,7 @@ from shotgun.agents.models import (
36
41
  from shotgun.codebase.core.manager import CodebaseAlreadyIndexedError
37
42
  from shotgun.sdk.codebase import CodebaseSDK
38
43
  from shotgun.sdk.exceptions import CodebaseNotFoundError, InvalidPathError
39
- from shotgun.sdk.services import get_artifact_service, get_codebase_service
44
+ from shotgun.sdk.services import get_codebase_service
40
45
  from shotgun.tui.commands import CommandHandler
41
46
  from shotgun.tui.screens.chat_screen.history import ChatHistory
42
47
 
@@ -323,12 +328,11 @@ class ChatScreen(Screen[None]):
323
328
  indexing_job: reactive[CodebaseIndexSelection | None] = reactive(None)
324
329
  partial_message: reactive[ModelMessage | None] = reactive(None)
325
330
 
326
- def __init__(self) -> None:
331
+ def __init__(self, continue_session: bool = False) -> None:
327
332
  super().__init__()
328
333
  # Get the model configuration and services
329
334
  model_config = get_provider_model()
330
335
  codebase_service = get_codebase_service()
331
- artifact_service = get_artifact_service()
332
336
  self.codebase_sdk = CodebaseSDK()
333
337
 
334
338
  # Create shared deps without system_prompt_fn (agents provide their own)
@@ -343,17 +347,23 @@ class ChatScreen(Screen[None]):
343
347
  is_tui_context=True,
344
348
  llm_model=model_config,
345
349
  codebase_service=codebase_service,
346
- artifact_service=artifact_service,
347
350
  system_prompt_fn=_placeholder_system_prompt_fn,
348
351
  )
349
352
  self.agent_manager = AgentManager(deps=self.deps, initial_type=self.mode)
350
353
  self.command_handler = CommandHandler()
351
354
  self.placeholder_hints = PlaceholderHints()
355
+ self.conversation_manager = ConversationManager()
356
+ self.continue_session = continue_session
352
357
 
353
358
  def on_mount(self) -> None:
354
359
  self.query_one(PromptInput).focus(scroll_visible=True)
355
360
  # Hide spinner initially
356
361
  self.query_one("#spinner").display = False
362
+
363
+ # Load conversation history if --continue flag was provided
364
+ if self.continue_session and self.conversation_manager.exists():
365
+ self._load_conversation()
366
+
357
367
  self.call_later(self.check_if_codebase_is_indexed)
358
368
  # Start the question listener worker to handle ask_user interactions
359
369
  self.call_later(self.add_question_listener)
@@ -368,16 +378,11 @@ class ChatScreen(Screen[None]):
368
378
  if is_empty:
369
379
  return
370
380
 
371
- # find at least one codebase that is indexed in the current directory
372
- directory_indexed = next(
373
- (
374
- dir
375
- for dir in (await self.codebase_sdk.list_codebases()).graphs
376
- if cur_dir.is_relative_to(Path(dir.repo_path).resolve())
377
- ),
378
- None,
379
- )
380
- if directory_indexed:
381
+ # Check if the current directory has any accessible codebases
382
+ accessible_graphs = (
383
+ await self.codebase_sdk.list_codebases_for_directory()
384
+ ).graphs
385
+ if accessible_graphs:
381
386
  self.mount_hint(help_text_with_codebase())
382
387
  return
383
388
 
@@ -478,6 +483,10 @@ class ChatScreen(Screen[None]):
478
483
  if not chat_history.vertical_tail:
479
484
  return
480
485
  chat_history.vertical_tail.mount(Markdown(markdown))
486
+ # Scroll to bottom after mounting hint
487
+ chat_history.vertical_tail.call_after_refresh(
488
+ chat_history.vertical_tail.scroll_end, animate=False
489
+ )
481
490
 
482
491
  @on(PartialResponseMessage)
483
492
  def handle_partial_response(self, event: PartialResponseMessage) -> None:
@@ -590,9 +599,7 @@ class ChatScreen(Screen[None]):
590
599
  Returns:
591
600
  Dynamic placeholder hint based on mode and progress.
592
601
  """
593
- return self.placeholder_hints.get_placeholder_for_mode(
594
- mode, force_new=force_new
595
- )
602
+ return self.placeholder_hints.get_placeholder_for_mode(mode)
596
603
 
597
604
  def index_codebase_command(self) -> None:
598
605
  start_path = Path.cwd()
@@ -639,8 +646,18 @@ class ChatScreen(Screen[None]):
639
646
  )
640
647
  label.refresh()
641
648
  try:
649
+ # Pass the current working directory as the indexed_from_cwd
650
+ logger.debug(
651
+ f"Starting indexing - repo_path: {selection.repo_path}, "
652
+ f"name: {selection.name}, cwd: {Path.cwd().resolve()}"
653
+ )
642
654
  result = await self.codebase_sdk.index_codebase(
643
- selection.repo_path, selection.name
655
+ selection.repo_path,
656
+ selection.name,
657
+ indexed_from_cwd=str(Path.cwd().resolve()),
658
+ )
659
+ logger.info(
660
+ f"Successfully indexed codebase '{result.name}' (ID: {result.graph_id})"
644
661
  )
645
662
  self.notify(
646
663
  f"Indexed codebase '{result.name}' (ID: {result.graph_id})",
@@ -650,12 +667,19 @@ class ChatScreen(Screen[None]):
650
667
 
651
668
  self.mount_hint(codebase_indexed_hint(selection.name))
652
669
  except CodebaseAlreadyIndexedError as exc:
670
+ logger.warning(f"Codebase already indexed: {exc}")
653
671
  self.notify(str(exc), severity="warning")
654
672
  return
655
673
  except InvalidPathError as exc:
674
+ logger.error(f"Invalid path error: {exc}")
656
675
  self.notify(str(exc), severity="error")
657
676
 
658
677
  except Exception as exc: # pragma: no cover - defensive UI path
678
+ # Log full exception details with stack trace
679
+ logger.exception(
680
+ f"Failed to index codebase - repo_path: {selection.repo_path}, "
681
+ f"name: {selection.name}, error: {exc}"
682
+ )
659
683
  self.notify(f"Failed to index codebase: {exc}", severity="error")
660
684
  finally:
661
685
  label.update("")
@@ -690,9 +714,46 @@ class ChatScreen(Screen[None]):
690
714
  )
691
715
  self.working = False
692
716
 
717
+ # Save conversation after each interaction
718
+ self._save_conversation()
719
+
693
720
  prompt_input = self.query_one(PromptInput)
694
721
  prompt_input.focus()
695
722
 
723
+ def _save_conversation(self) -> None:
724
+ """Save the current conversation to persistent storage."""
725
+ # Get conversation state from agent manager
726
+ state = self.agent_manager.get_conversation_state()
727
+
728
+ # Create conversation history object
729
+ conversation = ConversationHistory(
730
+ last_agent_model=state.agent_type,
731
+ )
732
+ conversation.set_agent_messages(state.agent_messages)
733
+
734
+ # Save to file
735
+ self.conversation_manager.save(conversation)
736
+
737
+ def _load_conversation(self) -> None:
738
+ """Load conversation from persistent storage."""
739
+ conversation = self.conversation_manager.load()
740
+ if conversation is None:
741
+ return
742
+
743
+ # Restore agent state
744
+ agent_messages = conversation.get_agent_messages()
745
+
746
+ # Create ConversationState for restoration
747
+ state = ConversationState(
748
+ agent_messages=agent_messages,
749
+ agent_type=conversation.last_agent_model,
750
+ )
751
+
752
+ self.agent_manager.restore_conversation_state(state)
753
+
754
+ # Update the current mode
755
+ self.mode = AgentType(conversation.last_agent_model)
756
+
696
757
 
697
758
  def codebase_indexed_hint(codebase_name: str) -> str:
698
759
  return (
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, cast
3
3
 
4
4
  from textual.command import DiscoveryHit, Hit, Provider
5
5
 
6
- from shotgun.agents.agent_manager import AgentType
6
+ from shotgun.agents.models import AgentType
7
7
  from shotgun.codebase.models import CodebaseGraph
8
8
 
9
9
  if TYPE_CHECKING:
@@ -187,6 +187,12 @@ class AgentResponseWidget(Widget):
187
187
  def _format_tool_call_part(self, part: ToolCallPart) -> str:
188
188
  if part.tool_name == "ask_user":
189
189
  return self._format_ask_user_part(part)
190
+ # write_file
191
+ if part.tool_name == "write_file" or part.tool_name == "append_file":
192
+ if isinstance(part.args, dict) and "filename" in part.args:
193
+ return f"{part.tool_name}({part.args['filename']})"
194
+ else:
195
+ return f"{part.tool_name}()"
190
196
  if part.tool_name == "write_artifact_section":
191
197
  if isinstance(part.args, dict) and "section_title" in part.args:
192
198
  return f"{part.tool_name}({part.args['section_title']})"