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.
- shotgun/agents/agent_manager.py +497 -30
- shotgun/agents/cancellation.py +103 -0
- shotgun/agents/common.py +90 -77
- shotgun/agents/config/README.md +0 -1
- shotgun/agents/config/manager.py +52 -8
- shotgun/agents/config/models.py +48 -45
- shotgun/agents/config/provider.py +44 -29
- shotgun/agents/conversation/history/file_content_deduplication.py +66 -43
- shotgun/agents/conversation/history/token_counting/base.py +51 -9
- shotgun/agents/export.py +12 -13
- shotgun/agents/file_read.py +176 -0
- shotgun/agents/messages.py +15 -3
- shotgun/agents/models.py +90 -2
- shotgun/agents/plan.py +12 -13
- shotgun/agents/research.py +13 -10
- shotgun/agents/router/__init__.py +47 -0
- shotgun/agents/router/models.py +384 -0
- shotgun/agents/router/router.py +185 -0
- shotgun/agents/router/tools/__init__.py +18 -0
- shotgun/agents/router/tools/delegation_tools.py +557 -0
- shotgun/agents/router/tools/plan_tools.py +403 -0
- shotgun/agents/runner.py +17 -2
- shotgun/agents/specify.py +12 -13
- shotgun/agents/tasks.py +12 -13
- shotgun/agents/tools/__init__.py +8 -0
- shotgun/agents/tools/codebase/directory_lister.py +27 -39
- shotgun/agents/tools/codebase/file_read.py +26 -35
- shotgun/agents/tools/codebase/query_graph.py +9 -0
- shotgun/agents/tools/codebase/retrieve_code.py +9 -0
- shotgun/agents/tools/file_management.py +81 -3
- shotgun/agents/tools/file_read_tools/__init__.py +7 -0
- shotgun/agents/tools/file_read_tools/multimodal_file_read.py +167 -0
- shotgun/agents/tools/markdown_tools/__init__.py +62 -0
- shotgun/agents/tools/markdown_tools/insert_section.py +148 -0
- shotgun/agents/tools/markdown_tools/models.py +86 -0
- shotgun/agents/tools/markdown_tools/remove_section.py +114 -0
- shotgun/agents/tools/markdown_tools/replace_section.py +119 -0
- shotgun/agents/tools/markdown_tools/utils.py +453 -0
- shotgun/agents/tools/registry.py +41 -0
- shotgun/agents/tools/web_search/__init__.py +1 -2
- shotgun/agents/tools/web_search/gemini.py +1 -3
- shotgun/agents/tools/web_search/openai.py +42 -23
- shotgun/attachments/__init__.py +41 -0
- shotgun/attachments/errors.py +60 -0
- shotgun/attachments/models.py +107 -0
- shotgun/attachments/parser.py +257 -0
- shotgun/attachments/processor.py +193 -0
- shotgun/cli/clear.py +2 -2
- shotgun/cli/codebase/commands.py +181 -65
- shotgun/cli/compact.py +2 -2
- shotgun/cli/context.py +2 -2
- shotgun/cli/run.py +90 -0
- shotgun/cli/spec/backup.py +2 -1
- shotgun/cli/spec/commands.py +2 -0
- shotgun/cli/spec/models.py +18 -0
- shotgun/cli/spec/pull_service.py +122 -68
- shotgun/codebase/__init__.py +2 -0
- shotgun/codebase/benchmarks/__init__.py +35 -0
- shotgun/codebase/benchmarks/benchmark_runner.py +309 -0
- shotgun/codebase/benchmarks/exporters.py +119 -0
- shotgun/codebase/benchmarks/formatters/__init__.py +49 -0
- shotgun/codebase/benchmarks/formatters/base.py +34 -0
- shotgun/codebase/benchmarks/formatters/json_formatter.py +106 -0
- shotgun/codebase/benchmarks/formatters/markdown.py +136 -0
- shotgun/codebase/benchmarks/models.py +129 -0
- shotgun/codebase/core/__init__.py +4 -0
- shotgun/codebase/core/call_resolution.py +91 -0
- shotgun/codebase/core/change_detector.py +11 -6
- shotgun/codebase/core/errors.py +159 -0
- shotgun/codebase/core/extractors/__init__.py +23 -0
- shotgun/codebase/core/extractors/base.py +138 -0
- shotgun/codebase/core/extractors/factory.py +63 -0
- shotgun/codebase/core/extractors/go/__init__.py +7 -0
- shotgun/codebase/core/extractors/go/extractor.py +122 -0
- shotgun/codebase/core/extractors/javascript/__init__.py +7 -0
- shotgun/codebase/core/extractors/javascript/extractor.py +132 -0
- shotgun/codebase/core/extractors/protocol.py +109 -0
- shotgun/codebase/core/extractors/python/__init__.py +7 -0
- shotgun/codebase/core/extractors/python/extractor.py +141 -0
- shotgun/codebase/core/extractors/rust/__init__.py +7 -0
- shotgun/codebase/core/extractors/rust/extractor.py +139 -0
- shotgun/codebase/core/extractors/types.py +15 -0
- shotgun/codebase/core/extractors/typescript/__init__.py +7 -0
- shotgun/codebase/core/extractors/typescript/extractor.py +92 -0
- shotgun/codebase/core/gitignore.py +252 -0
- shotgun/codebase/core/ingestor.py +644 -354
- shotgun/codebase/core/kuzu_compat.py +119 -0
- shotgun/codebase/core/language_config.py +239 -0
- shotgun/codebase/core/manager.py +256 -46
- shotgun/codebase/core/metrics_collector.py +310 -0
- shotgun/codebase/core/metrics_types.py +347 -0
- shotgun/codebase/core/parallel_executor.py +424 -0
- shotgun/codebase/core/work_distributor.py +254 -0
- shotgun/codebase/core/worker.py +768 -0
- shotgun/codebase/indexing_state.py +86 -0
- shotgun/codebase/models.py +94 -0
- shotgun/codebase/service.py +13 -0
- shotgun/exceptions.py +1 -1
- shotgun/main.py +2 -10
- shotgun/prompts/agents/export.j2 +2 -0
- shotgun/prompts/agents/file_read.j2 +48 -0
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +20 -28
- shotgun/prompts/agents/partials/content_formatting.j2 +12 -33
- shotgun/prompts/agents/partials/interactive_mode.j2 +9 -32
- shotgun/prompts/agents/partials/router_delegation_mode.j2 +35 -0
- shotgun/prompts/agents/plan.j2 +43 -1
- shotgun/prompts/agents/research.j2 +75 -20
- shotgun/prompts/agents/router.j2 +713 -0
- shotgun/prompts/agents/specify.j2 +94 -4
- shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +14 -1
- shotgun/prompts/agents/state/system_state.j2 +24 -15
- shotgun/prompts/agents/tasks.j2 +77 -23
- shotgun/settings.py +44 -0
- shotgun/shotgun_web/shared_specs/upload_pipeline.py +38 -0
- shotgun/tui/app.py +90 -23
- shotgun/tui/commands/__init__.py +9 -1
- shotgun/tui/components/attachment_bar.py +87 -0
- shotgun/tui/components/mode_indicator.py +120 -25
- shotgun/tui/components/prompt_input.py +23 -28
- shotgun/tui/components/status_bar.py +5 -4
- shotgun/tui/dependencies.py +58 -8
- shotgun/tui/protocols.py +37 -0
- shotgun/tui/screens/chat/chat.tcss +24 -1
- shotgun/tui/screens/chat/chat_screen.py +1374 -211
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +8 -4
- shotgun/tui/screens/chat_screen/attachment_hint.py +40 -0
- shotgun/tui/screens/chat_screen/command_providers.py +0 -97
- shotgun/tui/screens/chat_screen/history/agent_response.py +7 -3
- shotgun/tui/screens/chat_screen/history/chat_history.py +49 -6
- shotgun/tui/screens/chat_screen/history/formatters.py +75 -15
- shotgun/tui/screens/chat_screen/history/partial_response.py +11 -1
- shotgun/tui/screens/chat_screen/history/user_question.py +25 -3
- shotgun/tui/screens/chat_screen/messages.py +219 -0
- shotgun/tui/screens/database_locked_dialog.py +219 -0
- shotgun/tui/screens/database_timeout_dialog.py +158 -0
- shotgun/tui/screens/kuzu_error_dialog.py +135 -0
- shotgun/tui/screens/model_picker.py +14 -9
- shotgun/tui/screens/models.py +11 -0
- shotgun/tui/screens/shotgun_auth.py +50 -0
- shotgun/tui/screens/spec_pull.py +2 -0
- shotgun/tui/state/processing_state.py +19 -0
- shotgun/tui/utils/mode_progress.py +20 -86
- shotgun/tui/widgets/__init__.py +2 -1
- shotgun/tui/widgets/approval_widget.py +152 -0
- shotgun/tui/widgets/cascade_confirmation_widget.py +203 -0
- shotgun/tui/widgets/plan_panel.py +129 -0
- shotgun/tui/widgets/step_checkpoint_widget.py +180 -0
- shotgun/tui/widgets/widget_coordinator.py +18 -0
- shotgun/utils/file_system_utils.py +4 -1
- {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/METADATA +88 -34
- shotgun_sh-0.6.1.dev1.dist-info/RECORD +292 -0
- shotgun/cli/export.py +0 -81
- shotgun/cli/plan.py +0 -73
- shotgun/cli/research.py +0 -93
- shotgun/cli/specify.py +0 -70
- shotgun/cli/tasks.py +0 -78
- shotgun/tui/screens/onboarding.py +0 -580
- shotgun_sh-0.2.29.dev2.dist-info/RECORD +0 -229
- {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.29.dev2.dist-info → shotgun_sh-0.6.1.dev1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Metrics exporters for saving benchmark results to files.
|
|
2
|
+
|
|
3
|
+
This module provides the MetricsExporter class for exporting benchmark
|
|
4
|
+
results to various file formats.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from shotgun.codebase.benchmarks.formatters import (
|
|
13
|
+
JsonFormatter,
|
|
14
|
+
MarkdownFormatter,
|
|
15
|
+
MetricsDisplayOptions,
|
|
16
|
+
)
|
|
17
|
+
from shotgun.logging_config import get_logger
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from shotgun.codebase.benchmarks.models import BenchmarkResults
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MetricsExporter:
|
|
26
|
+
"""Export benchmark metrics to files."""
|
|
27
|
+
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
"""Initialize metrics exporter."""
|
|
30
|
+
self._format_map = {
|
|
31
|
+
".json": self._export_json,
|
|
32
|
+
".md": self._export_markdown,
|
|
33
|
+
".markdown": self._export_markdown,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def export(
|
|
37
|
+
self,
|
|
38
|
+
results: BenchmarkResults,
|
|
39
|
+
filepath: Path | str,
|
|
40
|
+
format: str | None = None,
|
|
41
|
+
options: MetricsDisplayOptions | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Export benchmark results to file.
|
|
44
|
+
|
|
45
|
+
The format is auto-detected from the file extension if not specified.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
results: Benchmark results to export
|
|
49
|
+
filepath: Path to export file
|
|
50
|
+
format: Optional format override ("json", "markdown")
|
|
51
|
+
options: Display options for controlling what to include
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If format cannot be determined or is unsupported
|
|
55
|
+
OSError: If file cannot be written
|
|
56
|
+
"""
|
|
57
|
+
filepath = Path(filepath)
|
|
58
|
+
options = options or MetricsDisplayOptions()
|
|
59
|
+
|
|
60
|
+
# Determine format
|
|
61
|
+
if format:
|
|
62
|
+
format_lower = format.lower()
|
|
63
|
+
if format_lower == "json":
|
|
64
|
+
export_func = self._export_json
|
|
65
|
+
elif format_lower in ("markdown", "md"):
|
|
66
|
+
export_func = self._export_markdown
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError(f"Unknown export format: {format}")
|
|
69
|
+
else:
|
|
70
|
+
# Auto-detect from extension
|
|
71
|
+
suffix = filepath.suffix.lower()
|
|
72
|
+
if suffix not in self._format_map:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"Cannot determine format from extension '{suffix}'. "
|
|
75
|
+
f"Supported extensions: {', '.join(self._format_map.keys())}. "
|
|
76
|
+
f"Or specify format explicitly."
|
|
77
|
+
)
|
|
78
|
+
export_func = self._format_map[suffix]
|
|
79
|
+
|
|
80
|
+
# Ensure parent directory exists
|
|
81
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
|
|
83
|
+
# Export
|
|
84
|
+
export_func(results, filepath, options)
|
|
85
|
+
logger.info(f"Exported benchmark results to {filepath}")
|
|
86
|
+
|
|
87
|
+
def _export_json(
|
|
88
|
+
self,
|
|
89
|
+
results: BenchmarkResults,
|
|
90
|
+
filepath: Path,
|
|
91
|
+
options: MetricsDisplayOptions,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Export results to JSON file.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
results: Benchmark results
|
|
97
|
+
filepath: Output path
|
|
98
|
+
options: Display options
|
|
99
|
+
"""
|
|
100
|
+
formatter = JsonFormatter()
|
|
101
|
+
content = formatter.format_results(results, options)
|
|
102
|
+
filepath.write_text(content)
|
|
103
|
+
|
|
104
|
+
def _export_markdown(
|
|
105
|
+
self,
|
|
106
|
+
results: BenchmarkResults,
|
|
107
|
+
filepath: Path,
|
|
108
|
+
options: MetricsDisplayOptions,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Export results to Markdown file.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
results: Benchmark results
|
|
114
|
+
filepath: Output path
|
|
115
|
+
options: Display options
|
|
116
|
+
"""
|
|
117
|
+
formatter = MarkdownFormatter()
|
|
118
|
+
content = formatter.format_results(results, options)
|
|
119
|
+
filepath.write_text(content)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Result formatters for benchmark output.
|
|
2
|
+
|
|
3
|
+
This package provides formatters for displaying benchmark results in various
|
|
4
|
+
formats: JSON and Markdown.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from shotgun.codebase.benchmarks.formatters.base import ResultFormatter
|
|
8
|
+
from shotgun.codebase.benchmarks.formatters.json_formatter import JsonFormatter
|
|
9
|
+
from shotgun.codebase.benchmarks.formatters.markdown import MarkdownFormatter
|
|
10
|
+
|
|
11
|
+
# Re-export MetricsDisplayOptions from models for convenience
|
|
12
|
+
from shotgun.codebase.benchmarks.models import MetricsDisplayOptions
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"JsonFormatter",
|
|
16
|
+
"MarkdownFormatter",
|
|
17
|
+
"MetricsDisplayOptions",
|
|
18
|
+
"ResultFormatter",
|
|
19
|
+
"get_formatter",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_formatter(
|
|
24
|
+
output_format: str,
|
|
25
|
+
) -> JsonFormatter | MarkdownFormatter:
|
|
26
|
+
"""Get appropriate formatter for output format.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
output_format: Format name - "json" or "markdown"
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Formatter instance
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
ValueError: If output format is unknown
|
|
36
|
+
"""
|
|
37
|
+
formatters: dict[str, type[JsonFormatter | MarkdownFormatter]] = {
|
|
38
|
+
"json": JsonFormatter,
|
|
39
|
+
"markdown": MarkdownFormatter,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
format_lower = output_format.lower()
|
|
43
|
+
if format_lower not in formatters:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Unknown output format: {output_format}. "
|
|
46
|
+
f"Valid formats: {', '.join(formatters.keys())}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return formatters[format_lower]()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Base classes and protocols for formatters.
|
|
2
|
+
|
|
3
|
+
This module provides the Protocol interface that all formatters implement.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Protocol
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from shotgun.codebase.benchmarks.models import (
|
|
12
|
+
BenchmarkResults,
|
|
13
|
+
MetricsDisplayOptions,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ResultFormatter(Protocol):
|
|
18
|
+
"""Protocol for formatting benchmark results."""
|
|
19
|
+
|
|
20
|
+
def format_results(
|
|
21
|
+
self,
|
|
22
|
+
results: BenchmarkResults,
|
|
23
|
+
options: MetricsDisplayOptions,
|
|
24
|
+
) -> str:
|
|
25
|
+
"""Format benchmark results for display.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
results: Benchmark results to format
|
|
29
|
+
options: Display options
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Formatted string ready for output
|
|
33
|
+
"""
|
|
34
|
+
...
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""JSON formatter for benchmark results.
|
|
2
|
+
|
|
3
|
+
This module provides the JsonFormatter class for displaying benchmark results
|
|
4
|
+
as JSON.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from shotgun.codebase.benchmarks.models import (
|
|
14
|
+
BenchmarkResults,
|
|
15
|
+
MetricsDisplayOptions,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class JsonFormatter:
|
|
20
|
+
"""Format benchmark results as JSON."""
|
|
21
|
+
|
|
22
|
+
def format_results(
|
|
23
|
+
self,
|
|
24
|
+
results: BenchmarkResults,
|
|
25
|
+
options: MetricsDisplayOptions,
|
|
26
|
+
) -> str:
|
|
27
|
+
"""Format benchmark results as JSON.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
results: Benchmark results to format
|
|
31
|
+
options: Display options
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
JSON string
|
|
35
|
+
"""
|
|
36
|
+
data = {
|
|
37
|
+
"codebase_name": results.codebase_name,
|
|
38
|
+
"codebase_path": results.codebase_path,
|
|
39
|
+
"config": {
|
|
40
|
+
"mode": results.config.mode,
|
|
41
|
+
"worker_count": results.config.worker_count,
|
|
42
|
+
"iterations": results.config.iterations,
|
|
43
|
+
"warmup_iterations": results.config.warmup_iterations,
|
|
44
|
+
},
|
|
45
|
+
"statistics": {
|
|
46
|
+
"avg_duration_seconds": results.avg_duration_seconds,
|
|
47
|
+
"min_duration_seconds": results.min_duration_seconds,
|
|
48
|
+
"max_duration_seconds": results.max_duration_seconds,
|
|
49
|
+
"std_dev_seconds": results.std_dev_seconds,
|
|
50
|
+
"avg_throughput": results.avg_throughput,
|
|
51
|
+
"avg_memory_mb": results.avg_memory_mb,
|
|
52
|
+
"speedup_factor": results.speedup_factor,
|
|
53
|
+
"efficiency": results.efficiency,
|
|
54
|
+
},
|
|
55
|
+
"runs": [],
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Add run data
|
|
59
|
+
for run in results.measured_runs:
|
|
60
|
+
run_data: dict[str, object] = {
|
|
61
|
+
"run_id": run.run_id,
|
|
62
|
+
"duration_seconds": run.metrics.total_duration_seconds,
|
|
63
|
+
"total_files": run.metrics.total_files,
|
|
64
|
+
"total_nodes": run.metrics.total_nodes,
|
|
65
|
+
"total_relationships": run.metrics.total_relationships,
|
|
66
|
+
"throughput": run.metrics.avg_throughput,
|
|
67
|
+
"peak_memory_mb": run.metrics.peak_memory_mb,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Add phase metrics
|
|
71
|
+
if options.show_phase_metrics:
|
|
72
|
+
phase_data: dict[str, dict[str, object]] = {}
|
|
73
|
+
for name, phase in run.metrics.phase_metrics.items():
|
|
74
|
+
phase_data[name] = {
|
|
75
|
+
"duration_seconds": phase.duration_seconds,
|
|
76
|
+
"items_processed": phase.items_processed,
|
|
77
|
+
"throughput": phase.throughput,
|
|
78
|
+
"memory_mb": phase.memory_mb,
|
|
79
|
+
}
|
|
80
|
+
run_data["phase_metrics"] = phase_data
|
|
81
|
+
|
|
82
|
+
# Add file metrics
|
|
83
|
+
if options.show_file_metrics and run.metrics.file_metrics:
|
|
84
|
+
file_metrics_list = run.metrics.file_metrics
|
|
85
|
+
if options.top_n_files:
|
|
86
|
+
file_metrics_list = sorted(
|
|
87
|
+
file_metrics_list,
|
|
88
|
+
key=lambda f: f.parse_time_ms,
|
|
89
|
+
reverse=True,
|
|
90
|
+
)[: options.top_n_files]
|
|
91
|
+
|
|
92
|
+
run_data["file_metrics"] = [
|
|
93
|
+
{
|
|
94
|
+
"file_path": f.file_path,
|
|
95
|
+
"language": f.language,
|
|
96
|
+
"file_size_bytes": f.file_size_bytes,
|
|
97
|
+
"parse_time_ms": f.parse_time_ms,
|
|
98
|
+
"definitions_extracted": f.definitions_extracted,
|
|
99
|
+
}
|
|
100
|
+
for f in file_metrics_list
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
runs_list: list[dict[str, object]] = data["runs"] # type: ignore[assignment]
|
|
104
|
+
runs_list.append(run_data)
|
|
105
|
+
|
|
106
|
+
return json.dumps(data, indent=2)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Markdown formatter for benchmark results.
|
|
2
|
+
|
|
3
|
+
This module provides the MarkdownFormatter class for displaying benchmark results
|
|
4
|
+
as GitHub-compatible markdown.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from shotgun.codebase.benchmarks.models import (
|
|
13
|
+
BenchmarkResults,
|
|
14
|
+
MetricsDisplayOptions,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MarkdownFormatter:
|
|
19
|
+
"""Format benchmark results as GitHub-compatible markdown."""
|
|
20
|
+
|
|
21
|
+
def format_results(
|
|
22
|
+
self,
|
|
23
|
+
results: BenchmarkResults,
|
|
24
|
+
options: MetricsDisplayOptions,
|
|
25
|
+
) -> str:
|
|
26
|
+
"""Format benchmark results as markdown.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
results: Benchmark results to format
|
|
30
|
+
options: Display options
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Markdown string
|
|
34
|
+
"""
|
|
35
|
+
lines = []
|
|
36
|
+
|
|
37
|
+
# Header
|
|
38
|
+
lines.append(f"# Indexing Benchmark: {results.codebase_name}")
|
|
39
|
+
lines.append("")
|
|
40
|
+
lines.append(f"**Path:** `{results.codebase_path}`")
|
|
41
|
+
|
|
42
|
+
mode = results.config.mode.capitalize()
|
|
43
|
+
worker_info = ""
|
|
44
|
+
if results.config.mode == "parallel":
|
|
45
|
+
worker_count = results.config.worker_count or "auto"
|
|
46
|
+
worker_info = f" ({worker_count} workers)"
|
|
47
|
+
lines.append(f"**Mode:** {mode}{worker_info}")
|
|
48
|
+
lines.append(
|
|
49
|
+
f"**Iterations:** {results.config.iterations} ({results.config.warmup_iterations} warmup)"
|
|
50
|
+
)
|
|
51
|
+
lines.append("")
|
|
52
|
+
|
|
53
|
+
# Summary statistics
|
|
54
|
+
lines.append("## Summary")
|
|
55
|
+
lines.append("")
|
|
56
|
+
lines.append("| Metric | Value |")
|
|
57
|
+
lines.append("|--------|-------|")
|
|
58
|
+
|
|
59
|
+
if results.config.iterations > 1:
|
|
60
|
+
lines.append(f"| Duration (avg) | {results.avg_duration_seconds:.2f}s |")
|
|
61
|
+
lines.append(f"| Duration (min) | {results.min_duration_seconds:.2f}s |")
|
|
62
|
+
lines.append(f"| Duration (max) | {results.max_duration_seconds:.2f}s |")
|
|
63
|
+
lines.append(f"| Duration (std dev) | {results.std_dev_seconds:.2f}s |")
|
|
64
|
+
else:
|
|
65
|
+
lines.append(f"| Duration | {results.avg_duration_seconds:.2f}s |")
|
|
66
|
+
|
|
67
|
+
lines.append(f"| Throughput | {results.avg_throughput:.1f} files/s |")
|
|
68
|
+
lines.append(f"| Peak Memory | {results.avg_memory_mb:.1f} MB |")
|
|
69
|
+
|
|
70
|
+
metrics = results.get_last_metrics()
|
|
71
|
+
if metrics:
|
|
72
|
+
lines.append(f"| Files Processed | {metrics.total_files:,} |")
|
|
73
|
+
lines.append(f"| Nodes Created | {metrics.total_nodes:,} |")
|
|
74
|
+
lines.append(f"| Relationships | {metrics.total_relationships:,} |")
|
|
75
|
+
|
|
76
|
+
if results.efficiency:
|
|
77
|
+
lines.append(
|
|
78
|
+
f"| Parallelism Efficiency | {results.efficiency * 100:.0f}% |"
|
|
79
|
+
)
|
|
80
|
+
if results.speedup_factor:
|
|
81
|
+
lines.append(f"| Speedup | {results.speedup_factor:.2f}x |")
|
|
82
|
+
|
|
83
|
+
lines.append("")
|
|
84
|
+
|
|
85
|
+
# Phase breakdown
|
|
86
|
+
if metrics and options.show_phase_metrics and not options.show_summary_only:
|
|
87
|
+
lines.append("## Phase Breakdown")
|
|
88
|
+
lines.append("")
|
|
89
|
+
lines.append("| Phase | Duration | Items | Throughput | Memory |")
|
|
90
|
+
lines.append("|-------|----------|-------|------------|--------|")
|
|
91
|
+
|
|
92
|
+
for name, phase in metrics.phase_metrics.items():
|
|
93
|
+
lines.append(
|
|
94
|
+
f"| {name} | {phase.duration_seconds:.2f}s | "
|
|
95
|
+
f"{phase.items_processed} | {phase.throughput:.1f}/s | "
|
|
96
|
+
f"{phase.memory_mb:.1f} MB |"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
lines.append("")
|
|
100
|
+
|
|
101
|
+
# File metrics
|
|
102
|
+
if (
|
|
103
|
+
metrics
|
|
104
|
+
and options.show_file_metrics
|
|
105
|
+
and metrics.file_metrics
|
|
106
|
+
and not options.show_summary_only
|
|
107
|
+
):
|
|
108
|
+
file_metrics = sorted(
|
|
109
|
+
metrics.file_metrics,
|
|
110
|
+
key=lambda f: f.parse_time_ms,
|
|
111
|
+
reverse=True,
|
|
112
|
+
)
|
|
113
|
+
if options.top_n_files:
|
|
114
|
+
file_metrics = file_metrics[: options.top_n_files]
|
|
115
|
+
|
|
116
|
+
if file_metrics:
|
|
117
|
+
title = "File Metrics"
|
|
118
|
+
if options.top_n_files:
|
|
119
|
+
title = f"Top {len(file_metrics)} Slowest Files"
|
|
120
|
+
|
|
121
|
+
lines.append(f"## {title}")
|
|
122
|
+
lines.append("")
|
|
123
|
+
lines.append("| File | Language | Size | Duration | Definitions |")
|
|
124
|
+
lines.append("|------|----------|------|----------|-------------|")
|
|
125
|
+
|
|
126
|
+
for f in file_metrics:
|
|
127
|
+
size_kb = f.file_size_bytes / 1024
|
|
128
|
+
lines.append(
|
|
129
|
+
f"| `{f.file_path}` | {f.language} | "
|
|
130
|
+
f"{size_kb:.1f} KB | {f.parse_time_ms:.1f}ms | "
|
|
131
|
+
f"{f.definitions_extracted} |"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
lines.append("")
|
|
135
|
+
|
|
136
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Pydantic models for benchmark system.
|
|
2
|
+
|
|
3
|
+
This module contains all data models used by the benchmark system.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import statistics
|
|
9
|
+
from enum import StrEnum
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from shotgun.codebase.core.metrics_types import IndexingMetrics
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BenchmarkMode(StrEnum):
|
|
17
|
+
"""Execution mode for benchmarks."""
|
|
18
|
+
|
|
19
|
+
PARALLEL = "parallel"
|
|
20
|
+
SEQUENTIAL = "sequential"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OutputFormat(StrEnum):
|
|
24
|
+
"""Output format for benchmark results."""
|
|
25
|
+
|
|
26
|
+
JSON = "json"
|
|
27
|
+
MARKDOWN = "markdown"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BenchmarkConfig(BaseModel):
|
|
31
|
+
"""Configuration for benchmark execution."""
|
|
32
|
+
|
|
33
|
+
mode: BenchmarkMode = BenchmarkMode.PARALLEL
|
|
34
|
+
worker_count: int | None = None
|
|
35
|
+
iterations: int = 1
|
|
36
|
+
warmup_iterations: int = 0
|
|
37
|
+
collect_file_metrics: bool = True
|
|
38
|
+
collect_worker_metrics: bool = True
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BenchmarkRun(BaseModel):
|
|
42
|
+
"""Results from a single benchmark run."""
|
|
43
|
+
|
|
44
|
+
run_id: int
|
|
45
|
+
is_warmup: bool
|
|
46
|
+
metrics: IndexingMetrics
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BenchmarkResults(BaseModel):
|
|
50
|
+
"""Complete results from benchmark execution."""
|
|
51
|
+
|
|
52
|
+
codebase_name: str
|
|
53
|
+
codebase_path: str
|
|
54
|
+
config: BenchmarkConfig
|
|
55
|
+
warmup_runs: list[BenchmarkRun] = Field(default_factory=list)
|
|
56
|
+
measured_runs: list[BenchmarkRun] = Field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
# Aggregate statistics (calculated after runs)
|
|
59
|
+
avg_duration_seconds: float = 0.0
|
|
60
|
+
min_duration_seconds: float = 0.0
|
|
61
|
+
max_duration_seconds: float = 0.0
|
|
62
|
+
std_dev_seconds: float = 0.0
|
|
63
|
+
avg_throughput: float = 0.0
|
|
64
|
+
avg_memory_mb: float = 0.0
|
|
65
|
+
|
|
66
|
+
# Comparison data
|
|
67
|
+
baseline_duration: float | None = None
|
|
68
|
+
speedup_factor: float | None = None
|
|
69
|
+
efficiency: float | None = None
|
|
70
|
+
|
|
71
|
+
def add_run(self, run: BenchmarkRun) -> None:
|
|
72
|
+
"""Add a benchmark run to results.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
run: Benchmark run to add
|
|
76
|
+
"""
|
|
77
|
+
if run.is_warmup:
|
|
78
|
+
self.warmup_runs.append(run)
|
|
79
|
+
else:
|
|
80
|
+
self.measured_runs.append(run)
|
|
81
|
+
|
|
82
|
+
def calculate_statistics(self) -> None:
|
|
83
|
+
"""Calculate aggregate statistics from measured runs."""
|
|
84
|
+
if not self.measured_runs:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
durations = [r.metrics.total_duration_seconds for r in self.measured_runs]
|
|
88
|
+
throughputs = [r.metrics.avg_throughput for r in self.measured_runs]
|
|
89
|
+
memories = [r.metrics.peak_memory_mb for r in self.measured_runs]
|
|
90
|
+
|
|
91
|
+
self.avg_duration_seconds = statistics.mean(durations)
|
|
92
|
+
self.min_duration_seconds = min(durations)
|
|
93
|
+
self.max_duration_seconds = max(durations)
|
|
94
|
+
self.std_dev_seconds = (
|
|
95
|
+
statistics.stdev(durations) if len(durations) > 1 else 0.0
|
|
96
|
+
)
|
|
97
|
+
self.avg_throughput = statistics.mean(throughputs)
|
|
98
|
+
self.avg_memory_mb = statistics.mean(memories)
|
|
99
|
+
|
|
100
|
+
# Calculate efficiency if parallel mode with known worker count
|
|
101
|
+
if (
|
|
102
|
+
self.config.mode == BenchmarkMode.PARALLEL
|
|
103
|
+
and self.config.worker_count
|
|
104
|
+
and self.baseline_duration
|
|
105
|
+
):
|
|
106
|
+
speedup = self.baseline_duration / self.avg_duration_seconds
|
|
107
|
+
self.speedup_factor = speedup
|
|
108
|
+
self.efficiency = speedup / self.config.worker_count
|
|
109
|
+
|
|
110
|
+
def get_last_metrics(self) -> IndexingMetrics | None:
|
|
111
|
+
"""Get metrics from the last measured run.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
IndexingMetrics from last run, or None if no runs
|
|
115
|
+
"""
|
|
116
|
+
if self.measured_runs:
|
|
117
|
+
return self.measured_runs[-1].metrics
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class MetricsDisplayOptions(BaseModel):
|
|
122
|
+
"""Options for controlling metrics display."""
|
|
123
|
+
|
|
124
|
+
show_phase_metrics: bool = True
|
|
125
|
+
show_worker_metrics: bool = False
|
|
126
|
+
show_file_metrics: bool = False
|
|
127
|
+
show_summary_only: bool = False
|
|
128
|
+
top_n_files: int | None = None
|
|
129
|
+
min_file_duration_ms: float | None = None
|
|
@@ -5,6 +5,7 @@ from shotgun.codebase.core.code_retrieval import (
|
|
|
5
5
|
retrieve_code_by_cypher,
|
|
6
6
|
retrieve_code_by_qualified_name,
|
|
7
7
|
)
|
|
8
|
+
from shotgun.codebase.core.gitignore import GitignoreManager, load_gitignore_for_repo
|
|
8
9
|
from shotgun.codebase.core.ingestor import (
|
|
9
10
|
CodebaseIngestor,
|
|
10
11
|
Ingestor,
|
|
@@ -29,6 +30,9 @@ __all__ = [
|
|
|
29
30
|
"Ingestor",
|
|
30
31
|
"SimpleGraphBuilder",
|
|
31
32
|
"CodebaseGraphManager",
|
|
33
|
+
# Gitignore support
|
|
34
|
+
"GitignoreManager",
|
|
35
|
+
"load_gitignore_for_repo",
|
|
32
36
|
# Language configuration
|
|
33
37
|
"LanguageConfig",
|
|
34
38
|
"LANGUAGE_CONFIGS",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Call resolution utilities for function/method call graph building.
|
|
2
|
+
|
|
3
|
+
This module provides shared utilities for resolving function calls
|
|
4
|
+
and calculating confidence scores for potential callee matches.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Collection, Mapping
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def calculate_callee_confidence(
|
|
13
|
+
caller_qn: str,
|
|
14
|
+
callee_qn: str,
|
|
15
|
+
module_qn: str,
|
|
16
|
+
object_name: str | None,
|
|
17
|
+
simple_name_lookup: Mapping[str, Collection[str]],
|
|
18
|
+
) -> float:
|
|
19
|
+
"""Calculate confidence score for a potential callee match.
|
|
20
|
+
|
|
21
|
+
Uses multiple heuristics to determine how likely a given callee
|
|
22
|
+
is the correct target of a function call:
|
|
23
|
+
1. Module locality - functions in the same module are most likely
|
|
24
|
+
2. Package locality - functions in the same package hierarchy
|
|
25
|
+
3. Object/class match for method calls
|
|
26
|
+
4. Standard library boost
|
|
27
|
+
5. Name uniqueness boost
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
caller_qn: Qualified name of the calling function
|
|
31
|
+
callee_qn: Qualified name of the potential callee
|
|
32
|
+
module_qn: Qualified name of the current module
|
|
33
|
+
object_name: Object name for method calls (e.g., 'obj' in obj.method())
|
|
34
|
+
simple_name_lookup: Mapping from simple names to qualified names
|
|
35
|
+
(supports both set[str] and list[str] values)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Confidence score between 0.0 and 1.0
|
|
39
|
+
"""
|
|
40
|
+
score = 0.0
|
|
41
|
+
|
|
42
|
+
# 1. Module locality - functions in the same module are most likely
|
|
43
|
+
if callee_qn.startswith(module_qn + "."):
|
|
44
|
+
score += 0.5
|
|
45
|
+
|
|
46
|
+
# Even higher if in the same class
|
|
47
|
+
caller_parts = caller_qn.split(".")
|
|
48
|
+
callee_parts = callee_qn.split(".")
|
|
49
|
+
if len(caller_parts) >= 3 and len(callee_parts) >= 3:
|
|
50
|
+
if caller_parts[:-1] == callee_parts[:-1]: # Same class
|
|
51
|
+
score += 0.2
|
|
52
|
+
|
|
53
|
+
# 2. Package locality - functions in the same package hierarchy
|
|
54
|
+
elif "." in module_qn:
|
|
55
|
+
package = module_qn.rsplit(".", 1)[0]
|
|
56
|
+
if callee_qn.startswith(package + "."):
|
|
57
|
+
score += 0.3
|
|
58
|
+
|
|
59
|
+
# 3. Object/class match for method calls
|
|
60
|
+
if object_name:
|
|
61
|
+
# Check if callee is a method of a class matching the object name
|
|
62
|
+
callee_parts = callee_qn.split(".")
|
|
63
|
+
if len(callee_parts) >= 2:
|
|
64
|
+
# Simple heuristic: check if class name matches object name
|
|
65
|
+
# (In reality, we'd need type inference for accuracy)
|
|
66
|
+
class_name = callee_parts[-2]
|
|
67
|
+
if class_name.lower() == object_name.lower():
|
|
68
|
+
score += 0.3
|
|
69
|
+
elif object_name == "self" and callee_qn.startswith(
|
|
70
|
+
caller_qn.rsplit(".", 1)[0]
|
|
71
|
+
):
|
|
72
|
+
# 'self' refers to the same class
|
|
73
|
+
score += 0.4
|
|
74
|
+
|
|
75
|
+
# 4. Standard library boost
|
|
76
|
+
# Give a small boost to standard library functions
|
|
77
|
+
if callee_qn.startswith(("builtins.", "typing.", "collections.")):
|
|
78
|
+
score += 0.1
|
|
79
|
+
|
|
80
|
+
# 5. Name uniqueness boost
|
|
81
|
+
# If function names are unique enough, boost confidence
|
|
82
|
+
callee_simple_name = callee_qn.split(".")[-1]
|
|
83
|
+
possible_matches = simple_name_lookup.get(callee_simple_name, [])
|
|
84
|
+
possible_count = len(possible_matches)
|
|
85
|
+
if possible_count == 1:
|
|
86
|
+
score += 0.2
|
|
87
|
+
elif possible_count <= 3:
|
|
88
|
+
score += 0.1
|
|
89
|
+
|
|
90
|
+
# Normalize to [0, 1]
|
|
91
|
+
return min(score, 1.0)
|