shotgun-sh 0.2.29.dev2__py3-none-any.whl → 0.6.1.dev1__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 (161) hide show
  1. shotgun/agents/agent_manager.py +497 -30
  2. shotgun/agents/cancellation.py +103 -0
  3. shotgun/agents/common.py +90 -77
  4. shotgun/agents/config/README.md +0 -1
  5. shotgun/agents/config/manager.py +52 -8
  6. shotgun/agents/config/models.py +48 -45
  7. shotgun/agents/config/provider.py +44 -29
  8. shotgun/agents/conversation/history/file_content_deduplication.py +66 -43
  9. shotgun/agents/conversation/history/token_counting/base.py +51 -9
  10. shotgun/agents/export.py +12 -13
  11. shotgun/agents/file_read.py +176 -0
  12. shotgun/agents/messages.py +15 -3
  13. shotgun/agents/models.py +90 -2
  14. shotgun/agents/plan.py +12 -13
  15. shotgun/agents/research.py +13 -10
  16. shotgun/agents/router/__init__.py +47 -0
  17. shotgun/agents/router/models.py +384 -0
  18. shotgun/agents/router/router.py +185 -0
  19. shotgun/agents/router/tools/__init__.py +18 -0
  20. shotgun/agents/router/tools/delegation_tools.py +557 -0
  21. shotgun/agents/router/tools/plan_tools.py +403 -0
  22. shotgun/agents/runner.py +17 -2
  23. shotgun/agents/specify.py +12 -13
  24. shotgun/agents/tasks.py +12 -13
  25. shotgun/agents/tools/__init__.py +8 -0
  26. shotgun/agents/tools/codebase/directory_lister.py +27 -39
  27. shotgun/agents/tools/codebase/file_read.py +26 -35
  28. shotgun/agents/tools/codebase/query_graph.py +9 -0
  29. shotgun/agents/tools/codebase/retrieve_code.py +9 -0
  30. shotgun/agents/tools/file_management.py +81 -3
  31. shotgun/agents/tools/file_read_tools/__init__.py +7 -0
  32. shotgun/agents/tools/file_read_tools/multimodal_file_read.py +167 -0
  33. shotgun/agents/tools/markdown_tools/__init__.py +62 -0
  34. shotgun/agents/tools/markdown_tools/insert_section.py +148 -0
  35. shotgun/agents/tools/markdown_tools/models.py +86 -0
  36. shotgun/agents/tools/markdown_tools/remove_section.py +114 -0
  37. shotgun/agents/tools/markdown_tools/replace_section.py +119 -0
  38. shotgun/agents/tools/markdown_tools/utils.py +453 -0
  39. shotgun/agents/tools/registry.py +41 -0
  40. shotgun/agents/tools/web_search/__init__.py +1 -2
  41. shotgun/agents/tools/web_search/gemini.py +1 -3
  42. shotgun/agents/tools/web_search/openai.py +42 -23
  43. shotgun/attachments/__init__.py +41 -0
  44. shotgun/attachments/errors.py +60 -0
  45. shotgun/attachments/models.py +107 -0
  46. shotgun/attachments/parser.py +257 -0
  47. shotgun/attachments/processor.py +193 -0
  48. shotgun/cli/clear.py +2 -2
  49. shotgun/cli/codebase/commands.py +181 -65
  50. shotgun/cli/compact.py +2 -2
  51. shotgun/cli/context.py +2 -2
  52. shotgun/cli/run.py +90 -0
  53. shotgun/cli/spec/backup.py +2 -1
  54. shotgun/cli/spec/commands.py +2 -0
  55. shotgun/cli/spec/models.py +18 -0
  56. shotgun/cli/spec/pull_service.py +122 -68
  57. shotgun/codebase/__init__.py +2 -0
  58. shotgun/codebase/benchmarks/__init__.py +35 -0
  59. shotgun/codebase/benchmarks/benchmark_runner.py +309 -0
  60. shotgun/codebase/benchmarks/exporters.py +119 -0
  61. shotgun/codebase/benchmarks/formatters/__init__.py +49 -0
  62. shotgun/codebase/benchmarks/formatters/base.py +34 -0
  63. shotgun/codebase/benchmarks/formatters/json_formatter.py +106 -0
  64. shotgun/codebase/benchmarks/formatters/markdown.py +136 -0
  65. shotgun/codebase/benchmarks/models.py +129 -0
  66. shotgun/codebase/core/__init__.py +4 -0
  67. shotgun/codebase/core/call_resolution.py +91 -0
  68. shotgun/codebase/core/change_detector.py +11 -6
  69. shotgun/codebase/core/errors.py +159 -0
  70. shotgun/codebase/core/extractors/__init__.py +23 -0
  71. shotgun/codebase/core/extractors/base.py +138 -0
  72. shotgun/codebase/core/extractors/factory.py +63 -0
  73. shotgun/codebase/core/extractors/go/__init__.py +7 -0
  74. shotgun/codebase/core/extractors/go/extractor.py +122 -0
  75. shotgun/codebase/core/extractors/javascript/__init__.py +7 -0
  76. shotgun/codebase/core/extractors/javascript/extractor.py +132 -0
  77. shotgun/codebase/core/extractors/protocol.py +109 -0
  78. shotgun/codebase/core/extractors/python/__init__.py +7 -0
  79. shotgun/codebase/core/extractors/python/extractor.py +141 -0
  80. shotgun/codebase/core/extractors/rust/__init__.py +7 -0
  81. shotgun/codebase/core/extractors/rust/extractor.py +139 -0
  82. shotgun/codebase/core/extractors/types.py +15 -0
  83. shotgun/codebase/core/extractors/typescript/__init__.py +7 -0
  84. shotgun/codebase/core/extractors/typescript/extractor.py +92 -0
  85. shotgun/codebase/core/gitignore.py +252 -0
  86. shotgun/codebase/core/ingestor.py +644 -354
  87. shotgun/codebase/core/kuzu_compat.py +119 -0
  88. shotgun/codebase/core/language_config.py +239 -0
  89. shotgun/codebase/core/manager.py +256 -46
  90. shotgun/codebase/core/metrics_collector.py +310 -0
  91. shotgun/codebase/core/metrics_types.py +347 -0
  92. shotgun/codebase/core/parallel_executor.py +424 -0
  93. shotgun/codebase/core/work_distributor.py +254 -0
  94. shotgun/codebase/core/worker.py +768 -0
  95. shotgun/codebase/indexing_state.py +86 -0
  96. shotgun/codebase/models.py +94 -0
  97. shotgun/codebase/service.py +13 -0
  98. shotgun/exceptions.py +1 -1
  99. shotgun/main.py +2 -10
  100. shotgun/prompts/agents/export.j2 +2 -0
  101. shotgun/prompts/agents/file_read.j2 +48 -0
  102. shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +20 -28
  103. shotgun/prompts/agents/partials/content_formatting.j2 +12 -33
  104. shotgun/prompts/agents/partials/interactive_mode.j2 +9 -32
  105. shotgun/prompts/agents/partials/router_delegation_mode.j2 +35 -0
  106. shotgun/prompts/agents/plan.j2 +43 -1
  107. shotgun/prompts/agents/research.j2 +75 -20
  108. shotgun/prompts/agents/router.j2 +713 -0
  109. shotgun/prompts/agents/specify.j2 +94 -4
  110. shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +14 -1
  111. shotgun/prompts/agents/state/system_state.j2 +24 -15
  112. shotgun/prompts/agents/tasks.j2 +77 -23
  113. shotgun/settings.py +44 -0
  114. shotgun/shotgun_web/shared_specs/upload_pipeline.py +38 -0
  115. shotgun/tui/app.py +90 -23
  116. shotgun/tui/commands/__init__.py +9 -1
  117. shotgun/tui/components/attachment_bar.py +87 -0
  118. shotgun/tui/components/mode_indicator.py +120 -25
  119. shotgun/tui/components/prompt_input.py +23 -28
  120. shotgun/tui/components/status_bar.py +5 -4
  121. shotgun/tui/dependencies.py +58 -8
  122. shotgun/tui/protocols.py +37 -0
  123. shotgun/tui/screens/chat/chat.tcss +24 -1
  124. shotgun/tui/screens/chat/chat_screen.py +1374 -211
  125. shotgun/tui/screens/chat/codebase_index_prompt_screen.py +8 -4
  126. shotgun/tui/screens/chat_screen/attachment_hint.py +40 -0
  127. shotgun/tui/screens/chat_screen/command_providers.py +0 -97
  128. shotgun/tui/screens/chat_screen/history/agent_response.py +7 -3
  129. shotgun/tui/screens/chat_screen/history/chat_history.py +49 -6
  130. shotgun/tui/screens/chat_screen/history/formatters.py +75 -15
  131. shotgun/tui/screens/chat_screen/history/partial_response.py +11 -1
  132. shotgun/tui/screens/chat_screen/history/user_question.py +25 -3
  133. shotgun/tui/screens/chat_screen/messages.py +219 -0
  134. shotgun/tui/screens/database_locked_dialog.py +219 -0
  135. shotgun/tui/screens/database_timeout_dialog.py +158 -0
  136. shotgun/tui/screens/kuzu_error_dialog.py +135 -0
  137. shotgun/tui/screens/model_picker.py +14 -9
  138. shotgun/tui/screens/models.py +11 -0
  139. shotgun/tui/screens/shotgun_auth.py +50 -0
  140. shotgun/tui/screens/spec_pull.py +2 -0
  141. shotgun/tui/state/processing_state.py +19 -0
  142. shotgun/tui/utils/mode_progress.py +20 -86
  143. shotgun/tui/widgets/__init__.py +2 -1
  144. shotgun/tui/widgets/approval_widget.py +152 -0
  145. shotgun/tui/widgets/cascade_confirmation_widget.py +203 -0
  146. shotgun/tui/widgets/plan_panel.py +129 -0
  147. shotgun/tui/widgets/step_checkpoint_widget.py +180 -0
  148. shotgun/tui/widgets/widget_coordinator.py +18 -0
  149. shotgun/utils/file_system_utils.py +4 -1
  150. {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/METADATA +88 -34
  151. shotgun_sh-0.6.1.dev1.dist-info/RECORD +292 -0
  152. shotgun/cli/export.py +0 -81
  153. shotgun/cli/plan.py +0 -73
  154. shotgun/cli/research.py +0 -93
  155. shotgun/cli/specify.py +0 -70
  156. shotgun/cli/tasks.py +0 -78
  157. shotgun/tui/screens/onboarding.py +0 -580
  158. shotgun_sh-0.2.29.dev2.dist-info/RECORD +0 -229
  159. {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/WHEEL +0 -0
  160. {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/entry_points.txt +0 -0
  161. {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,109 @@
1
+ """Protocol definition for language extractors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
6
+
7
+ if TYPE_CHECKING:
8
+ from tree_sitter import Node
9
+
10
+ from .types import SupportedLanguage
11
+
12
+
13
+ @runtime_checkable
14
+ class LanguageExtractor(Protocol):
15
+ """Protocol for language-specific AST extraction.
16
+
17
+ Each language implementation provides methods to extract:
18
+ - Decorators/attributes from function/class nodes
19
+ - Docstrings/documentation
20
+ - Inheritance information from class definitions
21
+ - Call expression parsing
22
+ - Node type information for the language
23
+ """
24
+
25
+ @property
26
+ def language(self) -> SupportedLanguage:
27
+ """The language this extractor handles."""
28
+ ...
29
+
30
+ def extract_decorators(self, node: Node) -> list[str]:
31
+ """Extract decorators/attributes from a function or class node.
32
+
33
+ Args:
34
+ node: The AST node (function or class definition)
35
+
36
+ Returns:
37
+ List of decorator/attribute names
38
+ """
39
+ ...
40
+
41
+ def extract_docstring(self, node: Node) -> str | None:
42
+ """Extract documentation string from a function or class node.
43
+
44
+ Args:
45
+ node: The AST node (function or class definition)
46
+
47
+ Returns:
48
+ The docstring content, or None if not present
49
+ """
50
+ ...
51
+
52
+ def extract_inheritance(self, class_node: Node) -> list[str]:
53
+ """Extract parent class names from a class definition.
54
+
55
+ Args:
56
+ class_node: The class definition AST node
57
+
58
+ Returns:
59
+ List of parent class names (simple names, may need resolution)
60
+ """
61
+ ...
62
+
63
+ def parse_call_node(self, call_node: Node) -> tuple[str | None, str | None]:
64
+ """Parse a call expression node to extract callee information.
65
+
66
+ Args:
67
+ call_node: The call expression AST node
68
+
69
+ Returns:
70
+ Tuple of (callee_name, object_name) where:
71
+ - callee_name: The name of the function/method being called
72
+ - object_name: The object the method is called on (for method calls)
73
+ """
74
+ ...
75
+
76
+ def find_parent_class(self, func_node: Node, module_qn: str) -> str | None:
77
+ """Find the parent class of a function node.
78
+
79
+ Args:
80
+ func_node: The function definition AST node
81
+ module_qn: Module qualified name
82
+
83
+ Returns:
84
+ Qualified name of parent class, or None if not in a class
85
+ """
86
+ ...
87
+
88
+ def find_containing_function(self, node: Node, module_qn: str) -> str | None:
89
+ """Find the containing function/method of a node.
90
+
91
+ Args:
92
+ node: The AST node
93
+ module_qn: Module qualified name
94
+
95
+ Returns:
96
+ Qualified name of containing function, or None
97
+ """
98
+ ...
99
+
100
+ def count_ast_nodes(self, node: Node) -> int:
101
+ """Count total AST nodes for metrics.
102
+
103
+ Args:
104
+ node: Root AST node
105
+
106
+ Returns:
107
+ Total node count
108
+ """
109
+ ...
@@ -0,0 +1,7 @@
1
+ """Python language extractor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .extractor import PythonExtractor
6
+
7
+ __all__ = ["PythonExtractor"]
@@ -0,0 +1,141 @@
1
+ """Python language extractor implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from shotgun.codebase.core.extractors.base import BaseExtractor
8
+ from shotgun.codebase.core.extractors.types import SupportedLanguage
9
+
10
+ if TYPE_CHECKING:
11
+ from tree_sitter import Node
12
+
13
+
14
+ class PythonExtractor(BaseExtractor):
15
+ """Extractor for Python source code."""
16
+
17
+ @property
18
+ def language(self) -> SupportedLanguage:
19
+ """The language this extractor handles."""
20
+ return SupportedLanguage.PYTHON
21
+
22
+ def _class_definition_types(self) -> list[str]:
23
+ """Return node types that represent class definitions."""
24
+ return ["class_definition"]
25
+
26
+ def _function_definition_types(self) -> list[str]:
27
+ """Return node types that represent function definitions."""
28
+ return ["function_definition"]
29
+
30
+ def extract_decorators(self, node: Node) -> list[str]:
31
+ """Extract decorators from a function or class node.
32
+
33
+ Args:
34
+ node: The AST node (function or class definition)
35
+
36
+ Returns:
37
+ List of decorator names
38
+ """
39
+ decorators: list[str] = []
40
+
41
+ for child in node.children:
42
+ if child.type == "decorator":
43
+ for grandchild in child.children:
44
+ if grandchild.type == "identifier" and grandchild.text:
45
+ decorators.append(grandchild.text.decode("utf-8"))
46
+ break
47
+ elif grandchild.type == "attribute":
48
+ attr_node = grandchild.child_by_field_name("attribute")
49
+ if attr_node and attr_node.text:
50
+ decorators.append(attr_node.text.decode("utf-8"))
51
+ break
52
+
53
+ return decorators
54
+
55
+ def extract_docstring(self, node: Node) -> str | None:
56
+ """Extract docstring from a function or class node.
57
+
58
+ Args:
59
+ node: The AST node (function or class definition)
60
+
61
+ Returns:
62
+ The docstring content, or None if not present
63
+ """
64
+ body_node = node.child_by_field_name("body")
65
+ if not body_node or not body_node.children:
66
+ return None
67
+
68
+ first_statement = body_node.children[0]
69
+ if first_statement.type == "expression_statement":
70
+ for child in first_statement.children:
71
+ if child.type == "string" and child.text:
72
+ docstring = child.text.decode("utf-8")
73
+ docstring = docstring.strip()
74
+ if (
75
+ docstring.startswith('"""')
76
+ and docstring.endswith('"""')
77
+ or docstring.startswith("'''")
78
+ and docstring.endswith("'''")
79
+ ):
80
+ docstring = docstring[3:-3]
81
+ elif (
82
+ docstring.startswith('"')
83
+ and docstring.endswith('"')
84
+ or docstring.startswith("'")
85
+ and docstring.endswith("'")
86
+ ):
87
+ docstring = docstring[1:-1]
88
+ return docstring.strip()
89
+
90
+ return None
91
+
92
+ def extract_inheritance(self, class_node: Node) -> list[str]:
93
+ """Extract parent class names from a class definition.
94
+
95
+ Args:
96
+ class_node: The class definition AST node
97
+
98
+ Returns:
99
+ List of parent class names (simple names, may need resolution)
100
+ """
101
+ parent_names: list[str] = []
102
+
103
+ for child in class_node.children:
104
+ if child.type == "argument_list":
105
+ for arg in child.children:
106
+ if arg.type == "identifier" and arg.text:
107
+ parent_names.append(arg.text.decode("utf-8"))
108
+ elif arg.type == "attribute":
109
+ full_name_parts: list[str] = []
110
+ self._extract_full_name(arg, full_name_parts)
111
+ if full_name_parts:
112
+ parent_names.append(".".join(full_name_parts))
113
+
114
+ return parent_names
115
+
116
+ def parse_call_node(self, call_node: Node) -> tuple[str | None, str | None]:
117
+ """Parse a call expression node to extract callee information.
118
+
119
+ Args:
120
+ call_node: The call expression AST node
121
+
122
+ Returns:
123
+ Tuple of (callee_name, object_name)
124
+ """
125
+ callee_name = None
126
+ object_name = None
127
+
128
+ for child in call_node.children:
129
+ if child.type == "identifier" and child.text:
130
+ callee_name = child.text.decode("utf-8")
131
+ break
132
+ elif child.type == "attribute":
133
+ obj_node = child.child_by_field_name("object")
134
+ attr_node = child.child_by_field_name("attribute")
135
+ if obj_node and obj_node.text:
136
+ object_name = obj_node.text.decode("utf-8")
137
+ if attr_node and attr_node.text:
138
+ callee_name = attr_node.text.decode("utf-8")
139
+ break
140
+
141
+ return callee_name, object_name
@@ -0,0 +1,7 @@
1
+ """Rust language extractor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .extractor import RustExtractor
6
+
7
+ __all__ = ["RustExtractor"]
@@ -0,0 +1,139 @@
1
+ """Rust language extractor implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from shotgun.codebase.core.extractors.base import BaseExtractor
8
+ from shotgun.codebase.core.extractors.types import SupportedLanguage
9
+
10
+ if TYPE_CHECKING:
11
+ from tree_sitter import Node
12
+
13
+
14
+ class RustExtractor(BaseExtractor):
15
+ """Extractor for Rust source code."""
16
+
17
+ @property
18
+ def language(self) -> SupportedLanguage:
19
+ """The language this extractor handles."""
20
+ return SupportedLanguage.RUST
21
+
22
+ def _class_definition_types(self) -> list[str]:
23
+ """Return node types that represent type definitions.
24
+
25
+ Rust has structs, enums, traits, and type aliases.
26
+ """
27
+ return ["struct_item", "enum_item", "trait_item", "type_item"]
28
+
29
+ def _function_definition_types(self) -> list[str]:
30
+ """Return node types that represent function definitions."""
31
+ return ["function_item", "function_signature_item"]
32
+
33
+ def extract_decorators(self, node: Node) -> list[str]:
34
+ """Extract attributes from a function or struct node.
35
+
36
+ Rust uses #[attribute] syntax.
37
+
38
+ Args:
39
+ node: The AST node
40
+
41
+ Returns:
42
+ List of attribute names
43
+ """
44
+ attributes: list[str] = []
45
+
46
+ for child in node.children:
47
+ if child.type == "attribute_item":
48
+ for attr_child in child.children:
49
+ if attr_child.type == "attribute":
50
+ path = attr_child.child_by_field_name("path")
51
+ if path and path.text:
52
+ attributes.append(path.text.decode("utf-8"))
53
+
54
+ return attributes
55
+
56
+ def extract_docstring(self, node: Node) -> str | None:
57
+ """Extract doc comment from a function or type node.
58
+
59
+ Rust uses /// for outer doc comments and //! for inner doc comments.
60
+
61
+ Args:
62
+ node: The AST node
63
+
64
+ Returns:
65
+ The doc comment, or None if not present
66
+ """
67
+ doc_lines: list[str] = []
68
+ prev_sibling = node.prev_named_sibling
69
+
70
+ while prev_sibling and prev_sibling.type == "line_comment":
71
+ comment_text = prev_sibling.text
72
+ if comment_text:
73
+ text = comment_text.decode("utf-8")
74
+ if text.startswith("///"):
75
+ doc_lines.insert(0, text[3:].strip())
76
+ else:
77
+ break
78
+ prev_sibling = prev_sibling.prev_named_sibling
79
+
80
+ if doc_lines:
81
+ return "\n".join(doc_lines)
82
+ return None
83
+
84
+ def extract_inheritance(self, class_node: Node) -> list[str]:
85
+ """Extract trait bounds or supertraits from a type definition.
86
+
87
+ For structs, this returns nothing (Rust doesn't have struct inheritance).
88
+ For traits, this returns supertraits.
89
+
90
+ Args:
91
+ class_node: The type definition AST node
92
+
93
+ Returns:
94
+ List of supertrait names
95
+ """
96
+ supertraits: list[str] = []
97
+
98
+ if class_node.type == "trait_item":
99
+ for child in class_node.children:
100
+ if child.type == "trait_bounds":
101
+ for bound in child.children:
102
+ if bound.type == "type_identifier" and bound.text:
103
+ supertraits.append(bound.text.decode("utf-8"))
104
+ elif bound.type == "generic_type":
105
+ type_node = bound.child_by_field_name("type")
106
+ if type_node and type_node.text:
107
+ supertraits.append(type_node.text.decode("utf-8"))
108
+
109
+ return supertraits
110
+
111
+ def parse_call_node(self, call_node: Node) -> tuple[str | None, str | None]:
112
+ """Parse a call expression node to extract callee information.
113
+
114
+ Args:
115
+ call_node: The call expression AST node
116
+
117
+ Returns:
118
+ Tuple of (callee_name, object_name)
119
+ """
120
+ callee_name = None
121
+ object_name = None
122
+
123
+ func_node = call_node.child_by_field_name("function")
124
+ if func_node:
125
+ if func_node.type == "identifier" and func_node.text:
126
+ callee_name = func_node.text.decode("utf-8")
127
+ elif func_node.type == "field_expression":
128
+ value = func_node.child_by_field_name("value")
129
+ field = func_node.child_by_field_name("field")
130
+ if value and value.text:
131
+ object_name = value.text.decode("utf-8")
132
+ if field and field.text:
133
+ callee_name = field.text.decode("utf-8")
134
+ elif func_node.type == "scoped_identifier":
135
+ name = func_node.child_by_field_name("name")
136
+ if name and name.text:
137
+ callee_name = name.text.decode("utf-8")
138
+
139
+ return callee_name, object_name
@@ -0,0 +1,15 @@
1
+ """Type definitions for the extractors module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+
7
+
8
+ class SupportedLanguage(StrEnum):
9
+ """Supported programming languages for AST extraction."""
10
+
11
+ PYTHON = "python"
12
+ JAVASCRIPT = "javascript"
13
+ TYPESCRIPT = "typescript"
14
+ GO = "go"
15
+ RUST = "rust"
@@ -0,0 +1,7 @@
1
+ """TypeScript language extractor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .extractor import TypeScriptExtractor
6
+
7
+ __all__ = ["TypeScriptExtractor"]
@@ -0,0 +1,92 @@
1
+ """TypeScript language extractor implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from shotgun.codebase.core.extractors.javascript.extractor import JavaScriptExtractor
8
+ from shotgun.codebase.core.extractors.types import SupportedLanguage
9
+
10
+ if TYPE_CHECKING:
11
+ from tree_sitter import Node
12
+
13
+
14
+ class TypeScriptExtractor(JavaScriptExtractor):
15
+ """Extractor for TypeScript source code.
16
+
17
+ TypeScript is a superset of JavaScript, so this extends
18
+ JavaScriptExtractor with TypeScript-specific features.
19
+ """
20
+
21
+ @property
22
+ def language(self) -> SupportedLanguage:
23
+ """The language this extractor handles."""
24
+ return SupportedLanguage.TYPESCRIPT
25
+
26
+ def _class_definition_types(self) -> list[str]:
27
+ """Return node types that represent class definitions.
28
+
29
+ TypeScript adds interface_declaration and type_alias_declaration.
30
+ """
31
+ return [
32
+ "class_declaration",
33
+ "class",
34
+ "interface_declaration",
35
+ "type_alias_declaration",
36
+ ]
37
+
38
+ def extract_decorators(self, node: Node) -> list[str]:
39
+ """Extract decorators from a function or class node.
40
+
41
+ TypeScript supports decorators (experimental feature).
42
+
43
+ Args:
44
+ node: The AST node
45
+
46
+ Returns:
47
+ List of decorator names
48
+ """
49
+ decorators: list[str] = []
50
+
51
+ for child in node.children:
52
+ if child.type == "decorator":
53
+ for grandchild in child.children:
54
+ if grandchild.type == "identifier" and grandchild.text:
55
+ decorators.append(grandchild.text.decode("utf-8"))
56
+ break
57
+ elif grandchild.type == "call_expression":
58
+ for call_child in grandchild.children:
59
+ if call_child.type == "identifier" and call_child.text:
60
+ decorators.append(call_child.text.decode("utf-8"))
61
+ break
62
+
63
+ return decorators
64
+
65
+ def extract_inheritance(self, class_node: Node) -> list[str]:
66
+ """Extract parent class/interface names from a class or interface.
67
+
68
+ TypeScript classes can extend one class and implement multiple interfaces.
69
+ Interfaces can extend multiple interfaces.
70
+
71
+ Args:
72
+ class_node: The class/interface definition AST node
73
+
74
+ Returns:
75
+ List of parent names
76
+ """
77
+ parent_names: list[str] = []
78
+
79
+ for child in class_node.children:
80
+ if child.type in ["extends_clause", "implements_clause"]:
81
+ for type_node in child.children:
82
+ if type_node.type == "type_identifier" and type_node.text:
83
+ parent_names.append(type_node.text.decode("utf-8"))
84
+ elif type_node.type == "generic_type":
85
+ name_node = type_node.child_by_field_name("name")
86
+ if name_node and name_node.text:
87
+ parent_names.append(name_node.text.decode("utf-8"))
88
+
89
+ if not parent_names:
90
+ parent_names = super().extract_inheritance(class_node)
91
+
92
+ return parent_names