shotgun-sh 0.1.0.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/__init__.py +3 -0
- shotgun/agents/__init__.py +1 -0
- shotgun/agents/agent_manager.py +196 -0
- shotgun/agents/common.py +295 -0
- shotgun/agents/config/__init__.py +13 -0
- shotgun/agents/config/manager.py +215 -0
- shotgun/agents/config/models.py +120 -0
- shotgun/agents/config/provider.py +91 -0
- shotgun/agents/history/__init__.py +5 -0
- shotgun/agents/history/history_processors.py +213 -0
- shotgun/agents/models.py +94 -0
- shotgun/agents/plan.py +119 -0
- shotgun/agents/research.py +131 -0
- shotgun/agents/tasks.py +122 -0
- shotgun/agents/tools/__init__.py +26 -0
- shotgun/agents/tools/codebase/__init__.py +28 -0
- shotgun/agents/tools/codebase/codebase_shell.py +256 -0
- shotgun/agents/tools/codebase/directory_lister.py +141 -0
- shotgun/agents/tools/codebase/file_read.py +144 -0
- shotgun/agents/tools/codebase/models.py +252 -0
- shotgun/agents/tools/codebase/query_graph.py +67 -0
- shotgun/agents/tools/codebase/retrieve_code.py +81 -0
- shotgun/agents/tools/file_management.py +130 -0
- shotgun/agents/tools/user_interaction.py +36 -0
- shotgun/agents/tools/web_search.py +69 -0
- shotgun/cli/__init__.py +1 -0
- shotgun/cli/codebase/__init__.py +5 -0
- shotgun/cli/codebase/commands.py +202 -0
- shotgun/cli/codebase/models.py +21 -0
- shotgun/cli/config.py +261 -0
- shotgun/cli/models.py +10 -0
- shotgun/cli/plan.py +65 -0
- shotgun/cli/research.py +78 -0
- shotgun/cli/tasks.py +71 -0
- shotgun/cli/utils.py +25 -0
- shotgun/codebase/__init__.py +12 -0
- shotgun/codebase/core/__init__.py +46 -0
- shotgun/codebase/core/change_detector.py +358 -0
- shotgun/codebase/core/code_retrieval.py +243 -0
- shotgun/codebase/core/ingestor.py +1497 -0
- shotgun/codebase/core/language_config.py +297 -0
- shotgun/codebase/core/manager.py +1554 -0
- shotgun/codebase/core/nl_query.py +327 -0
- shotgun/codebase/core/parser_loader.py +152 -0
- shotgun/codebase/models.py +107 -0
- shotgun/codebase/service.py +148 -0
- shotgun/logging_config.py +172 -0
- shotgun/main.py +73 -0
- shotgun/prompts/__init__.py +5 -0
- shotgun/prompts/agents/__init__.py +1 -0
- shotgun/prompts/agents/partials/codebase_understanding.j2 +79 -0
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +10 -0
- shotgun/prompts/agents/partials/interactive_mode.j2 +8 -0
- shotgun/prompts/agents/plan.j2 +57 -0
- shotgun/prompts/agents/research.j2 +38 -0
- shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +13 -0
- shotgun/prompts/agents/state/system_state.j2 +1 -0
- shotgun/prompts/agents/tasks.j2 +67 -0
- shotgun/prompts/codebase/__init__.py +1 -0
- shotgun/prompts/codebase/cypher_query_patterns.j2 +221 -0
- shotgun/prompts/codebase/cypher_system.j2 +28 -0
- shotgun/prompts/codebase/enhanced_query_context.j2 +10 -0
- shotgun/prompts/codebase/partials/cypher_rules.j2 +24 -0
- shotgun/prompts/codebase/partials/graph_schema.j2 +28 -0
- shotgun/prompts/codebase/partials/temporal_context.j2 +21 -0
- shotgun/prompts/history/__init__.py +1 -0
- shotgun/prompts/history/summarization.j2 +46 -0
- shotgun/prompts/loader.py +140 -0
- shotgun/prompts/user/research.j2 +5 -0
- shotgun/py.typed +0 -0
- shotgun/sdk/__init__.py +13 -0
- shotgun/sdk/codebase.py +195 -0
- shotgun/sdk/exceptions.py +17 -0
- shotgun/sdk/models.py +189 -0
- shotgun/sdk/services.py +23 -0
- shotgun/telemetry.py +68 -0
- shotgun/tui/__init__.py +0 -0
- shotgun/tui/app.py +49 -0
- shotgun/tui/components/prompt_input.py +69 -0
- shotgun/tui/components/spinner.py +86 -0
- shotgun/tui/components/splash.py +25 -0
- shotgun/tui/components/vertical_tail.py +28 -0
- shotgun/tui/screens/chat.py +415 -0
- shotgun/tui/screens/chat.tcss +28 -0
- shotgun/tui/screens/provider_config.py +221 -0
- shotgun/tui/screens/splash.py +31 -0
- shotgun/tui/styles.tcss +10 -0
- shotgun/utils/__init__.py +5 -0
- shotgun/utils/file_system_utils.py +31 -0
- shotgun_sh-0.1.0.dev1.dist-info/METADATA +318 -0
- shotgun_sh-0.1.0.dev1.dist-info/RECORD +94 -0
- shotgun_sh-0.1.0.dev1.dist-info/WHEEL +4 -0
- shotgun_sh-0.1.0.dev1.dist-info/entry_points.txt +3 -0
- shotgun_sh-0.1.0.dev1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""High-level service for managing codebase graphs and executing queries."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from shotgun.codebase.core.manager import CodebaseGraphManager
|
|
8
|
+
from shotgun.codebase.core.nl_query import generate_cypher
|
|
9
|
+
from shotgun.codebase.models import CodebaseGraph, QueryResult, QueryType
|
|
10
|
+
from shotgun.logging_config import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CodebaseService:
|
|
16
|
+
"""High-level service for codebase graph management and querying."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, storage_dir: Path | str):
|
|
19
|
+
"""Initialize the service.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
storage_dir: Directory to store graph databases
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(storage_dir, str):
|
|
25
|
+
storage_dir = Path(storage_dir)
|
|
26
|
+
|
|
27
|
+
self.storage_dir = storage_dir
|
|
28
|
+
self.storage_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
self.manager = CodebaseGraphManager(storage_dir)
|
|
30
|
+
|
|
31
|
+
async def list_graphs(self) -> list[CodebaseGraph]:
|
|
32
|
+
"""List all existing graphs.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List of CodebaseGraph objects
|
|
36
|
+
"""
|
|
37
|
+
return await self.manager.list_graphs()
|
|
38
|
+
|
|
39
|
+
async def create_graph(self, repo_path: str | Path, name: str) -> CodebaseGraph:
|
|
40
|
+
"""Create and index a new graph from a repository.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
repo_path: Path to the repository to index
|
|
44
|
+
name: Human-readable name for the graph
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The created CodebaseGraph
|
|
48
|
+
"""
|
|
49
|
+
return await self.manager.build_graph(str(repo_path), name)
|
|
50
|
+
|
|
51
|
+
async def get_graph(self, graph_id: str) -> CodebaseGraph | None:
|
|
52
|
+
"""Get graph metadata by ID.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
graph_id: Graph ID to retrieve
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
CodebaseGraph object or None if not found
|
|
59
|
+
"""
|
|
60
|
+
return await self.manager.get_graph(graph_id)
|
|
61
|
+
|
|
62
|
+
async def delete_graph(self, graph_id: str) -> None:
|
|
63
|
+
"""Delete a graph and its data.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
graph_id: Graph ID to delete
|
|
67
|
+
"""
|
|
68
|
+
await self.manager.delete_graph(graph_id)
|
|
69
|
+
|
|
70
|
+
async def reindex_graph(self, graph_id: str) -> dict[str, Any]:
|
|
71
|
+
"""Rebuild an existing graph (full reindex).
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
graph_id: Graph ID to reindex
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Statistics from the reindex operation
|
|
78
|
+
"""
|
|
79
|
+
return await self.manager.update_graph_incremental(graph_id)
|
|
80
|
+
|
|
81
|
+
async def execute_query(
|
|
82
|
+
self,
|
|
83
|
+
graph_id: str,
|
|
84
|
+
query: str,
|
|
85
|
+
query_type: QueryType,
|
|
86
|
+
parameters: dict[str, Any] | None = None,
|
|
87
|
+
) -> QueryResult:
|
|
88
|
+
"""Execute a query against a graph.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
graph_id: Graph ID to query
|
|
92
|
+
query: The query (natural language or Cypher)
|
|
93
|
+
query_type: Type of query being executed
|
|
94
|
+
parameters: Optional parameters for Cypher queries
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
QueryResult with results and metadata
|
|
98
|
+
"""
|
|
99
|
+
start_time = time.time()
|
|
100
|
+
cypher_query = None
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Handle query type conversion
|
|
104
|
+
if query_type == QueryType.NATURAL_LANGUAGE:
|
|
105
|
+
logger.info(f"Converting natural language query to Cypher: {query}")
|
|
106
|
+
cypher_query = await generate_cypher(query)
|
|
107
|
+
logger.info(f"Generated Cypher: {cypher_query}")
|
|
108
|
+
execute_query = cypher_query
|
|
109
|
+
else:
|
|
110
|
+
execute_query = query
|
|
111
|
+
|
|
112
|
+
# Execute the query
|
|
113
|
+
results = await self.manager.execute_query(
|
|
114
|
+
graph_id=graph_id, query=execute_query, parameters=parameters
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Extract column names from first result
|
|
118
|
+
column_names = list(results[0].keys()) if results else []
|
|
119
|
+
|
|
120
|
+
execution_time = (time.time() - start_time) * 1000
|
|
121
|
+
|
|
122
|
+
return QueryResult(
|
|
123
|
+
query=query,
|
|
124
|
+
cypher_query=cypher_query
|
|
125
|
+
if query_type == QueryType.NATURAL_LANGUAGE
|
|
126
|
+
else None,
|
|
127
|
+
results=results,
|
|
128
|
+
column_names=column_names,
|
|
129
|
+
row_count=len(results),
|
|
130
|
+
execution_time_ms=execution_time,
|
|
131
|
+
success=True,
|
|
132
|
+
error=None,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
execution_time = (time.time() - start_time) * 1000
|
|
137
|
+
logger.error(f"Query execution failed: {e}")
|
|
138
|
+
|
|
139
|
+
return QueryResult(
|
|
140
|
+
query=query,
|
|
141
|
+
cypher_query=cypher_query,
|
|
142
|
+
results=[],
|
|
143
|
+
column_names=[],
|
|
144
|
+
row_count=0,
|
|
145
|
+
execution_time_ms=execution_time,
|
|
146
|
+
success=False,
|
|
147
|
+
error=str(e),
|
|
148
|
+
)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Centralized logging configuration for Shotgun CLI."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import logging.handlers
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_log_directory() -> Path:
|
|
11
|
+
"""Get the log directory path, creating it if necessary.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Path to log directory (~/.shotgun-sh/logs/)
|
|
15
|
+
"""
|
|
16
|
+
# Lazy import to avoid circular dependency
|
|
17
|
+
from shotgun.utils.file_system_utils import get_shotgun_home
|
|
18
|
+
|
|
19
|
+
log_dir = get_shotgun_home() / "logs"
|
|
20
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
return log_dir
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ColoredFormatter(logging.Formatter):
|
|
25
|
+
"""Custom formatter with colors for different log levels."""
|
|
26
|
+
|
|
27
|
+
# ANSI color codes
|
|
28
|
+
COLORS = {
|
|
29
|
+
"DEBUG": "\033[36m", # Cyan
|
|
30
|
+
"INFO": "\033[32m", # Green
|
|
31
|
+
"WARNING": "\033[33m", # Yellow
|
|
32
|
+
"ERROR": "\033[31m", # Red
|
|
33
|
+
"CRITICAL": "\033[35m", # Magenta
|
|
34
|
+
}
|
|
35
|
+
RESET = "\033[0m"
|
|
36
|
+
|
|
37
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
38
|
+
# Create a copy of the record to avoid modifying the original
|
|
39
|
+
record = logging.makeLogRecord(record.__dict__)
|
|
40
|
+
|
|
41
|
+
# Add color to levelname
|
|
42
|
+
if record.levelname in self.COLORS:
|
|
43
|
+
colored_levelname = (
|
|
44
|
+
f"{self.COLORS[record.levelname]}{record.levelname}{self.RESET}"
|
|
45
|
+
)
|
|
46
|
+
record.levelname = colored_levelname
|
|
47
|
+
|
|
48
|
+
return super().format(record)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def setup_logger(
|
|
52
|
+
name: str,
|
|
53
|
+
format_string: str | None = None,
|
|
54
|
+
) -> logging.Logger:
|
|
55
|
+
"""Set up a logger with consistent configuration.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name: Logger name (typically __name__)
|
|
59
|
+
format_string: Custom format string, uses default if None
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Configured logger instance
|
|
63
|
+
"""
|
|
64
|
+
logger = logging.getLogger(name)
|
|
65
|
+
|
|
66
|
+
# Avoid adding duplicate handlers
|
|
67
|
+
if logger.handlers:
|
|
68
|
+
return logger
|
|
69
|
+
|
|
70
|
+
# Get log level from environment variable, default to ERROR
|
|
71
|
+
env_level = os.getenv("SHOTGUN_LOG_LEVEL", "ERROR").upper()
|
|
72
|
+
if env_level not in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
|
|
73
|
+
env_level = "ERROR"
|
|
74
|
+
|
|
75
|
+
logger.setLevel(getattr(logging, env_level))
|
|
76
|
+
|
|
77
|
+
# Default format string
|
|
78
|
+
if format_string is None:
|
|
79
|
+
format_string = "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s"
|
|
80
|
+
|
|
81
|
+
# Check if console logging is enabled (default: off)
|
|
82
|
+
console_logging_enabled = os.getenv("LOGGING_TO_CONSOLE", "false").lower() == "true"
|
|
83
|
+
|
|
84
|
+
if console_logging_enabled:
|
|
85
|
+
# Create console handler
|
|
86
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
87
|
+
console_handler.setLevel(getattr(logging, env_level))
|
|
88
|
+
|
|
89
|
+
# Use colored formatter for console
|
|
90
|
+
console_formatter = ColoredFormatter(format_string, datefmt="%H:%M:%S")
|
|
91
|
+
console_handler.setFormatter(console_formatter)
|
|
92
|
+
|
|
93
|
+
# Add handler to logger
|
|
94
|
+
logger.addHandler(console_handler)
|
|
95
|
+
|
|
96
|
+
# Check if file logging is enabled (default: on)
|
|
97
|
+
file_logging_enabled = os.getenv("LOGGING_TO_FILE", "true").lower() == "true"
|
|
98
|
+
|
|
99
|
+
if file_logging_enabled:
|
|
100
|
+
try:
|
|
101
|
+
# Create file handler with rotation
|
|
102
|
+
log_dir = get_log_directory()
|
|
103
|
+
log_file = log_dir / "shotgun.log"
|
|
104
|
+
|
|
105
|
+
# Use TimedRotatingFileHandler - rotates daily and keeps 7 days of logs
|
|
106
|
+
file_handler = logging.handlers.TimedRotatingFileHandler(
|
|
107
|
+
filename=log_file,
|
|
108
|
+
when="midnight", # Rotate at midnight
|
|
109
|
+
interval=1, # Every 1 day
|
|
110
|
+
backupCount=7, # Keep 7 days of logs
|
|
111
|
+
encoding="utf-8",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Also set max file size (10MB) using RotatingFileHandler as fallback
|
|
115
|
+
# Note: We'll use TimedRotatingFileHandler which handles both time and size
|
|
116
|
+
file_handler.setLevel(getattr(logging, env_level))
|
|
117
|
+
|
|
118
|
+
# Use standard formatter for file (no colors)
|
|
119
|
+
file_formatter = logging.Formatter(
|
|
120
|
+
format_string, datefmt="%Y-%m-%d %H:%M:%S"
|
|
121
|
+
)
|
|
122
|
+
file_handler.setFormatter(file_formatter)
|
|
123
|
+
|
|
124
|
+
# Add handler to logger
|
|
125
|
+
logger.addHandler(file_handler)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
# If file logging fails, log to stderr but don't crash
|
|
128
|
+
print(f"Warning: Could not set up file logging: {e}", file=sys.stderr)
|
|
129
|
+
|
|
130
|
+
# Prevent propagation to avoid duplicate messages from parent loggers
|
|
131
|
+
if name != "shotgun": # Keep propagation for root logger
|
|
132
|
+
logger.propagate = False
|
|
133
|
+
|
|
134
|
+
return logger
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_logger(name: str) -> logging.Logger:
|
|
138
|
+
"""Get a logger instance with default configuration.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
name: Logger name (typically __name__)
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Logger instance with handlers configured
|
|
145
|
+
"""
|
|
146
|
+
logger = logging.getLogger(name)
|
|
147
|
+
|
|
148
|
+
# If logger doesn't have handlers, set it up
|
|
149
|
+
if not logger.handlers:
|
|
150
|
+
return setup_logger(name)
|
|
151
|
+
|
|
152
|
+
return logger
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def set_global_log_level(level: str) -> None:
|
|
156
|
+
"""Set log level for all shotgun loggers.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
level: Log level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
|
|
160
|
+
"""
|
|
161
|
+
# Set level for all existing shotgun loggers
|
|
162
|
+
for name, logger in logging.getLogger().manager.loggerDict.items():
|
|
163
|
+
if isinstance(logger, logging.Logger) and name.startswith("shotgun"):
|
|
164
|
+
logger.setLevel(getattr(logging, level.upper()))
|
|
165
|
+
# Only set handler levels if handlers exist
|
|
166
|
+
for handler in logger.handlers:
|
|
167
|
+
handler.setLevel(getattr(logging, level.upper()))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def configure_root_logger() -> None:
|
|
171
|
+
"""Configure the root shotgun logger."""
|
|
172
|
+
setup_logger("shotgun")
|
shotgun/main.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Main CLI application for shotgun."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
from shotgun.agents.config import get_config_manager
|
|
9
|
+
from shotgun.cli import codebase, config, plan, research, tasks
|
|
10
|
+
from shotgun.logging_config import configure_root_logger, get_logger
|
|
11
|
+
from shotgun.telemetry import setup_phoenix_observability
|
|
12
|
+
|
|
13
|
+
# Load environment variables from .env file
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
# Initialize logging
|
|
17
|
+
configure_root_logger()
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
# Initialize configuration
|
|
21
|
+
try:
|
|
22
|
+
config_manager = get_config_manager()
|
|
23
|
+
config_manager.load() # Ensure config is loaded at startup
|
|
24
|
+
except Exception as e:
|
|
25
|
+
logger.debug("Configuration initialization warning: %s", e)
|
|
26
|
+
|
|
27
|
+
# Initialize telemetry
|
|
28
|
+
_telemetry_enabled = setup_phoenix_observability()
|
|
29
|
+
logger.debug("Phoenix observability enabled: %s", _telemetry_enabled)
|
|
30
|
+
|
|
31
|
+
app = typer.Typer(
|
|
32
|
+
name="shotgun",
|
|
33
|
+
help="Shotgun - AI-powered CLI tool for research, planning, and task management",
|
|
34
|
+
no_args_is_help=True,
|
|
35
|
+
rich_markup_mode="rich",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Add commands
|
|
39
|
+
app.add_typer(config.app, name="config", help="Manage Shotgun configuration")
|
|
40
|
+
app.add_typer(
|
|
41
|
+
codebase.app, name="codebase", help="Manage and query code knowledge graphs"
|
|
42
|
+
)
|
|
43
|
+
app.add_typer(research.app, name="research", help="Perform research with agentic loops")
|
|
44
|
+
app.add_typer(plan.app, name="plan", help="Generate structured plans")
|
|
45
|
+
app.add_typer(tasks.app, name="tasks", help="Generate task lists with agentic approach")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def version_callback(value: bool) -> None:
|
|
49
|
+
"""Show version and exit."""
|
|
50
|
+
if value:
|
|
51
|
+
logger.info("shotgun 0.1.0")
|
|
52
|
+
raise typer.Exit()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.callback()
|
|
56
|
+
def main(
|
|
57
|
+
version: Annotated[
|
|
58
|
+
bool,
|
|
59
|
+
typer.Option(
|
|
60
|
+
"--version",
|
|
61
|
+
"-v",
|
|
62
|
+
callback=version_callback,
|
|
63
|
+
is_eager=True,
|
|
64
|
+
help="Show version and exit",
|
|
65
|
+
),
|
|
66
|
+
] = False,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Shotgun - AI-powered CLI tool."""
|
|
69
|
+
logger.debug("Starting shotgun CLI application")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent-specific prompt templates."""
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{% if codebase_understanding_graphs -%}
|
|
2
|
+
**Graph Tools Available**:
|
|
3
|
+
- `query_graph` - Query this graph using natural language
|
|
4
|
+
- `retrieve_code` - Get source code by qualified name (e.g., "module.Class.method")
|
|
5
|
+
- `codebase_shell` - Run shell commands in the codebase context
|
|
6
|
+
- `directory_lister` - List directory contents in the codebase
|
|
7
|
+
- `file_read` - Read file contents in the codebase
|
|
8
|
+
|
|
9
|
+
## Using codebases
|
|
10
|
+
|
|
11
|
+
When you have a codebase available, assume the user is asking you about that codebase or in context of that codebase.
|
|
12
|
+
Make sure to check the codebase before answering any question or suggesting any action.
|
|
13
|
+
|
|
14
|
+
## Codebase Analysis Guidelines
|
|
15
|
+
|
|
16
|
+
1. **Use existing graphs** when analyzing code structure, dependencies, or relationships
|
|
17
|
+
2. **Query graphs** using natural language with the `query_graph` tool - ask questions like:
|
|
18
|
+
- "Find all classes that inherit from BaseModel"
|
|
19
|
+
- "Show functions with the most dependencies"
|
|
20
|
+
- "What are the main entry points in this codebase?"
|
|
21
|
+
3. **Retrieve specific code** using `retrieve_code` with fully qualified names (module.Class.method)
|
|
22
|
+
|
|
23
|
+
## Code Graph Query Examples
|
|
24
|
+
|
|
25
|
+
**Natural Language Queries**:
|
|
26
|
+
- "Find all functions that call each other"
|
|
27
|
+
- "What classes are in the user authentication module?"
|
|
28
|
+
- "Show me the dependencies of the main application class"
|
|
29
|
+
- "List all test functions that are not being called"
|
|
30
|
+
- "Find functions with high cyclomatic complexity"
|
|
31
|
+
|
|
32
|
+
**Graph Management**:
|
|
33
|
+
- Use graph IDs shown above when calling codebase tools
|
|
34
|
+
- Each session can have multiple graphs for different repositories
|
|
35
|
+
- Graphs persist across sessions and are shared between users working on the same repository
|
|
36
|
+
|
|
37
|
+
## Codebase Tools and Management
|
|
38
|
+
|
|
39
|
+
Important:
|
|
40
|
+
- From a technical point of view, the codebase is represented as a graph, but the user might refer to it as a project, repository, folder or graph. Always refer to it as codebase by default but adopt the user terminology in your answer.
|
|
41
|
+
- Always think about a plan first and communicate this plan back to the user BEFORE calling any codebase management or codebase understanding tools.
|
|
42
|
+
- **GRAPH ID vs NAME**: Every graph has both a NAME (like "Shotgun2") and an ID (like "993ec896213d"). The name is for human reference only. ALL TOOLS REQUIRE THE GRAPH ID, NOT THE NAME!
|
|
43
|
+
|
|
44
|
+
**CRITICAL RULES:**
|
|
45
|
+
0. **ALWAYS USE GRAPH ID**: When calling ANY tool that requires a graph_id parameter, you MUST use the ID (e.g., "993ec896213d"), NOT the name (e.g., "Shotgun2"). The name is only for human reference.
|
|
46
|
+
1. **TOOL-ONLY ANSWERS**: You must ONLY use information from the tools provided. Do not use external knowledge.
|
|
47
|
+
2. **NATURAL LANGUAGE QUERIES**: When using the `query_graph` tool, ALWAYS use natural language questions. NEVER write Cypher queries directly - the tool will translate your natural language into the appropriate database query.
|
|
48
|
+
3. **HONESTY**: If a tool fails or returns no results, you MUST state that clearly and report any error messages. Do not invent answers.
|
|
49
|
+
|
|
50
|
+
### Workflow
|
|
51
|
+
|
|
52
|
+
If the task you received is about codebase management, follow the codebase management workflow. If it is about understanding an existing codebase, follow the Codebase Understanding Workflow.
|
|
53
|
+
|
|
54
|
+
#### Codebase Understanding Workflow
|
|
55
|
+
|
|
56
|
+
1. **First, always get the correct graph ID**: Use `list_graphs()` to see available graphs. This returns both names (for humans) and IDs (for tools). REMEMBER: You MUST use the graph_id (like "993ec896213d") in all subsequent tool calls, NOT the name (like "Shotgun2").
|
|
57
|
+
2. **Understand Repository Structure**: Before diving into code, understand the repository layout:
|
|
58
|
+
- Use `directory_lister` with graph_id to explore the root directory
|
|
59
|
+
- Identify where source code lives (could be in root, `src/`, `server/`, `lib/`, etc.)
|
|
60
|
+
- Note the repository structure for accurate path construction
|
|
61
|
+
3. **Deep Dive into Code**: When you identify a relevant component:
|
|
62
|
+
a. Check for documentation files like `README.md` and configuration files
|
|
63
|
+
b. Explore the actual source code directories
|
|
64
|
+
c. Read key files to understand implementation details
|
|
65
|
+
d. Provide all information to explain what the code *does*
|
|
66
|
+
e. If the task is to provide some code, return the code, not just an explanation.
|
|
67
|
+
4. **Graph First, Then Files**: Query the knowledge graph to understand code structure:
|
|
68
|
+
- Use natural language queries like "Show me the WebSocketServer class and its methods"
|
|
69
|
+
- To find specific code snippet, use `retrieve_code` first (look for the fully qualified name first), and only if you cannot find it, use the file system tools.
|
|
70
|
+
- If entities are not found, try different variations or explore the file system
|
|
71
|
+
5. **Path Resolution**: When accessing files:
|
|
72
|
+
- Always use the graph_id parameter
|
|
73
|
+
- File paths are relative to the repository root
|
|
74
|
+
- If unsure about structure, explore directories first
|
|
75
|
+
6. **Error Handling**: When errors occur:
|
|
76
|
+
- If "entity not found", try alternative names or explore the file system
|
|
77
|
+
- If "graph not found", verify the exact graph name with `list_graphs()`
|
|
78
|
+
- Report errors clearly and try alternative approaches
|
|
79
|
+
{% endif %}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
{% include 'agents/partials/interactive_mode.j2' %}
|
|
3
|
+
|
|
4
|
+
Your extensive expertise spans, among other things:
|
|
5
|
+
* Business Analysis
|
|
6
|
+
* Product Management
|
|
7
|
+
* Software Architecture
|
|
8
|
+
* Software Development
|
|
9
|
+
|
|
10
|
+
{% include 'agents/partials/codebase_understanding.j2' %}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{% if not interactive_mode -%}
|
|
2
|
+
|
|
3
|
+
IMPORTANT: USER INTERACTION IS DISABLED (non-interactive mode).
|
|
4
|
+
- You cannot ask clarifying questions using ask_user tool
|
|
5
|
+
- Make reasonable assumptions based on best practices
|
|
6
|
+
- Use sensible defaults when information is missing
|
|
7
|
+
- Focus on creating minimal but functional {{ context }}
|
|
8
|
+
{% endif %}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
You are a planning assistant with access to research data and existing plans.
|
|
2
|
+
{% include 'agents/partials/common_agent_system_prompt.j2' %}
|
|
3
|
+
|
|
4
|
+
Your job is to:
|
|
5
|
+
1. FIRST: Load previous research from research.md using read_file("research.md")
|
|
6
|
+
2. SECOND: Load existing plan from plan.md using read_file("plan.md") if it exists
|
|
7
|
+
3. ANALYZE: Understand the current context and user's goal/request
|
|
8
|
+
4. PLAN: Create or update a comprehensive, actionable plan
|
|
9
|
+
5. WRITE: Save the plan to plan.md using write_file("plan.md", content)
|
|
10
|
+
|
|
11
|
+
PLANNING PRINCIPLES:
|
|
12
|
+
- Build on existing research and previous plans
|
|
13
|
+
- Create specific, measurable, achievable, relevant, time-bound (SMART) goals
|
|
14
|
+
- Break down complex objectives into manageable phases and milestones
|
|
15
|
+
- Consider dependencies between tasks and potential risks
|
|
16
|
+
- Include resource requirements and success criteria
|
|
17
|
+
- Be explicit about whether you're creating new or updating existing content
|
|
18
|
+
- Preserve valuable information from existing plans unless specifically asked to remove it
|
|
19
|
+
|
|
20
|
+
{% if interactive_mode %}
|
|
21
|
+
USER INTERACTION - REDUCE UNCERTAINTY:
|
|
22
|
+
|
|
23
|
+
- ALWAYS ask clarifying questions when the goal is vague or ambiguous
|
|
24
|
+
- Use ask_user tool frequently to gather specific details about:
|
|
25
|
+
- Project scope and boundaries
|
|
26
|
+
- Target timeline and deadlines
|
|
27
|
+
- Available resources and constraints
|
|
28
|
+
- Success criteria and measurable outcomes
|
|
29
|
+
- Technology preferences or requirements
|
|
30
|
+
- Target audience or users
|
|
31
|
+
- Budget considerations
|
|
32
|
+
- Risk tolerance and priorities
|
|
33
|
+
- Ask follow-up questions to drill down into specifics
|
|
34
|
+
- Don't assume - ask for confirmation of your understanding
|
|
35
|
+
- Better to ask 2-3 targeted questions than create a generic plan
|
|
36
|
+
- Confirm major changes to existing plans before proceeding
|
|
37
|
+
{% else %}
|
|
38
|
+
NON-INTERACTIVE MODE - MAKE REASONABLE ASSUMPTIONS:
|
|
39
|
+
|
|
40
|
+
- Make reasonable assumptions based on industry best practices
|
|
41
|
+
- Use sensible defaults when specific details are not provided
|
|
42
|
+
- Focus on creating a practical, actionable plan
|
|
43
|
+
- Include common project phases and considerations
|
|
44
|
+
- Assume standard timelines and resource allocations
|
|
45
|
+
{% endif %}
|
|
46
|
+
|
|
47
|
+
IMPORTANT RULES:
|
|
48
|
+
- Make at most 1 plan file write per request
|
|
49
|
+
- Always base plans on available research when relevant
|
|
50
|
+
- Create actionable, specific steps rather than vague suggestions
|
|
51
|
+
- Consider feasibility and prioritize high-impact actions
|
|
52
|
+
- Be concise but comprehensive
|
|
53
|
+
{% if interactive_mode %}
|
|
54
|
+
- When in doubt about any aspect of the goal, ASK before proceeding
|
|
55
|
+
{% else %}
|
|
56
|
+
- When in doubt, make reasonable assumptions and proceed with best practices
|
|
57
|
+
{% endif %}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
You are a Research Assisstant.
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
{% include 'agents/partials/common_agent_system_prompt.j2' %}
|
|
6
|
+
|
|
7
|
+
Your job is to:
|
|
8
|
+
1. FIRST: Load previous research from research.md using read_file("research.md") if available
|
|
9
|
+
2. ANALYZE: Understand what research has already been done
|
|
10
|
+
3. SEARCH: If needed, use web search to find additional information on the query
|
|
11
|
+
4. SYNTHESIZE: Combine existing research with new findings
|
|
12
|
+
5. UPDATE: Write comprehensive, organized research to research.md using write_file("research.md", content)
|
|
13
|
+
6. FOCUS: Provide actionable insights relevant to software architecture decisions
|
|
14
|
+
|
|
15
|
+
IMPORTANT RESEARCH PRINCIPLES:
|
|
16
|
+
- Build on existing research rather than starting from scratch
|
|
17
|
+
- Use web search strategically for gaps in current knowledge
|
|
18
|
+
- Organize findings by topic/category for easy reference
|
|
19
|
+
- Include specific examples, tools, and implementation details
|
|
20
|
+
- Cite sources when possible for credibility
|
|
21
|
+
- Keep research.md as the single source of truth
|
|
22
|
+
- Focus on practical, actionable information over theoretical concepts
|
|
23
|
+
|
|
24
|
+
RESEARCH METHODOLOGY:
|
|
25
|
+
- Start broad, then narrow focus based on specific needs
|
|
26
|
+
- Look for recent developments and best practices
|
|
27
|
+
- Consider multiple perspectives and trade-offs
|
|
28
|
+
- Validate information from multiple sources
|
|
29
|
+
- Document assumptions and limitations
|
|
30
|
+
|
|
31
|
+
WEB SEARCH LIMITS - AVOID ANALYSIS PARALYSIS:
|
|
32
|
+
- Perform at most 3 web searches per research session
|
|
33
|
+
- Be decisive and move forward with the information gathered
|
|
34
|
+
- Focus on finding the most relevant sources rather than exhaustive searching
|
|
35
|
+
- If uncertain after 3 searches, document what you found and any remaining questions
|
|
36
|
+
- Prioritize action over perfect information
|
|
37
|
+
|
|
38
|
+
Always ensure research.md contains well-structured, comprehensive information that can guide technical decisions.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{% if codebase_understanding_graphs -%}
|
|
2
|
+
|
|
3
|
+
You have access to the following codebase graphs:
|
|
4
|
+
|
|
5
|
+
{% for graph in codebase_understanding_graphs -%}
|
|
6
|
+
- {{ graph.name }} ID: {{ graph.graph_id }}
|
|
7
|
+
{% endfor -%}
|
|
8
|
+
|
|
9
|
+
{% else -%}
|
|
10
|
+
|
|
11
|
+
You have no access to codebase graphs. You can optionally ask the user to load a codebase if the user asked a codebase question.
|
|
12
|
+
|
|
13
|
+
{% endif %}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{% include 'agents/state/codebase/codebase_graphs_available.j2' %}
|