shotgun-sh 0.1.0.dev15__py3-none-any.whl → 0.1.0.dev16__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 +243 -24
- shotgun/agents/tools/artifact_management.py +2 -3
- shotgun/artifacts/templates/research/sdk_comparison.yaml +73 -73
- shotgun/prompts/agents/partials/interactive_mode.j2 +10 -1
- shotgun/prompts/agents/plan.j2 +9 -12
- shotgun/prompts/agents/research.j2 +6 -3
- shotgun/prompts/agents/specify.j2 +8 -9
- shotgun/prompts/agents/state/artifact_templates_available.j2 +3 -1
- shotgun/prompts/agents/state/existing_artifacts_available.j2 +2 -0
- shotgun/prompts/agents/tasks.j2 +1 -1
- shotgun/prompts/codebase/cypher_query_patterns.j2 +2 -0
- shotgun/prompts/codebase/partials/graph_schema.j2 +4 -2
- shotgun/tui/app.py +9 -1
- shotgun/tui/commands/__init__.py +73 -0
- shotgun/tui/screens/chat.py +361 -178
- shotgun/tui/screens/chat.tcss +11 -0
- shotgun/tui/screens/chat_screen/__init__.py +0 -0
- shotgun/tui/screens/chat_screen/command_providers.py +197 -0
- shotgun/tui/screens/chat_screen/history.py +160 -0
- {shotgun_sh-0.1.0.dev15.dist-info → shotgun_sh-0.1.0.dev16.dist-info}/METADATA +1 -1
- {shotgun_sh-0.1.0.dev15.dist-info → shotgun_sh-0.1.0.dev16.dist-info}/RECORD +24 -20
- {shotgun_sh-0.1.0.dev15.dist-info → shotgun_sh-0.1.0.dev16.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.1.0.dev15.dist-info → shotgun_sh-0.1.0.dev16.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.1.0.dev15.dist-info → shotgun_sh-0.1.0.dev16.dist-info}/licenses/LICENSE +0 -0
shotgun/agents/agent_manager.py
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
"""Agent manager for coordinating multiple AI agents with shared message history."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import AsyncIterable
|
|
5
|
+
from dataclasses import dataclass, field
|
|
3
6
|
from enum import Enum
|
|
4
|
-
from typing import Any
|
|
7
|
+
from typing import Any, cast
|
|
5
8
|
|
|
6
9
|
from pydantic_ai import (
|
|
7
10
|
Agent,
|
|
8
11
|
DeferredToolRequests,
|
|
9
12
|
DeferredToolResults,
|
|
13
|
+
RunContext,
|
|
10
14
|
UsageLimits,
|
|
11
15
|
)
|
|
12
16
|
from pydantic_ai.agent import AgentRunResult
|
|
13
|
-
from pydantic_ai.messages import
|
|
17
|
+
from pydantic_ai.messages import (
|
|
18
|
+
AgentStreamEvent,
|
|
19
|
+
FinalResultEvent,
|
|
20
|
+
ModelMessage,
|
|
21
|
+
ModelRequest,
|
|
22
|
+
ModelResponse,
|
|
23
|
+
ModelResponsePart,
|
|
24
|
+
PartDeltaEvent,
|
|
25
|
+
PartStartEvent,
|
|
26
|
+
ToolCallPartDelta,
|
|
27
|
+
)
|
|
14
28
|
from textual.message import Message
|
|
15
29
|
from textual.widget import Widget
|
|
16
30
|
|
|
@@ -18,8 +32,11 @@ from .history.compaction import apply_persistent_compaction
|
|
|
18
32
|
from .models import AgentDeps, AgentRuntimeOptions, FileOperation
|
|
19
33
|
from .plan import create_plan_agent
|
|
20
34
|
from .research import create_research_agent
|
|
35
|
+
from .specify import create_specify_agent
|
|
21
36
|
from .tasks import create_tasks_agent
|
|
22
37
|
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
23
40
|
|
|
24
41
|
class AgentType(Enum):
|
|
25
42
|
"""Enumeration for available agent types (for Python < 3.11)."""
|
|
@@ -27,6 +44,7 @@ class AgentType(Enum):
|
|
|
27
44
|
RESEARCH = "research"
|
|
28
45
|
PLAN = "plan"
|
|
29
46
|
TASKS = "tasks"
|
|
47
|
+
SPECIFY = "specify"
|
|
30
48
|
|
|
31
49
|
|
|
32
50
|
class MessageHistoryUpdated(Message):
|
|
@@ -51,6 +69,25 @@ class MessageHistoryUpdated(Message):
|
|
|
51
69
|
self.file_operations = file_operations or []
|
|
52
70
|
|
|
53
71
|
|
|
72
|
+
class PartialResponseMessage(Message):
|
|
73
|
+
"""Event posted when a partial response is received."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, message: ModelResponse | None, is_last: bool) -> None:
|
|
76
|
+
"""Initialize the partial response message."""
|
|
77
|
+
super().__init__()
|
|
78
|
+
self.message = message
|
|
79
|
+
self.is_last = is_last
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(slots=True)
|
|
83
|
+
class _PartialStreamState:
|
|
84
|
+
"""Tracks partial response parts while streaming a single agent run."""
|
|
85
|
+
|
|
86
|
+
parts: list[ModelResponsePart | ToolCallPartDelta] = field(default_factory=list)
|
|
87
|
+
latest_partial: ModelResponse | None = None
|
|
88
|
+
final_sent: bool = False
|
|
89
|
+
|
|
90
|
+
|
|
54
91
|
class AgentManager(Widget):
|
|
55
92
|
"""Manages multiple agents with shared message history."""
|
|
56
93
|
|
|
@@ -65,6 +102,8 @@ class AgentManager(Widget):
|
|
|
65
102
|
deps: Optional agent dependencies. If not provided, defaults to interactive mode.
|
|
66
103
|
"""
|
|
67
104
|
super().__init__()
|
|
105
|
+
self.display = False
|
|
106
|
+
|
|
68
107
|
# Use provided deps or create default with interactive mode
|
|
69
108
|
self.deps = deps
|
|
70
109
|
|
|
@@ -80,14 +119,17 @@ class AgentManager(Widget):
|
|
|
80
119
|
tasks=self.deps.tasks,
|
|
81
120
|
)
|
|
82
121
|
|
|
83
|
-
# Initialize all agents
|
|
84
|
-
self.research_agent,
|
|
122
|
+
# Initialize all agents and store their specific deps
|
|
123
|
+
self.research_agent, self.research_deps = create_research_agent(
|
|
124
|
+
agent_runtime_options=agent_runtime_options
|
|
125
|
+
)
|
|
126
|
+
self.plan_agent, self.plan_deps = create_plan_agent(
|
|
85
127
|
agent_runtime_options=agent_runtime_options
|
|
86
128
|
)
|
|
87
|
-
self.
|
|
129
|
+
self.tasks_agent, self.tasks_deps = create_tasks_agent(
|
|
88
130
|
agent_runtime_options=agent_runtime_options
|
|
89
131
|
)
|
|
90
|
-
self.
|
|
132
|
+
self.specify_agent, self.specify_deps = create_specify_agent(
|
|
91
133
|
agent_runtime_options=agent_runtime_options
|
|
92
134
|
)
|
|
93
135
|
|
|
@@ -98,6 +140,7 @@ class AgentManager(Widget):
|
|
|
98
140
|
self.ui_message_history: list[ModelMessage] = []
|
|
99
141
|
self.message_history: list[ModelMessage] = []
|
|
100
142
|
self.recently_change_files: list[FileOperation] = []
|
|
143
|
+
self._stream_state: _PartialStreamState | None = None
|
|
101
144
|
|
|
102
145
|
@property
|
|
103
146
|
def current_agent(self) -> Agent[AgentDeps, str | DeferredToolRequests]:
|
|
@@ -123,9 +166,52 @@ class AgentManager(Widget):
|
|
|
123
166
|
AgentType.RESEARCH: self.research_agent,
|
|
124
167
|
AgentType.PLAN: self.plan_agent,
|
|
125
168
|
AgentType.TASKS: self.tasks_agent,
|
|
169
|
+
AgentType.SPECIFY: self.specify_agent,
|
|
126
170
|
}
|
|
127
171
|
return agent_map[agent_type]
|
|
128
172
|
|
|
173
|
+
def _get_agent_deps(self, agent_type: AgentType) -> AgentDeps:
|
|
174
|
+
"""Get agent-specific deps by type.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
agent_type: The type of agent to retrieve deps for.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
The agent-specific dependencies.
|
|
181
|
+
"""
|
|
182
|
+
deps_map = {
|
|
183
|
+
AgentType.RESEARCH: self.research_deps,
|
|
184
|
+
AgentType.PLAN: self.plan_deps,
|
|
185
|
+
AgentType.TASKS: self.tasks_deps,
|
|
186
|
+
AgentType.SPECIFY: self.specify_deps,
|
|
187
|
+
}
|
|
188
|
+
return deps_map[agent_type]
|
|
189
|
+
|
|
190
|
+
def _create_merged_deps(self, agent_type: AgentType) -> AgentDeps:
|
|
191
|
+
"""Create merged dependencies combining shared and agent-specific deps.
|
|
192
|
+
|
|
193
|
+
This preserves the agent's system_prompt_fn while using shared runtime state.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
agent_type: The type of agent to create merged deps for.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Merged AgentDeps with agent-specific system_prompt_fn.
|
|
200
|
+
"""
|
|
201
|
+
agent_deps = self._get_agent_deps(agent_type)
|
|
202
|
+
|
|
203
|
+
# Ensure shared deps is not None (should be guaranteed by __init__)
|
|
204
|
+
if self.deps is None:
|
|
205
|
+
raise ValueError("Shared deps is None - this should not happen")
|
|
206
|
+
|
|
207
|
+
# Create new deps with shared runtime state but agent's system_prompt_fn
|
|
208
|
+
# Use a copy of the shared deps and update the system_prompt_fn
|
|
209
|
+
merged_deps = self.deps.model_copy(
|
|
210
|
+
update={"system_prompt_fn": agent_deps.system_prompt_fn}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return merged_deps
|
|
214
|
+
|
|
129
215
|
def set_agent(self, agent_type: AgentType) -> None:
|
|
130
216
|
"""Set the current active agent.
|
|
131
217
|
|
|
@@ -166,9 +252,9 @@ class AgentManager(Widget):
|
|
|
166
252
|
Returns:
|
|
167
253
|
The agent run result.
|
|
168
254
|
"""
|
|
169
|
-
# Use
|
|
255
|
+
# Use merged deps (shared state + agent-specific system prompt) if not provided
|
|
170
256
|
if deps is None:
|
|
171
|
-
deps = self.
|
|
257
|
+
deps = self._create_merged_deps(self._current_agent_type)
|
|
172
258
|
|
|
173
259
|
# Ensure deps is not None
|
|
174
260
|
if deps is None:
|
|
@@ -178,35 +264,168 @@ class AgentManager(Widget):
|
|
|
178
264
|
self.ui_message_history.append(ModelRequest.user_text_prompt(prompt))
|
|
179
265
|
self._post_messages_updated()
|
|
180
266
|
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
267
|
+
# Ensure system prompt is added to message history before running agent
|
|
268
|
+
from pydantic_ai.messages import SystemPromptPart
|
|
269
|
+
|
|
270
|
+
from shotgun.agents.common import add_system_prompt_message
|
|
271
|
+
|
|
272
|
+
# Start with persistent message history
|
|
273
|
+
message_history = self.message_history
|
|
274
|
+
|
|
275
|
+
# Check if the message history already has a system prompt
|
|
276
|
+
has_system_prompt = any(
|
|
277
|
+
hasattr(msg, "parts")
|
|
278
|
+
and any(isinstance(part, SystemPromptPart) for part in msg.parts)
|
|
279
|
+
for msg in message_history
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Always ensure we have a system prompt for the agent
|
|
283
|
+
# (compaction may remove it from persistent history, but agent needs it)
|
|
284
|
+
if not has_system_prompt:
|
|
285
|
+
message_history = await add_system_prompt_message(deps, message_history)
|
|
286
|
+
|
|
287
|
+
# Run the agent with streaming support (from origin/main)
|
|
288
|
+
self._stream_state = _PartialStreamState()
|
|
289
|
+
|
|
290
|
+
model_name = ""
|
|
291
|
+
if hasattr(deps, "llm_model") and deps.llm_model is not None:
|
|
292
|
+
model_name = deps.llm_model.name
|
|
293
|
+
is_gpt5 = ( # streaming is likely not supported for gpt5. It varies between keys.
|
|
294
|
+
"gpt-5" in model_name.lower()
|
|
191
295
|
)
|
|
192
296
|
|
|
193
|
-
|
|
297
|
+
try:
|
|
298
|
+
result: AgentRunResult[
|
|
299
|
+
str | DeferredToolRequests
|
|
300
|
+
] = await self.current_agent.run(
|
|
301
|
+
prompt,
|
|
302
|
+
deps=deps,
|
|
303
|
+
usage_limits=usage_limits,
|
|
304
|
+
message_history=message_history,
|
|
305
|
+
deferred_tool_results=deferred_tool_results,
|
|
306
|
+
event_stream_handler=self._handle_event_stream if not is_gpt5 else None,
|
|
307
|
+
**kwargs,
|
|
308
|
+
)
|
|
309
|
+
finally:
|
|
310
|
+
# If the stream ended unexpectedly without a final result, clear accumulated state.
|
|
311
|
+
if self._stream_state is not None and not self._stream_state.final_sent:
|
|
312
|
+
partial_message = self._build_partial_response(self._stream_state.parts)
|
|
313
|
+
if partial_message is not None:
|
|
314
|
+
self._post_partial_message(partial_message, True)
|
|
315
|
+
self._stream_state = None
|
|
316
|
+
|
|
194
317
|
self.ui_message_history = self.ui_message_history + [
|
|
195
318
|
mes for mes in result.new_messages() if not isinstance(mes, ModelRequest)
|
|
196
319
|
]
|
|
197
320
|
|
|
198
321
|
# Apply compaction to persistent message history to prevent cascading growth
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
)
|
|
322
|
+
all_messages = result.all_messages()
|
|
323
|
+
self.message_history = await apply_persistent_compaction(all_messages, deps)
|
|
202
324
|
|
|
203
325
|
# Log file operations summary if any files were modified
|
|
204
|
-
|
|
326
|
+
file_operations = deps.file_tracker.operations.copy()
|
|
327
|
+
self.recently_change_files = file_operations
|
|
205
328
|
|
|
206
|
-
self._post_messages_updated(
|
|
329
|
+
self._post_messages_updated(file_operations)
|
|
207
330
|
|
|
208
331
|
return result
|
|
209
332
|
|
|
333
|
+
async def _handle_event_stream(
|
|
334
|
+
self,
|
|
335
|
+
_ctx: RunContext[AgentDeps],
|
|
336
|
+
stream: AsyncIterable[AgentStreamEvent],
|
|
337
|
+
) -> None:
|
|
338
|
+
"""Process streamed events and forward partial updates to the UI."""
|
|
339
|
+
|
|
340
|
+
state = self._stream_state
|
|
341
|
+
if state is None:
|
|
342
|
+
state = self._stream_state = _PartialStreamState()
|
|
343
|
+
|
|
344
|
+
partial_parts = state.parts
|
|
345
|
+
|
|
346
|
+
async for event in stream:
|
|
347
|
+
try:
|
|
348
|
+
if isinstance(event, PartStartEvent):
|
|
349
|
+
index = event.index
|
|
350
|
+
if index < len(partial_parts):
|
|
351
|
+
partial_parts[index] = event.part
|
|
352
|
+
elif index == len(partial_parts):
|
|
353
|
+
partial_parts.append(event.part)
|
|
354
|
+
else:
|
|
355
|
+
logger.warning(
|
|
356
|
+
"Received PartStartEvent with out-of-bounds index",
|
|
357
|
+
extra={"index": index, "current_len": len(partial_parts)},
|
|
358
|
+
)
|
|
359
|
+
partial_parts.append(event.part)
|
|
360
|
+
|
|
361
|
+
partial_message = self._build_partial_response(partial_parts)
|
|
362
|
+
if partial_message is not None:
|
|
363
|
+
state.latest_partial = partial_message
|
|
364
|
+
self._post_partial_message(partial_message, False)
|
|
365
|
+
|
|
366
|
+
elif isinstance(event, PartDeltaEvent):
|
|
367
|
+
index = event.index
|
|
368
|
+
if index >= len(partial_parts):
|
|
369
|
+
logger.warning(
|
|
370
|
+
"Received PartDeltaEvent before corresponding start event",
|
|
371
|
+
extra={"index": index, "current_len": len(partial_parts)},
|
|
372
|
+
)
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
updated_part = event.delta.apply(
|
|
377
|
+
cast(ModelResponsePart, partial_parts[index])
|
|
378
|
+
)
|
|
379
|
+
except Exception: # pragma: no cover - defensive logging
|
|
380
|
+
logger.exception(
|
|
381
|
+
"Failed to apply part delta", extra={"event": event}
|
|
382
|
+
)
|
|
383
|
+
continue
|
|
384
|
+
|
|
385
|
+
partial_parts[index] = updated_part
|
|
386
|
+
|
|
387
|
+
partial_message = self._build_partial_response(partial_parts)
|
|
388
|
+
if partial_message is not None:
|
|
389
|
+
state.latest_partial = partial_message
|
|
390
|
+
self._post_partial_message(partial_message, False)
|
|
391
|
+
|
|
392
|
+
elif isinstance(event, FinalResultEvent):
|
|
393
|
+
final_message = (
|
|
394
|
+
state.latest_partial
|
|
395
|
+
or self._build_partial_response(partial_parts)
|
|
396
|
+
)
|
|
397
|
+
self._post_partial_message(final_message, True)
|
|
398
|
+
state.latest_partial = None
|
|
399
|
+
state.final_sent = True
|
|
400
|
+
partial_parts.clear()
|
|
401
|
+
self._stream_state = None
|
|
402
|
+
break
|
|
403
|
+
|
|
404
|
+
# Ignore other AgentStreamEvent variants (e.g. tool call notifications) for partial UI updates.
|
|
405
|
+
|
|
406
|
+
except Exception: # pragma: no cover - defensive logging
|
|
407
|
+
logger.exception(
|
|
408
|
+
"Error while handling agent stream event", extra={"event": event}
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def _build_partial_response(
|
|
412
|
+
self, parts: list[ModelResponsePart | ToolCallPartDelta]
|
|
413
|
+
) -> ModelResponse | None:
|
|
414
|
+
"""Create a `ModelResponse` from the currently streamed parts."""
|
|
415
|
+
|
|
416
|
+
completed_parts = [
|
|
417
|
+
part for part in parts if not isinstance(part, ToolCallPartDelta)
|
|
418
|
+
]
|
|
419
|
+
if not completed_parts:
|
|
420
|
+
return None
|
|
421
|
+
return ModelResponse(parts=list(completed_parts))
|
|
422
|
+
|
|
423
|
+
def _post_partial_message(
|
|
424
|
+
self, message: ModelResponse | None, is_last: bool
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Post a partial message to the UI."""
|
|
427
|
+
self.post_message(PartialResponseMessage(message, is_last))
|
|
428
|
+
|
|
210
429
|
def _post_messages_updated(
|
|
211
430
|
self, file_operations: list[FileOperation] | None = None
|
|
212
431
|
) -> None:
|
|
@@ -347,14 +347,13 @@ async def read_artifact_section(
|
|
|
347
347
|
|
|
348
348
|
section = service.get_section(artifact_id, mode, section_number)
|
|
349
349
|
|
|
350
|
-
# Return
|
|
351
|
-
formatted_content = f"# {section.title}\n\n{section.content}"
|
|
350
|
+
# Return section content (already contains title header from file storage)
|
|
352
351
|
logger.debug(
|
|
353
352
|
"📄 Read section %d with %d characters",
|
|
354
353
|
section_number,
|
|
355
354
|
len(section.content),
|
|
356
355
|
)
|
|
357
|
-
return
|
|
356
|
+
return section.content
|
|
358
357
|
|
|
359
358
|
except Exception as e:
|
|
360
359
|
error_msg = (
|
|
@@ -15,16 +15,16 @@ sections:
|
|
|
15
15
|
instructions: |
|
|
16
16
|
# What is a good research.overview section?
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
* Clarifies the technical need
|
|
19
|
+
* Establishes scope and constraints
|
|
20
|
+
* Aligns the team on evaluation criteria
|
|
21
|
+
* Sets realistic timeline for decision
|
|
22
22
|
|
|
23
23
|
# Questions to ask yourself:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
* What problem are we trying to solve with this SDK/library?
|
|
25
|
+
* What happens if we make the wrong choice?
|
|
26
|
+
* What are our non-negotiable requirements?
|
|
27
|
+
* Is there anything specific to our tech stack that constrains choices?
|
|
28
28
|
|
|
29
29
|
# Includes:
|
|
30
30
|
Research Objective: [One clear sentence stating what SDK/library type we need and why]
|
|
@@ -50,36 +50,36 @@ sections:
|
|
|
50
50
|
|
|
51
51
|
Define technical and business requirements that will guide the evaluation.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
* Separates must-haves from nice-to-haves
|
|
54
|
+
* Includes both functional and non-functional requirements
|
|
55
|
+
* Considers team capabilities and constraints
|
|
56
56
|
|
|
57
57
|
# Questions to ask yourself:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
* What features are absolutely essential vs. "would be nice"?
|
|
59
|
+
* What are our technical constraints (language, platform, licenses)?
|
|
60
|
+
* What are our resource constraints (budget, team expertise)?
|
|
61
61
|
|
|
62
62
|
# Includes:
|
|
63
63
|
## Functional Requirements
|
|
64
64
|
Essential Features:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
* [Core functionality needed]
|
|
66
|
+
* [Integration requirements]
|
|
67
|
+
* [Performance benchmarks]
|
|
68
68
|
|
|
69
69
|
Nice-to-Have Features:
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
* [Additional capabilities]
|
|
71
|
+
* [Future-proofing considerations]
|
|
72
72
|
|
|
73
73
|
## Non-Functional Requirements
|
|
74
74
|
Technical Constraints:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
* Language/Platform: [e.g., Must support TypeScript]
|
|
76
|
+
* Architecture: [e.g., Must work in serverless environment]
|
|
77
|
+
* Performance: [e.g., < 50ms latency for operations]
|
|
78
78
|
|
|
79
79
|
Business Constraints:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
* License: [e.g., MIT or Apache 2.0 preferred]
|
|
81
|
+
* Cost: [e.g., Free for commercial use or < $X/month]
|
|
82
|
+
* Support: [e.g., Active community or paid support available]
|
|
83
83
|
depends_on:
|
|
84
84
|
- "research.overview"
|
|
85
85
|
|
|
@@ -89,21 +89,21 @@ sections:
|
|
|
89
89
|
|
|
90
90
|
Document the process and results of finding available options.
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
* Shows comprehensive search methodology
|
|
93
|
+
* Lists all viable candidates with brief descriptions
|
|
94
|
+
* Explains why certain options were excluded early
|
|
95
95
|
|
|
96
96
|
# Questions to ask yourself:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
* Where did we search? (GitHub, npm, package managers, forums)
|
|
98
|
+
* What keywords and criteria did we use?
|
|
99
|
+
* Are there any industry-standard or popular choices we're missing?
|
|
100
100
|
|
|
101
101
|
# Includes:
|
|
102
102
|
## Search Methodology
|
|
103
103
|
Sources Consulted:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
* [Package registries searched]
|
|
105
|
+
* [Community recommendations]
|
|
106
|
+
* [Industry reports/comparisons referenced]
|
|
107
107
|
|
|
108
108
|
## Initial Candidates
|
|
109
109
|
|
|
@@ -115,8 +115,8 @@ sections:
|
|
|
115
115
|
|
|
116
116
|
## Early Eliminations
|
|
117
117
|
Excluded Options:
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
* **[Library Name]**: [Reason for exclusion]
|
|
119
|
+
* **[Library Name]**: [Critical missing feature or constraint violation]
|
|
120
120
|
|
|
121
121
|
depends_on:
|
|
122
122
|
- "research.requirements"
|
|
@@ -127,23 +127,23 @@ sections:
|
|
|
127
127
|
|
|
128
128
|
Deep technical analysis of the top candidates.
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
* Provides objective, measurable comparisons
|
|
131
|
+
* Includes code examples and API comparisons
|
|
132
|
+
* Evaluates developer experience and learning curve
|
|
133
133
|
|
|
134
134
|
# Questions to ask yourself:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
* How easy is it to implement our use case in each option?
|
|
136
|
+
* What are the performance characteristics under our expected load?
|
|
137
|
+
* How well does each option integrate with our existing stack?
|
|
138
138
|
|
|
139
139
|
# Includes:
|
|
140
140
|
## Evaluated Options (Top 3-5 candidates)
|
|
141
141
|
|
|
142
142
|
### **Option A: [Name]**
|
|
143
143
|
Technical Architecture:
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
* Core design philosophy
|
|
145
|
+
* Key abstractions and patterns
|
|
146
|
+
* Dependencies and size
|
|
147
147
|
|
|
148
148
|
Code Example:
|
|
149
149
|
```[language]
|
|
@@ -151,15 +151,15 @@ sections:
|
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
Performance Metrics:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
* Benchmark results
|
|
155
|
+
* Memory footprint
|
|
156
|
+
* Bundle size impact
|
|
157
157
|
|
|
158
158
|
Developer Experience:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
* Setup complexity: [Rating]
|
|
160
|
+
* Documentation quality: [Rating]
|
|
161
|
+
* API design: [Rating]
|
|
162
|
+
* Debugging tools: [Rating]
|
|
163
163
|
|
|
164
164
|
[Repeat for each candidate]
|
|
165
165
|
|
|
@@ -172,21 +172,21 @@ sections:
|
|
|
172
172
|
|
|
173
173
|
Side-by-side comparison of all evaluation criteria.
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
* Enables quick visual comparison
|
|
176
|
+
* Uses consistent scoring methodology
|
|
177
|
+
* Weights criteria by importance
|
|
178
178
|
|
|
179
179
|
# Questions to ask yourself:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
* Are we comparing apples to apples?
|
|
181
|
+
* Is our scoring methodology clear and reproducible?
|
|
182
|
+
* Have we weighted criteria appropriately for our use case?
|
|
183
183
|
|
|
184
184
|
# Includes:
|
|
185
185
|
## Scoring Methodology
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
* 🟢 Excellent (3 points): Exceeds requirements
|
|
187
|
+
* 🟡 Good (2 points): Meets requirements
|
|
188
|
+
* 🔴 Poor (1 point): Below requirements
|
|
189
|
+
* ❌ Missing (0 points): Doesn't support
|
|
190
190
|
|
|
191
191
|
## Feature Comparison
|
|
192
192
|
|
|
@@ -215,14 +215,14 @@ sections:
|
|
|
215
215
|
|
|
216
216
|
Clear recommendation with thorough justification.
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
* States the recommendation unambiguously
|
|
219
|
+
* Provides clear rationale tied to requirements
|
|
220
|
+
* Addresses concerns and mitigation strategies
|
|
221
221
|
|
|
222
222
|
# Questions to ask yourself:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
* Will stakeholders understand why we chose this option?
|
|
224
|
+
* Have we addressed all major concerns about our choice?
|
|
225
|
+
* Is our decision reversible if needed?
|
|
226
226
|
|
|
227
227
|
# Includes:
|
|
228
228
|
## Recommendation
|
|
@@ -236,12 +236,12 @@ sections:
|
|
|
236
236
|
## Detailed Justification
|
|
237
237
|
|
|
238
238
|
### Why [Selected] Over [Alternative A]
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
* [Specific technical advantage]
|
|
240
|
+
* [Business consideration]
|
|
241
|
+
* [Risk mitigation]
|
|
242
242
|
|
|
243
243
|
### Why [Selected] Over [Alternative B]
|
|
244
|
-
|
|
244
|
+
* [Comparison points]
|
|
245
245
|
|
|
246
246
|
## Risk Mitigation Plan
|
|
247
247
|
|
|
@@ -250,7 +250,7 @@ sections:
|
|
|
250
250
|
| [Specific risk] | [How we'll address it] | [Team/Person] |
|
|
251
251
|
|
|
252
252
|
## Dissenting Opinions
|
|
253
|
-
|
|
253
|
+
* [Any team concerns and how they were addressed]
|
|
254
254
|
|
|
255
255
|
depends_on:
|
|
256
256
|
- "research.comparison_matrix"
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
!!! CRITICALLY IMPORTANT !!!
|
|
5
|
+
|
|
1
6
|
{% if interactive_mode -%}
|
|
2
7
|
IMPORTANT: USER INTERACTION IS ENABLED (interactive mode).
|
|
3
8
|
|
|
9
|
+
- BEFORE GETTING TO WORK, ALWAYS THINK WHAT YOU'RE GOING TO DO AND ask_user() TO ACCEPT WHAT YOU'RE GOING TO DO.
|
|
10
|
+
- ALWAYS USE ask_user TO REVIEW AND ACCEPT THE ARTIFACT SECTION YOU'VE WORKED ON BEFORE PROCEEDING TO THE NEXT SECTION.
|
|
4
11
|
- Don't assume - ask for confirmation of your understanding
|
|
5
12
|
- When in doubt about any aspect of the goal, ASK before proceeding
|
|
6
13
|
|
|
@@ -13,4 +20,6 @@ IMPORTANT: USER INTERACTION IS DISABLED (non-interactive mode).
|
|
|
13
20
|
- Make reasonable assumptions based on industry best practices
|
|
14
21
|
- Use sensible defaults when specific details are not provided
|
|
15
22
|
- When in doubt, make reasonable assumptions and proceed with best practices
|
|
16
|
-
{% endif %}
|
|
23
|
+
{% endif %}
|
|
24
|
+
|
|
25
|
+
|