shotgun-sh 0.1.0.dev22__py3-none-any.whl → 0.1.0.dev24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shotgun-sh might be problematic. Click here for more details.
- shotgun/agents/agent_manager.py +95 -15
- shotgun/agents/common.py +143 -25
- shotgun/agents/conversation_history.py +56 -0
- shotgun/agents/conversation_manager.py +105 -0
- shotgun/agents/export.py +5 -2
- shotgun/agents/models.py +16 -7
- shotgun/agents/plan.py +2 -1
- shotgun/agents/research.py +2 -1
- shotgun/agents/specify.py +2 -1
- shotgun/agents/tasks.py +5 -2
- shotgun/agents/tools/file_management.py +67 -2
- shotgun/codebase/core/ingestor.py +1 -1
- shotgun/codebase/core/manager.py +106 -4
- shotgun/codebase/models.py +4 -0
- shotgun/codebase/service.py +60 -2
- shotgun/main.py +9 -1
- shotgun/prompts/agents/export.j2 +14 -11
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +6 -9
- shotgun/prompts/agents/plan.j2 +9 -13
- shotgun/prompts/agents/research.j2 +11 -14
- shotgun/prompts/agents/specify.j2 +9 -12
- shotgun/prompts/agents/state/system_state.j2 +27 -5
- shotgun/prompts/agents/tasks.j2 +12 -12
- shotgun/sdk/codebase.py +26 -2
- shotgun/sdk/services.py +0 -14
- shotgun/tui/app.py +9 -4
- shotgun/tui/screens/chat.py +80 -19
- shotgun/tui/screens/chat_screen/command_providers.py +1 -1
- shotgun/tui/screens/chat_screen/history.py +6 -0
- shotgun/tui/utils/mode_progress.py +111 -78
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/METADATA +8 -9
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/RECORD +35 -54
- shotgun/agents/artifact_state.py +0 -58
- shotgun/agents/tools/artifact_management.py +0 -481
- shotgun/artifacts/__init__.py +0 -17
- shotgun/artifacts/exceptions.py +0 -89
- shotgun/artifacts/manager.py +0 -530
- shotgun/artifacts/models.py +0 -334
- shotgun/artifacts/service.py +0 -463
- shotgun/artifacts/templates/__init__.py +0 -10
- shotgun/artifacts/templates/loader.py +0 -252
- shotgun/artifacts/templates/models.py +0 -136
- shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +0 -66
- shotgun/artifacts/templates/research/market_research.yaml +0 -585
- shotgun/artifacts/templates/research/sdk_comparison.yaml +0 -257
- shotgun/artifacts/templates/specify/prd.yaml +0 -331
- shotgun/artifacts/templates/specify/product_spec.yaml +0 -301
- shotgun/artifacts/utils.py +0 -76
- shotgun/prompts/agents/partials/artifact_system.j2 +0 -32
- shotgun/prompts/agents/state/artifact_templates_available.j2 +0 -20
- shotgun/prompts/agents/state/existing_artifacts_available.j2 +0 -25
- shotgun/sdk/artifact_models.py +0 -186
- shotgun/sdk/artifacts.py +0 -448
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev24.dist-info}/licenses/LICENSE +0 -0
shotgun/agents/tasks.py
CHANGED
|
@@ -19,7 +19,7 @@ from .common import (
|
|
|
19
19
|
create_usage_limits,
|
|
20
20
|
run_agent,
|
|
21
21
|
)
|
|
22
|
-
from .models import AgentDeps, AgentRuntimeOptions
|
|
22
|
+
from .models import AgentDeps, AgentRuntimeOptions, AgentType
|
|
23
23
|
|
|
24
24
|
logger = get_logger(__name__)
|
|
25
25
|
|
|
@@ -41,7 +41,10 @@ def create_tasks_agent(
|
|
|
41
41
|
system_prompt_fn = partial(build_agent_system_prompt, "tasks")
|
|
42
42
|
|
|
43
43
|
agent, deps = create_base_agent(
|
|
44
|
-
system_prompt_fn,
|
|
44
|
+
system_prompt_fn,
|
|
45
|
+
agent_runtime_options,
|
|
46
|
+
provider=provider,
|
|
47
|
+
agent_mode=AgentType.TASKS,
|
|
45
48
|
)
|
|
46
49
|
return agent, deps
|
|
47
50
|
|
|
@@ -8,12 +8,76 @@ from typing import Literal
|
|
|
8
8
|
|
|
9
9
|
from pydantic_ai import RunContext
|
|
10
10
|
|
|
11
|
-
from shotgun.agents.models import AgentDeps, FileOperationType
|
|
11
|
+
from shotgun.agents.models import AgentDeps, AgentType, FileOperationType
|
|
12
12
|
from shotgun.logging_config import get_logger
|
|
13
13
|
from shotgun.utils.file_system_utils import get_shotgun_base_path
|
|
14
14
|
|
|
15
15
|
logger = get_logger(__name__)
|
|
16
16
|
|
|
17
|
+
# Map agent modes to their allowed directories/files (in workflow order)
|
|
18
|
+
AGENT_DIRECTORIES = {
|
|
19
|
+
AgentType.RESEARCH: "research.md",
|
|
20
|
+
AgentType.SPECIFY: "specification.md",
|
|
21
|
+
AgentType.PLAN: "plan.md",
|
|
22
|
+
AgentType.TASKS: "tasks.md",
|
|
23
|
+
AgentType.EXPORT: "exports/",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _validate_agent_scoped_path(filename: str, agent_mode: AgentType | None) -> Path:
|
|
28
|
+
"""Validate and resolve a file path within the agent's scoped directory.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
filename: Relative filename
|
|
32
|
+
agent_mode: The current agent mode
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Absolute path to the file within the agent's scoped directory
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
ValueError: If the path attempts to access files outside the agent's scope
|
|
39
|
+
"""
|
|
40
|
+
base_path = get_shotgun_base_path()
|
|
41
|
+
|
|
42
|
+
if agent_mode and agent_mode in AGENT_DIRECTORIES:
|
|
43
|
+
# For export mode, allow writing to any file in exports/ directory
|
|
44
|
+
if agent_mode == AgentType.EXPORT:
|
|
45
|
+
# Ensure the filename starts with exports/ or is being written to exports/
|
|
46
|
+
if not filename.startswith("exports/"):
|
|
47
|
+
filename = f"exports/{filename}"
|
|
48
|
+
full_path = (base_path / filename).resolve()
|
|
49
|
+
|
|
50
|
+
# Ensure it's within .shotgun/exports/
|
|
51
|
+
exports_dir = base_path / "exports"
|
|
52
|
+
try:
|
|
53
|
+
full_path.relative_to(exports_dir.resolve())
|
|
54
|
+
except ValueError as e:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"Export agent can only write to exports/ directory. Path '{filename}' is not allowed"
|
|
57
|
+
) from e
|
|
58
|
+
else:
|
|
59
|
+
# For other agents, only allow writing to their specific file
|
|
60
|
+
allowed_file = AGENT_DIRECTORIES[agent_mode]
|
|
61
|
+
if filename != allowed_file:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"{agent_mode.value.capitalize()} agent can only write to '{allowed_file}'. "
|
|
64
|
+
f"Attempted to write to '{filename}'"
|
|
65
|
+
)
|
|
66
|
+
full_path = (base_path / filename).resolve()
|
|
67
|
+
else:
|
|
68
|
+
# No agent mode specified, fall back to old validation
|
|
69
|
+
full_path = (base_path / filename).resolve()
|
|
70
|
+
|
|
71
|
+
# Ensure the resolved path is within the .shotgun directory
|
|
72
|
+
try:
|
|
73
|
+
full_path.relative_to(base_path.resolve())
|
|
74
|
+
except ValueError as e:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Access denied: Path '{filename}' is outside .shotgun directory"
|
|
77
|
+
) from e
|
|
78
|
+
|
|
79
|
+
return full_path
|
|
80
|
+
|
|
17
81
|
|
|
18
82
|
def _validate_shotgun_path(filename: str) -> Path:
|
|
19
83
|
"""Validate and resolve a file path within the .shotgun directory.
|
|
@@ -99,7 +163,8 @@ async def write_file(
|
|
|
99
163
|
raise ValueError(f"Invalid mode '{mode}'. Use 'w' for write or 'a' for append")
|
|
100
164
|
|
|
101
165
|
try:
|
|
102
|
-
|
|
166
|
+
# Use agent-scoped validation for write operations
|
|
167
|
+
file_path = _validate_agent_scoped_path(filename, ctx.deps.agent_mode)
|
|
103
168
|
|
|
104
169
|
# Determine operation type
|
|
105
170
|
if mode == "a":
|
|
@@ -54,7 +54,7 @@ class Ingestor:
|
|
|
54
54
|
|
|
55
55
|
# Node tables
|
|
56
56
|
node_schemas = [
|
|
57
|
-
"CREATE NODE TABLE Project(name STRING PRIMARY KEY, repo_path STRING, graph_id STRING, created_at INT64, updated_at INT64, schema_version STRING, build_options STRING, node_count INT64, relationship_count INT64, stats_updated_at INT64, status STRING, current_operation_id STRING, last_operation STRING)",
|
|
57
|
+
"CREATE NODE TABLE Project(name STRING PRIMARY KEY, repo_path STRING, graph_id STRING, created_at INT64, updated_at INT64, schema_version STRING, build_options STRING, node_count INT64, relationship_count INT64, stats_updated_at INT64, status STRING, current_operation_id STRING, last_operation STRING, indexed_from_cwds STRING)",
|
|
58
58
|
"CREATE NODE TABLE Package(qualified_name STRING PRIMARY KEY, name STRING, path STRING)",
|
|
59
59
|
"CREATE NODE TABLE Folder(path STRING PRIMARY KEY, name STRING)",
|
|
60
60
|
"CREATE NODE TABLE File(path STRING PRIMARY KEY, name STRING, extension STRING)",
|
shotgun/codebase/core/manager.py
CHANGED
|
@@ -252,6 +252,7 @@ class CodebaseGraphManager:
|
|
|
252
252
|
name: str,
|
|
253
253
|
languages: list[str] | None,
|
|
254
254
|
exclude_patterns: list[str] | None,
|
|
255
|
+
indexed_from_cwd: str | None = None,
|
|
255
256
|
) -> None:
|
|
256
257
|
"""Initialize the graph database and create initial metadata.
|
|
257
258
|
|
|
@@ -294,7 +295,8 @@ class CodebaseGraphManager:
|
|
|
294
295
|
last_operation: $last_operation,
|
|
295
296
|
node_count: 0,
|
|
296
297
|
relationship_count: 0,
|
|
297
|
-
stats_updated_at: $stats_updated_at
|
|
298
|
+
stats_updated_at: $stats_updated_at,
|
|
299
|
+
indexed_from_cwds: $indexed_from_cwds
|
|
298
300
|
})
|
|
299
301
|
""",
|
|
300
302
|
{
|
|
@@ -311,6 +313,9 @@ class CodebaseGraphManager:
|
|
|
311
313
|
"current_operation_id": None,
|
|
312
314
|
"last_operation": None,
|
|
313
315
|
"stats_updated_at": int(time.time()),
|
|
316
|
+
"indexed_from_cwds": json.dumps(
|
|
317
|
+
[indexed_from_cwd] if indexed_from_cwd else []
|
|
318
|
+
),
|
|
314
319
|
},
|
|
315
320
|
)
|
|
316
321
|
|
|
@@ -323,6 +328,7 @@ class CodebaseGraphManager:
|
|
|
323
328
|
name: str | None = None,
|
|
324
329
|
languages: list[str] | None = None,
|
|
325
330
|
exclude_patterns: list[str] | None = None,
|
|
331
|
+
indexed_from_cwd: str | None = None,
|
|
326
332
|
) -> CodebaseGraph:
|
|
327
333
|
"""Build a new code knowledge graph.
|
|
328
334
|
|
|
@@ -397,7 +403,8 @@ class CodebaseGraphManager:
|
|
|
397
403
|
created_at: $created_at,
|
|
398
404
|
updated_at: $updated_at,
|
|
399
405
|
schema_version: $schema_version,
|
|
400
|
-
build_options: $build_options
|
|
406
|
+
build_options: $build_options,
|
|
407
|
+
indexed_from_cwds: $indexed_from_cwds
|
|
401
408
|
})
|
|
402
409
|
""",
|
|
403
410
|
{
|
|
@@ -410,6 +417,9 @@ class CodebaseGraphManager:
|
|
|
410
417
|
"build_options": json.dumps(
|
|
411
418
|
{"languages": languages, "exclude_patterns": exclude_patterns}
|
|
412
419
|
),
|
|
420
|
+
"indexed_from_cwds": json.dumps(
|
|
421
|
+
[indexed_from_cwd] if indexed_from_cwd else []
|
|
422
|
+
),
|
|
413
423
|
},
|
|
414
424
|
)
|
|
415
425
|
|
|
@@ -475,6 +485,7 @@ class CodebaseGraphManager:
|
|
|
475
485
|
status=GraphStatus.READY,
|
|
476
486
|
last_operation=None,
|
|
477
487
|
current_operation_id=None,
|
|
488
|
+
indexed_from_cwds=[indexed_from_cwd] if indexed_from_cwd else [],
|
|
478
489
|
)
|
|
479
490
|
|
|
480
491
|
# Update status to READY
|
|
@@ -1145,6 +1156,15 @@ class CodebaseGraphManager:
|
|
|
1145
1156
|
logger.debug(f"Failed to parse last operation stats: {e}")
|
|
1146
1157
|
last_operation = None
|
|
1147
1158
|
|
|
1159
|
+
# Parse indexed_from_cwds - handle backward compatibility
|
|
1160
|
+
indexed_from_cwds_json = project.get("indexed_from_cwds", "[]")
|
|
1161
|
+
try:
|
|
1162
|
+
indexed_from_cwds = (
|
|
1163
|
+
json.loads(indexed_from_cwds_json) if indexed_from_cwds_json else []
|
|
1164
|
+
)
|
|
1165
|
+
except (json.JSONDecodeError, TypeError):
|
|
1166
|
+
indexed_from_cwds = []
|
|
1167
|
+
|
|
1148
1168
|
return CodebaseGraph(
|
|
1149
1169
|
graph_id=graph_id,
|
|
1150
1170
|
repo_path=project.get("repo_path", ""),
|
|
@@ -1165,6 +1185,7 @@ class CodebaseGraphManager:
|
|
|
1165
1185
|
status=status,
|
|
1166
1186
|
last_operation=last_operation,
|
|
1167
1187
|
current_operation_id=project.get("current_operation_id"),
|
|
1188
|
+
indexed_from_cwds=indexed_from_cwds,
|
|
1168
1189
|
)
|
|
1169
1190
|
except Exception as e:
|
|
1170
1191
|
logger.error(
|
|
@@ -1190,6 +1211,83 @@ class CodebaseGraphManager:
|
|
|
1190
1211
|
|
|
1191
1212
|
return sorted(graphs, key=lambda g: g.updated_at, reverse=True)
|
|
1192
1213
|
|
|
1214
|
+
async def add_cwd_access(self, graph_id: str, cwd: str | None = None) -> None:
|
|
1215
|
+
"""Add a working directory to a graph's access list.
|
|
1216
|
+
|
|
1217
|
+
Args:
|
|
1218
|
+
graph_id: Graph ID to update
|
|
1219
|
+
cwd: Working directory to add. If None, uses current working directory.
|
|
1220
|
+
"""
|
|
1221
|
+
from pathlib import Path
|
|
1222
|
+
|
|
1223
|
+
if cwd is None:
|
|
1224
|
+
cwd = str(Path.cwd().resolve())
|
|
1225
|
+
else:
|
|
1226
|
+
cwd = str(Path(cwd).resolve())
|
|
1227
|
+
|
|
1228
|
+
# Get current graph
|
|
1229
|
+
graph = await self.get_graph(graph_id)
|
|
1230
|
+
if not graph:
|
|
1231
|
+
raise ValueError(f"Graph {graph_id} not found")
|
|
1232
|
+
|
|
1233
|
+
# Get current list
|
|
1234
|
+
current_cwds = graph.indexed_from_cwds.copy()
|
|
1235
|
+
|
|
1236
|
+
# Add new CWD if not already present
|
|
1237
|
+
if cwd not in current_cwds:
|
|
1238
|
+
current_cwds.append(cwd)
|
|
1239
|
+
|
|
1240
|
+
# Update in database
|
|
1241
|
+
await self._execute_query(
|
|
1242
|
+
graph_id,
|
|
1243
|
+
"""
|
|
1244
|
+
MATCH (p:Project {graph_id: $graph_id})
|
|
1245
|
+
SET p.indexed_from_cwds = $indexed_from_cwds
|
|
1246
|
+
""",
|
|
1247
|
+
{
|
|
1248
|
+
"graph_id": graph_id,
|
|
1249
|
+
"indexed_from_cwds": json.dumps(current_cwds),
|
|
1250
|
+
},
|
|
1251
|
+
)
|
|
1252
|
+
logger.info(f"Added CWD access for {cwd} to graph {graph_id}")
|
|
1253
|
+
|
|
1254
|
+
async def remove_cwd_access(self, graph_id: str, cwd: str) -> None:
|
|
1255
|
+
"""Remove a working directory from a graph's access list.
|
|
1256
|
+
|
|
1257
|
+
Args:
|
|
1258
|
+
graph_id: Graph ID to update
|
|
1259
|
+
cwd: Working directory to remove
|
|
1260
|
+
"""
|
|
1261
|
+
from pathlib import Path
|
|
1262
|
+
|
|
1263
|
+
cwd = str(Path(cwd).resolve())
|
|
1264
|
+
|
|
1265
|
+
# Get current graph
|
|
1266
|
+
graph = await self.get_graph(graph_id)
|
|
1267
|
+
if not graph:
|
|
1268
|
+
raise ValueError(f"Graph {graph_id} not found")
|
|
1269
|
+
|
|
1270
|
+
# Get current list
|
|
1271
|
+
current_cwds = graph.indexed_from_cwds.copy()
|
|
1272
|
+
|
|
1273
|
+
# Remove CWD if present
|
|
1274
|
+
if cwd in current_cwds:
|
|
1275
|
+
current_cwds.remove(cwd)
|
|
1276
|
+
|
|
1277
|
+
# Update in database
|
|
1278
|
+
await self._execute_query(
|
|
1279
|
+
graph_id,
|
|
1280
|
+
"""
|
|
1281
|
+
MATCH (p:Project {graph_id: $graph_id})
|
|
1282
|
+
SET p.indexed_from_cwds = $indexed_from_cwds
|
|
1283
|
+
""",
|
|
1284
|
+
{
|
|
1285
|
+
"graph_id": graph_id,
|
|
1286
|
+
"indexed_from_cwds": json.dumps(current_cwds),
|
|
1287
|
+
},
|
|
1288
|
+
)
|
|
1289
|
+
logger.info(f"Removed CWD access for {cwd} from graph {graph_id}")
|
|
1290
|
+
|
|
1193
1291
|
async def delete_graph(self, graph_id: str) -> None:
|
|
1194
1292
|
"""Delete a graph.
|
|
1195
1293
|
|
|
@@ -1365,6 +1463,7 @@ class CodebaseGraphManager:
|
|
|
1365
1463
|
name: str,
|
|
1366
1464
|
languages: list[str] | None,
|
|
1367
1465
|
exclude_patterns: list[str] | None,
|
|
1466
|
+
indexed_from_cwd: str | None = None,
|
|
1368
1467
|
) -> CodebaseGraph:
|
|
1369
1468
|
"""Internal implementation of graph building (runs in background)."""
|
|
1370
1469
|
operation_id = str(uuid.uuid4())
|
|
@@ -1388,7 +1487,7 @@ class CodebaseGraphManager:
|
|
|
1388
1487
|
|
|
1389
1488
|
# Do the actual build work
|
|
1390
1489
|
graph = await self._do_build_graph(
|
|
1391
|
-
graph_id, repo_path, name, languages, exclude_patterns
|
|
1490
|
+
graph_id, repo_path, name, languages, exclude_patterns, indexed_from_cwd
|
|
1392
1491
|
)
|
|
1393
1492
|
|
|
1394
1493
|
# Update operation stats
|
|
@@ -1436,6 +1535,7 @@ class CodebaseGraphManager:
|
|
|
1436
1535
|
name: str,
|
|
1437
1536
|
languages: list[str] | None,
|
|
1438
1537
|
exclude_patterns: list[str] | None,
|
|
1538
|
+
indexed_from_cwd: str | None = None,
|
|
1439
1539
|
) -> CodebaseGraph:
|
|
1440
1540
|
"""Execute the actual graph building logic (extracted from original build_graph)."""
|
|
1441
1541
|
# The database and Project node already exist from _initialize_graph_metadata
|
|
@@ -1515,6 +1615,7 @@ class CodebaseGraphManager:
|
|
|
1515
1615
|
name: str | None = None,
|
|
1516
1616
|
languages: list[str] | None = None,
|
|
1517
1617
|
exclude_patterns: list[str] | None = None,
|
|
1618
|
+
indexed_from_cwd: str | None = None,
|
|
1518
1619
|
) -> str:
|
|
1519
1620
|
"""Start building a new code knowledge graph asynchronously.
|
|
1520
1621
|
|
|
@@ -1547,12 +1648,13 @@ class CodebaseGraphManager:
|
|
|
1547
1648
|
name=name,
|
|
1548
1649
|
languages=languages,
|
|
1549
1650
|
exclude_patterns=exclude_patterns,
|
|
1651
|
+
indexed_from_cwd=indexed_from_cwd,
|
|
1550
1652
|
)
|
|
1551
1653
|
|
|
1552
1654
|
# Start the build operation in background
|
|
1553
1655
|
task = asyncio.create_task(
|
|
1554
1656
|
self._build_graph_impl(
|
|
1555
|
-
graph_id, repo_path, name, languages, exclude_patterns
|
|
1657
|
+
graph_id, repo_path, name, languages, exclude_patterns, indexed_from_cwd
|
|
1556
1658
|
)
|
|
1557
1659
|
)
|
|
1558
1660
|
self._operations[graph_id] = task
|
shotgun/codebase/models.py
CHANGED
|
@@ -73,6 +73,10 @@ class CodebaseGraph(BaseModel):
|
|
|
73
73
|
current_operation_id: str | None = Field(
|
|
74
74
|
None, description="ID of current in-progress operation"
|
|
75
75
|
)
|
|
76
|
+
indexed_from_cwds: list[str] = Field(
|
|
77
|
+
default_factory=list,
|
|
78
|
+
description="List of working directories from which this graph is accessible. Empty list means globally accessible.",
|
|
79
|
+
)
|
|
76
80
|
|
|
77
81
|
|
|
78
82
|
class QueryResult(BaseModel):
|
shotgun/codebase/service.py
CHANGED
|
@@ -36,17 +36,57 @@ class CodebaseService:
|
|
|
36
36
|
"""
|
|
37
37
|
return await self.manager.list_graphs()
|
|
38
38
|
|
|
39
|
-
async def
|
|
39
|
+
async def list_graphs_for_directory(
|
|
40
|
+
self, directory: Path | str | None = None
|
|
41
|
+
) -> list[CodebaseGraph]:
|
|
42
|
+
"""List graphs that match a specific directory.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
directory: Directory to filter by. If None, uses current working directory.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of CodebaseGraph objects accessible from the specified directory
|
|
49
|
+
"""
|
|
50
|
+
from pathlib import Path
|
|
51
|
+
|
|
52
|
+
if directory is None:
|
|
53
|
+
directory = Path.cwd()
|
|
54
|
+
elif isinstance(directory, str):
|
|
55
|
+
directory = Path(directory)
|
|
56
|
+
|
|
57
|
+
# Resolve to absolute path for comparison
|
|
58
|
+
target_path = str(directory.resolve())
|
|
59
|
+
|
|
60
|
+
# Get all graphs and filter by those accessible from this directory
|
|
61
|
+
all_graphs = await self.manager.list_graphs()
|
|
62
|
+
filtered_graphs = []
|
|
63
|
+
|
|
64
|
+
for graph in all_graphs:
|
|
65
|
+
# If indexed_from_cwds is empty, it's globally accessible (backward compatibility)
|
|
66
|
+
if not graph.indexed_from_cwds:
|
|
67
|
+
filtered_graphs.append(graph)
|
|
68
|
+
# Otherwise, check if current directory is in the allowed list
|
|
69
|
+
elif target_path in graph.indexed_from_cwds:
|
|
70
|
+
filtered_graphs.append(graph)
|
|
71
|
+
|
|
72
|
+
return filtered_graphs
|
|
73
|
+
|
|
74
|
+
async def create_graph(
|
|
75
|
+
self, repo_path: str | Path, name: str, indexed_from_cwd: str | None = None
|
|
76
|
+
) -> CodebaseGraph:
|
|
40
77
|
"""Create and index a new graph from a repository.
|
|
41
78
|
|
|
42
79
|
Args:
|
|
43
80
|
repo_path: Path to the repository to index
|
|
44
81
|
name: Human-readable name for the graph
|
|
82
|
+
indexed_from_cwd: Working directory from which indexing was initiated
|
|
45
83
|
|
|
46
84
|
Returns:
|
|
47
85
|
The created CodebaseGraph
|
|
48
86
|
"""
|
|
49
|
-
return await self.manager.build_graph(
|
|
87
|
+
return await self.manager.build_graph(
|
|
88
|
+
str(repo_path), name, indexed_from_cwd=indexed_from_cwd
|
|
89
|
+
)
|
|
50
90
|
|
|
51
91
|
async def get_graph(self, graph_id: str) -> CodebaseGraph | None:
|
|
52
92
|
"""Get graph metadata by ID.
|
|
@@ -59,6 +99,24 @@ class CodebaseService:
|
|
|
59
99
|
"""
|
|
60
100
|
return await self.manager.get_graph(graph_id)
|
|
61
101
|
|
|
102
|
+
async def add_cwd_access(self, graph_id: str, cwd: str | None = None) -> None:
|
|
103
|
+
"""Add a working directory to a graph's access list.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
graph_id: Graph ID to update
|
|
107
|
+
cwd: Working directory to add. If None, uses current working directory.
|
|
108
|
+
"""
|
|
109
|
+
await self.manager.add_cwd_access(graph_id, cwd)
|
|
110
|
+
|
|
111
|
+
async def remove_cwd_access(self, graph_id: str, cwd: str) -> None:
|
|
112
|
+
"""Remove a working directory from a graph's access list.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
graph_id: Graph ID to update
|
|
116
|
+
cwd: Working directory to remove
|
|
117
|
+
"""
|
|
118
|
+
await self.manager.remove_cwd_access(graph_id, cwd)
|
|
119
|
+
|
|
62
120
|
async def delete_graph(self, graph_id: str) -> None:
|
|
63
121
|
"""Delete a graph and its data.
|
|
64
122
|
|
shotgun/main.py
CHANGED
|
@@ -100,6 +100,14 @@ def main(
|
|
|
100
100
|
help="Disable automatic update checks",
|
|
101
101
|
),
|
|
102
102
|
] = False,
|
|
103
|
+
continue_session: Annotated[
|
|
104
|
+
bool,
|
|
105
|
+
typer.Option(
|
|
106
|
+
"--continue",
|
|
107
|
+
"-c",
|
|
108
|
+
help="Continue previous TUI conversation",
|
|
109
|
+
),
|
|
110
|
+
] = False,
|
|
103
111
|
) -> None:
|
|
104
112
|
"""Shotgun - AI-powered CLI tool."""
|
|
105
113
|
logger.debug("Starting shotgun CLI application")
|
|
@@ -112,7 +120,7 @@ def main(
|
|
|
112
120
|
|
|
113
121
|
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
114
122
|
logger.debug("Launching shotgun TUI application")
|
|
115
|
-
tui_app.run(no_update_check=no_update_check)
|
|
123
|
+
tui_app.run(no_update_check=no_update_check, continue_session=continue_session)
|
|
116
124
|
|
|
117
125
|
# Show update notification after TUI exits
|
|
118
126
|
if _update_notification:
|
shotgun/prompts/agents/export.j2
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
You are an experienced Export Specialist and Data Transformation Expert.
|
|
2
2
|
|
|
3
|
-
Your job is to help export
|
|
3
|
+
Your job is to help export project documentation and findings to various formats and destinations in the exports/ folder.
|
|
4
4
|
|
|
5
5
|
{% include 'agents/partials/common_agent_system_prompt.j2' %}
|
|
6
6
|
|
|
7
7
|
## EXPORT WORKFLOW
|
|
8
8
|
|
|
9
9
|
For export tasks:
|
|
10
|
-
1. **Check existing
|
|
11
|
-
2. **Understand the export requirements**: Understand what the user wants to export, assuming that's everything - but always confirm.
|
|
10
|
+
1. **Check existing files**: Read `research.md`, `plan.md`, `tasks.md`, and `specification.md` to see what content is available
|
|
11
|
+
2. **Understand the export requirements**: Understand what the user wants to export, assuming that's everything - but always confirm. Priority: Tasks, Plan, Specify, Research.
|
|
12
12
|
3. **Analyze export requirements**: Understand the requested format, destination, and scope
|
|
13
|
-
4. **Read source content**: Read the content of all the relevant
|
|
13
|
+
4. **Read source content**: Read the content of all the relevant files
|
|
14
14
|
5. **Transform and format**: Convert content to the requested format
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
6. **Create export files**: Use write_file with path like "exports/filename.ext" to save in exports folder
|
|
16
|
+
7. **Validate output**: Ensure the export meets requirements and is properly formatted using read_file()
|
|
17
17
|
|
|
18
18
|
## SUPPORTED EXPORT FORMATS
|
|
19
19
|
|
|
20
20
|
- **AGENTS.md**: See below
|
|
21
21
|
- **Markdown (.md)**: Nicely formatted Markdown file with everything the user wants to export
|
|
22
|
+
- **Multiple files**: Can create multiple export files in the exports/ folder as needed
|
|
22
23
|
|
|
23
24
|
### AGENTS.md
|
|
24
25
|
|
|
25
26
|
Your task: generate an **AGENTS.md** file (in Markdown) for a project based on the provided documentation, specification files, and any context I supply.
|
|
26
27
|
|
|
27
28
|
**Instructions / constraints**:
|
|
28
|
-
- Use clear headings (e.g.
|
|
29
|
+
- Use clear headings (e.g. "Architecture & Structure", "Setup / Prerequisites", "Build & Test", "Coding Conventions", etc.).
|
|
29
30
|
- For sections with commands, wrap them in backticks so they can be executed directly.
|
|
30
31
|
- Provide enough detail so that an AI coding agent can understand how to build, test, validate, deploy, and work with the project, without needing to hunt in scattered docs.
|
|
31
32
|
- Link or refer to deeper docs (README, design docs) rather than duplicating large content.
|
|
@@ -45,6 +46,7 @@ Your task: generate an **AGENTS.md** file (in Markdown) for a project based on t
|
|
|
45
46
|
- Validate exported content is properly formatted and complete
|
|
46
47
|
- Handle missing or incomplete source data gracefully
|
|
47
48
|
- Consider target audience and use case for formatting decisions
|
|
49
|
+
- Always save exports in the exports/ folder
|
|
48
50
|
|
|
49
51
|
{% if interactive_mode %}
|
|
50
52
|
USER INTERACTION - CLARIFY EXPORT REQUIREMENTS:
|
|
@@ -53,9 +55,9 @@ USER INTERACTION - CLARIFY EXPORT REQUIREMENTS:
|
|
|
53
55
|
- Use ask_user tool to gather specific details about:
|
|
54
56
|
- Target format and file type preferences
|
|
55
57
|
- Intended use case and audience for the export
|
|
56
|
-
- Specific content sections
|
|
58
|
+
- Specific content sections to include/exclude from files
|
|
57
59
|
- Output structure and organization preferences
|
|
58
|
-
- Destination (
|
|
60
|
+
- Destination filename(s) in the exports/ folder
|
|
59
61
|
- Ask follow-up questions to ensure exports meet exact needs
|
|
60
62
|
- Confirm export scope and format before proceeding with large exports
|
|
61
63
|
- Better to ask 2-3 targeted questions than create generic exports
|
|
@@ -71,13 +73,14 @@ NON-INTERACTIVE MODE - MAKE REASONABLE EXPORT DECISIONS:
|
|
|
71
73
|
{% endif %}
|
|
72
74
|
|
|
73
75
|
IMPORTANT RULES:
|
|
74
|
-
- Always verify source
|
|
76
|
+
- Always verify source files exist before attempting export
|
|
75
77
|
- Preserve all critical information during format conversion
|
|
76
78
|
- Include source attribution and export metadata
|
|
77
79
|
- Create well-structured, properly formatted output
|
|
80
|
+
- Always save exports in the exports/ folder (create if needed)
|
|
78
81
|
{% if interactive_mode %}
|
|
79
82
|
- When export requirements are ambiguous, ASK before proceeding
|
|
80
83
|
{% else %}
|
|
81
84
|
- When requirements are unclear, use industry standard practices
|
|
82
85
|
{% endif %}
|
|
83
|
-
- Ensure exported content is self-contained and usable
|
|
86
|
+
- Ensure exported content is self-contained and usable
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
Your extensive expertise spans, among other things:
|
|
3
2
|
* Business Analysis
|
|
4
3
|
* Product Management
|
|
@@ -8,16 +7,16 @@ Your extensive expertise spans, among other things:
|
|
|
8
7
|
## KEY RULES
|
|
9
8
|
|
|
10
9
|
{% if interactive_mode %}
|
|
11
|
-
0. Always ask for
|
|
10
|
+
0. Always ask for review and go ahead to move forward after writing files.
|
|
12
11
|
{% endif %}
|
|
13
12
|
1. Above all, prefer using tools to do the work and NEVER respond with text.
|
|
14
|
-
2. IMPORTANT: Always ask for
|
|
15
|
-
3. **Be Detailed**: Write meticulously detailed documents in
|
|
13
|
+
2. IMPORTANT: Always ask for review and go ahead to move forward after using write_file().
|
|
14
|
+
3. **Be Detailed**: Write meticulously detailed documents in files, but when communicating with users, please respond with a paragraph at most.
|
|
16
15
|
4. **Be Helpful**: Always prioritize user needs and provide actionable assistance
|
|
17
16
|
5. **Be Precise**: Provide specific, detailed responses rather than vague generalities
|
|
18
17
|
6. **Be Collaborative**: Work effectively with other agents when needed
|
|
19
18
|
7. **Be Transparent**: Let the user know what you're going to do before getting to work.
|
|
20
|
-
8. **Be Efficient**: Avoid redundant work - check existing
|
|
19
|
+
8. **Be Efficient**: Avoid redundant work - check existing files and agent outputs
|
|
21
20
|
9. **Be Creative**: If the user seems not to know something, always be creative and come up with ideas that fit their thinking.
|
|
22
21
|
10. Greet the user when you're just starting to work.
|
|
23
22
|
11. DO NOT repeat yourself.
|
|
@@ -29,12 +28,10 @@ Your extensive expertise spans, among other things:
|
|
|
29
28
|
- Provide complete, actionable outputs rather than partial solutions
|
|
30
29
|
- Consider user experience and business context in recommendations
|
|
31
30
|
- Maintain consistency with existing project patterns and conventions
|
|
32
|
-
- Always
|
|
33
|
-
|
|
34
|
-
{% include 'agents/partials/artifact_system.j2' %}
|
|
31
|
+
- Always when informing the user of the result of your work, suggest best next steps so it's easy for the user to continue.
|
|
35
32
|
|
|
36
33
|
{% include 'agents/partials/codebase_understanding.j2' %}
|
|
37
34
|
|
|
38
35
|
{% include 'agents/partials/content_formatting.j2' %}
|
|
39
36
|
|
|
40
|
-
{% include 'agents/partials/interactive_mode.j2' %}
|
|
37
|
+
{% include 'agents/partials/interactive_mode.j2' %}
|
shotgun/prompts/agents/plan.j2
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
You are an experienced Project Manager and Strategic Planner.
|
|
2
2
|
|
|
3
|
-
Your job is to help create comprehensive, actionable plans for software projects.
|
|
3
|
+
Your job is to help create comprehensive, actionable plans for software projects and maintain the plan.md file.
|
|
4
4
|
|
|
5
5
|
{% include 'agents/partials/common_agent_system_prompt.j2' %}
|
|
6
6
|
|
|
7
7
|
## PLANNING WORKFLOW
|
|
8
8
|
|
|
9
9
|
For PLANNING tasks:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
4. **Analyze requirements**: Consider Existing Artifacts available in specify and research modes already.
|
|
16
|
-
5. **Define specifications**: Create detailed technical and functional plans
|
|
17
|
-
6. **Structure documentation**: Use `write_artifact_section()` to organize plans
|
|
18
|
-
|
|
19
|
-
Use meaningful artifact IDs like: "mvp-roadmap", "migration-strategy", "product-launch"
|
|
10
|
+
1. **Load existing plan**: ALWAYS first use `read_file("plan.md")` to see what plans already exist (if the file exists)
|
|
11
|
+
2. **Check related files**: Read `specification.md` and `research.md` if they exist to understand context
|
|
12
|
+
3. **Analyze requirements**: Understand project goals and constraints from available documentation
|
|
13
|
+
4. **Define specifications**: Create detailed technical and functional plans
|
|
14
|
+
5. **Structure documentation**: Use `write_file("plan.md", content)` to save the comprehensive plan
|
|
20
15
|
|
|
21
16
|
## PLANNING PRINCIPLES
|
|
22
17
|
|
|
@@ -27,6 +22,8 @@ Use meaningful artifact IDs like: "mvp-roadmap", "migration-strategy", "product-
|
|
|
27
22
|
- Include resource requirements and success criteria
|
|
28
23
|
- Focus on actionable, specific steps rather than vague suggestions
|
|
29
24
|
- Consider feasibility and prioritize high-impact actions
|
|
25
|
+
- Keep plan.md as the single source of truth
|
|
26
|
+
- Organize plans by phases, milestones, or components
|
|
30
27
|
|
|
31
28
|
IMPORTANT RULES:
|
|
32
29
|
- Make at most 1 plan file write per request
|
|
@@ -51,5 +48,4 @@ NON-INTERACTIVE MODE - MAKE REASONABLE ASSUMPTIONS:
|
|
|
51
48
|
- Focus on creating a practical, actionable plan
|
|
52
49
|
- Include common project phases and considerations
|
|
53
50
|
- Assume standard timelines and resource allocations
|
|
54
|
-
{% endif %}
|
|
55
|
-
|
|
51
|
+
{% endif %}
|