shotgun-sh 0.1.0.dev22__py3-none-any.whl → 0.1.0.dev23__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 (51) hide show
  1. shotgun/agents/agent_manager.py +95 -15
  2. shotgun/agents/common.py +139 -24
  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/main.py +9 -1
  13. shotgun/prompts/agents/export.j2 +14 -11
  14. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +6 -9
  15. shotgun/prompts/agents/plan.j2 +9 -13
  16. shotgun/prompts/agents/research.j2 +11 -14
  17. shotgun/prompts/agents/specify.j2 +9 -12
  18. shotgun/prompts/agents/state/system_state.j2 +27 -5
  19. shotgun/prompts/agents/tasks.j2 +12 -12
  20. shotgun/sdk/services.py +0 -14
  21. shotgun/tui/app.py +9 -4
  22. shotgun/tui/screens/chat.py +57 -8
  23. shotgun/tui/screens/chat_screen/command_providers.py +1 -1
  24. shotgun/tui/screens/chat_screen/history.py +6 -0
  25. shotgun/tui/utils/mode_progress.py +111 -78
  26. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/METADATA +8 -9
  27. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/RECORD +30 -49
  28. shotgun/agents/artifact_state.py +0 -58
  29. shotgun/agents/tools/artifact_management.py +0 -481
  30. shotgun/artifacts/__init__.py +0 -17
  31. shotgun/artifacts/exceptions.py +0 -89
  32. shotgun/artifacts/manager.py +0 -530
  33. shotgun/artifacts/models.py +0 -334
  34. shotgun/artifacts/service.py +0 -463
  35. shotgun/artifacts/templates/__init__.py +0 -10
  36. shotgun/artifacts/templates/loader.py +0 -252
  37. shotgun/artifacts/templates/models.py +0 -136
  38. shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +0 -66
  39. shotgun/artifacts/templates/research/market_research.yaml +0 -585
  40. shotgun/artifacts/templates/research/sdk_comparison.yaml +0 -257
  41. shotgun/artifacts/templates/specify/prd.yaml +0 -331
  42. shotgun/artifacts/templates/specify/product_spec.yaml +0 -301
  43. shotgun/artifacts/utils.py +0 -76
  44. shotgun/prompts/agents/partials/artifact_system.j2 +0 -32
  45. shotgun/prompts/agents/state/artifact_templates_available.j2 +0 -20
  46. shotgun/prompts/agents/state/existing_artifacts_available.j2 +0 -25
  47. shotgun/sdk/artifact_models.py +0 -186
  48. shotgun/sdk/artifacts.py +0 -448
  49. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/WHEEL +0 -0
  50. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/entry_points.txt +0 -0
  51. {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/licenses/LICENSE +0 -0
@@ -1,463 +0,0 @@
1
- """High-level service for artifact management and operations."""
2
-
3
- from pathlib import Path
4
- from typing import Any
5
-
6
- from shotgun.logging_config import setup_logger
7
-
8
- from .exceptions import (
9
- ArtifactAlreadyExistsError,
10
- ArtifactNotFoundError,
11
- SectionAlreadyExistsError,
12
- SectionNotFoundError,
13
- )
14
- from .manager import ArtifactManager
15
- from .models import AgentMode, Artifact, ArtifactSection, ArtifactSummary
16
- from .templates.loader import ArtifactTemplateLoader
17
- from .templates.models import ArtifactTemplate, TemplateSummary
18
- from .utils import generate_artifact_name
19
-
20
- logger = setup_logger(__name__)
21
-
22
-
23
- class ArtifactService:
24
- """High-level service for artifact management and operations."""
25
-
26
- def __init__(self, base_path: Path | None = None) -> None:
27
- """Initialize artifact service.
28
-
29
- Args:
30
- base_path: Base path for artifacts. Defaults to .shotgun in current directory.
31
- """
32
- self.manager = ArtifactManager(base_path)
33
- logger.debug("Initialized ArtifactService")
34
-
35
- def create_artifact(
36
- self,
37
- artifact_id: str,
38
- agent_mode: AgentMode,
39
- name: str,
40
- template_id: str | None = None,
41
- ) -> Artifact:
42
- """Create a new artifact.
43
-
44
- Args:
45
- artifact_id: Unique identifier for the artifact
46
- agent_mode: Agent mode this artifact belongs to
47
- name: Human-readable name for the artifact
48
- template_id: Optional template ID to use for creating the artifact
49
-
50
- Returns:
51
- Created artifact
52
-
53
- Raises:
54
- ArtifactAlreadyExistsError: If artifact already exists
55
- ValueError: If template is invalid or not found
56
- """
57
- if self.manager.artifact_exists(agent_mode, artifact_id):
58
- raise ArtifactAlreadyExistsError(artifact_id, agent_mode.value)
59
-
60
- # Load and validate template if provided
61
- template = None
62
- if template_id:
63
- template_loader = ArtifactTemplateLoader()
64
- template = template_loader.get_template(template_id)
65
- if not template:
66
- raise ValueError(f"Template '{template_id}' not found")
67
-
68
- # Validate template is for correct agent mode
69
- if template.agent_mode != agent_mode:
70
- raise ValueError(
71
- f"Template '{template_id}' is for agent mode '{template.agent_mode.value}', "
72
- f"but artifact is being created for '{agent_mode.value}'"
73
- )
74
-
75
- artifact = Artifact(
76
- artifact_id=artifact_id,
77
- agent_mode=agent_mode,
78
- name=name,
79
- sections=[],
80
- )
81
-
82
- # Create artifact first
83
- self.manager.create_artifact(artifact)
84
-
85
- # Apply template if provided
86
- if template and template_id:
87
- # Save template content to .template.yaml file
88
- self.manager.save_template_file(
89
- agent_mode, artifact_id, template.to_yaml_dict()
90
- )
91
- logger.info("Applied template '%s' to artifact", template_id)
92
-
93
- logger.info("Created artifact: %s/%s", agent_mode.value, artifact_id)
94
- return artifact
95
-
96
- def get_artifact(
97
- self, artifact_id: str, agent_mode: AgentMode, name: str = ""
98
- ) -> Artifact:
99
- """Get an artifact by ID and agent mode.
100
-
101
- Args:
102
- artifact_id: Artifact identifier
103
- agent_mode: Agent mode
104
- name: Artifact name (for display, not used for lookup)
105
-
106
- Returns:
107
- Artifact object
108
-
109
- Raises:
110
- ArtifactNotFoundError: If artifact is not found
111
- """
112
- return self.manager.load_artifact(agent_mode, artifact_id, name)
113
-
114
- def update_artifact(self, artifact: Artifact) -> Artifact:
115
- """Update an existing artifact.
116
-
117
- Args:
118
- artifact: Artifact to update
119
-
120
- Returns:
121
- Updated artifact
122
-
123
- Raises:
124
- ArtifactNotFoundError: If artifact is not found
125
- """
126
- self.manager.save_artifact(artifact)
127
- logger.info(
128
- "Updated artifact: %s/%s", artifact.agent_mode.value, artifact.artifact_id
129
- )
130
- return artifact
131
-
132
- def delete_artifact(self, artifact_id: str, agent_mode: AgentMode) -> None:
133
- """Delete an artifact.
134
-
135
- Args:
136
- artifact_id: Artifact identifier
137
- agent_mode: Agent mode
138
-
139
- Raises:
140
- ArtifactNotFoundError: If artifact is not found
141
- """
142
- self.manager.delete_artifact(agent_mode, artifact_id)
143
- logger.info("Deleted artifact: %s/%s", agent_mode.value, artifact_id)
144
-
145
- def list_artifacts(
146
- self, agent_mode: AgentMode | None = None
147
- ) -> list[ArtifactSummary]:
148
- """List all artifacts, optionally filtered by agent mode.
149
-
150
- Args:
151
- agent_mode: Optional agent mode filter
152
-
153
- Returns:
154
- List of artifact summaries
155
- """
156
- artifacts_data = self.manager.list_artifacts(agent_mode)
157
- summaries = []
158
-
159
- for data in artifacts_data:
160
- summary = ArtifactSummary(
161
- artifact_id=data["artifact_id"],
162
- agent_mode=data["agent_mode"],
163
- name=data["artifact_id"]
164
- .replace("-", " ")
165
- .title(), # Default name from ID
166
- section_count=data["section_count"],
167
- created_at=data["created_at"],
168
- updated_at=data["updated_at"],
169
- section_titles=data["section_titles"],
170
- template_id=None, # Will be detected from file when needed
171
- )
172
- summaries.append(summary)
173
-
174
- return summaries
175
-
176
- def artifact_exists(self, artifact_id: str, agent_mode: AgentMode) -> bool:
177
- """Check if an artifact exists.
178
-
179
- Args:
180
- artifact_id: Artifact identifier
181
- agent_mode: Agent mode
182
-
183
- Returns:
184
- True if artifact exists, False otherwise
185
- """
186
- return self.manager.artifact_exists(agent_mode, artifact_id)
187
-
188
- # Section operations
189
-
190
- def add_section(
191
- self,
192
- artifact_id: str,
193
- agent_mode: AgentMode,
194
- section: ArtifactSection,
195
- ) -> Artifact:
196
- """Add a section to an artifact.
197
-
198
- Args:
199
- artifact_id: Artifact identifier
200
- agent_mode: Agent mode
201
- section: Section to add
202
-
203
- Returns:
204
- Updated artifact
205
-
206
- Raises:
207
- ArtifactNotFoundError: If artifact is not found
208
- SectionAlreadyExistsError: If section number or slug already exists
209
- """
210
- artifact = self.manager.load_artifact(agent_mode, artifact_id, "")
211
-
212
- # Check for conflicts
213
- if artifact.get_section_by_number(section.number):
214
- raise SectionAlreadyExistsError(section.number, artifact_id)
215
- if artifact.get_section_by_slug(section.slug):
216
- raise SectionAlreadyExistsError(section.slug, artifact_id)
217
-
218
- artifact.add_section(section)
219
- # Write only the new section to disk, not all sections
220
- self.manager.write_section(agent_mode, artifact_id, section)
221
-
222
- logger.info(
223
- "Added section %d to artifact: %s/%s",
224
- section.number,
225
- agent_mode.value,
226
- artifact_id,
227
- )
228
- return artifact
229
-
230
- def get_section(
231
- self,
232
- artifact_id: str,
233
- agent_mode: AgentMode,
234
- section_number: int,
235
- ) -> ArtifactSection:
236
- """Get a section from an artifact.
237
-
238
- Args:
239
- artifact_id: Artifact identifier
240
- agent_mode: Agent mode
241
- section_number: Section number
242
-
243
- Returns:
244
- The section
245
-
246
- Raises:
247
- ArtifactNotFoundError: If artifact is not found
248
- SectionNotFoundError: If section is not found
249
- """
250
- artifact = self.manager.load_artifact(agent_mode, artifact_id, "")
251
- section = artifact.get_section_by_number(section_number)
252
-
253
- if not section:
254
- raise SectionNotFoundError(section_number, artifact_id)
255
-
256
- return section
257
-
258
- def update_section(
259
- self,
260
- artifact_id: str,
261
- agent_mode: AgentMode,
262
- section_number: int,
263
- **kwargs: Any,
264
- ) -> Artifact:
265
- """Update a section in an artifact.
266
-
267
- Args:
268
- artifact_id: Artifact identifier
269
- agent_mode: Agent mode
270
- section_number: Section number to update
271
- **kwargs: Fields to update
272
-
273
- Returns:
274
- Updated artifact
275
-
276
- Raises:
277
- ArtifactNotFoundError: If artifact is not found
278
- SectionNotFoundError: If section is not found
279
- """
280
- artifact = self.manager.load_artifact(agent_mode, artifact_id, "")
281
-
282
- if not artifact.update_section(section_number, **kwargs):
283
- raise SectionNotFoundError(section_number, artifact_id)
284
-
285
- # Write only the updated section to disk, not all sections
286
- updated_section = artifact.get_section_by_number(section_number)
287
- if updated_section:
288
- self.manager.update_section_file(agent_mode, artifact_id, updated_section)
289
-
290
- logger.info(
291
- "Updated section %d in artifact: %s/%s",
292
- section_number,
293
- agent_mode.value,
294
- artifact_id,
295
- )
296
- return artifact
297
-
298
- def delete_section(
299
- self,
300
- artifact_id: str,
301
- agent_mode: AgentMode,
302
- section_number: int,
303
- ) -> Artifact:
304
- """Delete a section from an artifact.
305
-
306
- Args:
307
- artifact_id: Artifact identifier
308
- agent_mode: Agent mode
309
- section_number: Section number to delete
310
-
311
- Returns:
312
- Updated artifact
313
-
314
- Raises:
315
- ArtifactNotFoundError: If artifact is not found
316
- SectionNotFoundError: If section is not found
317
- """
318
- artifact = self.manager.load_artifact(agent_mode, artifact_id, "")
319
-
320
- if not artifact.remove_section(section_number):
321
- raise SectionNotFoundError(section_number, artifact_id)
322
-
323
- # Delete only the specific section file, not rewrite all sections
324
- self.manager.delete_section_file(agent_mode, artifact_id, section_number)
325
-
326
- logger.info(
327
- "Deleted section %d from artifact: %s/%s",
328
- section_number,
329
- agent_mode.value,
330
- artifact_id,
331
- )
332
- return artifact
333
-
334
- def get_section_content(
335
- self,
336
- artifact_id: str,
337
- agent_mode: AgentMode,
338
- section_number: int,
339
- ) -> str:
340
- """Get the raw content of a section.
341
-
342
- Args:
343
- artifact_id: Artifact identifier
344
- agent_mode: Agent mode
345
- section_number: Section number
346
-
347
- Returns:
348
- Raw markdown content of the section
349
-
350
- Raises:
351
- ArtifactNotFoundError: If artifact is not found
352
- SectionNotFoundError: If section is not found
353
- """
354
- return self.manager.read_section(agent_mode, artifact_id, section_number)
355
-
356
- # Convenience methods
357
-
358
- def get_or_create_artifact(
359
- self,
360
- artifact_id: str,
361
- agent_mode: AgentMode,
362
- name: str,
363
- template_id: str | None = None,
364
- ) -> tuple[Artifact, bool]:
365
- """Get an existing artifact or create a new one.
366
-
367
- Args:
368
- artifact_id: Artifact identifier
369
- agent_mode: Agent mode
370
- name: Artifact name
371
- template_id: Optional template ID to use when creating
372
-
373
- Returns:
374
- Tuple of (artifact, created) where created is True if newly created
375
- """
376
- try:
377
- artifact = self.get_artifact(artifact_id, agent_mode, name)
378
- return artifact, False
379
- except ArtifactNotFoundError:
380
- artifact = self.create_artifact(artifact_id, agent_mode, name, template_id)
381
- return artifact, True
382
-
383
- def get_or_create_section(
384
- self,
385
- artifact_id: str,
386
- agent_mode: AgentMode,
387
- section_number: int,
388
- section_slug: str,
389
- section_title: str,
390
- initial_content: str = "",
391
- ) -> tuple[ArtifactSection, bool]:
392
- """Get an existing section or create a new one.
393
-
394
- Args:
395
- artifact_id: Artifact identifier
396
- agent_mode: Agent mode
397
- section_number: Section number
398
- section_slug: Section slug
399
- section_title: Section title
400
- initial_content: Initial content for new sections
401
-
402
- Returns:
403
- Tuple of (section, created) where created is True if newly created
404
- """
405
- try:
406
- section = self.get_section(artifact_id, agent_mode, section_number)
407
- return section, False
408
- except (ArtifactNotFoundError, SectionNotFoundError):
409
- # Create artifact if it doesn't exist
410
- artifact, _ = self.get_or_create_artifact(
411
- artifact_id, agent_mode, generate_artifact_name(artifact_id)
412
- )
413
-
414
- # Create section
415
- section = ArtifactSection(
416
- number=section_number,
417
- slug=section_slug,
418
- title=section_title,
419
- content=initial_content,
420
- )
421
-
422
- self.add_section(artifact_id, agent_mode, section)
423
- return section, True
424
-
425
- # Template operations
426
-
427
- def list_templates(
428
- self, agent_mode: AgentMode | None = None
429
- ) -> list[TemplateSummary]:
430
- """List available artifact templates.
431
-
432
- Args:
433
- agent_mode: Optional agent mode filter
434
-
435
- Returns:
436
- List of template summaries
437
- """
438
- template_loader = ArtifactTemplateLoader()
439
- return template_loader.list_templates(agent_mode)
440
-
441
- def get_template(self, template_id: str) -> ArtifactTemplate | None:
442
- """Get a specific template by ID.
443
-
444
- Args:
445
- template_id: Template identifier
446
-
447
- Returns:
448
- Template object or None if not found
449
- """
450
- template_loader = ArtifactTemplateLoader()
451
- return template_loader.get_template(template_id)
452
-
453
- def get_templates_for_mode(self, agent_mode: AgentMode) -> list[ArtifactTemplate]:
454
- """Get all templates for a specific agent mode.
455
-
456
- Args:
457
- agent_mode: Agent mode to filter by
458
-
459
- Returns:
460
- List of templates for the specified mode
461
- """
462
- template_loader = ArtifactTemplateLoader()
463
- return template_loader.get_templates_for_mode(agent_mode)
@@ -1,10 +0,0 @@
1
- """Artifact template system for structured guidance."""
2
-
3
- from .loader import ArtifactTemplateLoader
4
- from .models import ArtifactTemplate, TemplateSection
5
-
6
- __all__ = [
7
- "ArtifactTemplate",
8
- "TemplateSection",
9
- "ArtifactTemplateLoader",
10
- ]