tapps-agents 3.5.39__py3-none-any.whl → 3.5.41__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.
Files changed (71) hide show
  1. tapps_agents/__init__.py +2 -2
  2. tapps_agents/agents/enhancer/agent.py +2728 -2728
  3. tapps_agents/agents/implementer/agent.py +35 -13
  4. tapps_agents/agents/reviewer/agent.py +43 -10
  5. tapps_agents/agents/reviewer/scoring.py +59 -68
  6. tapps_agents/agents/reviewer/tools/__init__.py +24 -0
  7. tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -0
  8. tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -0
  9. tapps_agents/beads/__init__.py +11 -0
  10. tapps_agents/beads/hydration.py +213 -0
  11. tapps_agents/beads/specs.py +206 -0
  12. tapps_agents/cli/commands/health.py +19 -3
  13. tapps_agents/cli/commands/simple_mode.py +842 -676
  14. tapps_agents/cli/commands/task.py +227 -0
  15. tapps_agents/cli/commands/top_level.py +13 -0
  16. tapps_agents/cli/main.py +658 -651
  17. tapps_agents/cli/parsers/top_level.py +1978 -1881
  18. tapps_agents/core/config.py +1622 -1622
  19. tapps_agents/core/init_project.py +3012 -2897
  20. tapps_agents/epic/markdown_sync.py +105 -0
  21. tapps_agents/epic/orchestrator.py +1 -2
  22. tapps_agents/epic/parser.py +427 -423
  23. tapps_agents/experts/adaptive_domain_detector.py +0 -2
  24. tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +15 -15
  25. tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +19 -44
  26. tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
  27. tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
  28. tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
  29. tapps_agents/health/checks/outcomes.py +134 -46
  30. tapps_agents/health/orchestrator.py +12 -4
  31. tapps_agents/hooks/__init__.py +33 -0
  32. tapps_agents/hooks/config.py +140 -0
  33. tapps_agents/hooks/events.py +135 -0
  34. tapps_agents/hooks/executor.py +128 -0
  35. tapps_agents/hooks/manager.py +143 -0
  36. tapps_agents/session/__init__.py +19 -0
  37. tapps_agents/session/manager.py +256 -0
  38. tapps_agents/simple_mode/code_snippet_handler.py +382 -0
  39. tapps_agents/simple_mode/intent_parser.py +29 -4
  40. tapps_agents/simple_mode/orchestrators/base.py +185 -59
  41. tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2667 -2642
  42. tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +2 -2
  43. tapps_agents/simple_mode/workflow_suggester.py +37 -3
  44. tapps_agents/workflow/agent_handlers/implementer_handler.py +18 -3
  45. tapps_agents/workflow/cursor_executor.py +2337 -2118
  46. tapps_agents/workflow/direct_execution_fallback.py +16 -3
  47. tapps_agents/workflow/message_formatter.py +2 -1
  48. tapps_agents/workflow/models.py +38 -1
  49. tapps_agents/workflow/parallel_executor.py +43 -4
  50. tapps_agents/workflow/parser.py +375 -357
  51. tapps_agents/workflow/rules_generator.py +337 -337
  52. tapps_agents/workflow/skill_invoker.py +9 -3
  53. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/METADATA +5 -1
  54. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/RECORD +58 -54
  55. tapps_agents/agents/analyst/SKILL.md +0 -85
  56. tapps_agents/agents/architect/SKILL.md +0 -80
  57. tapps_agents/agents/debugger/SKILL.md +0 -66
  58. tapps_agents/agents/designer/SKILL.md +0 -78
  59. tapps_agents/agents/documenter/SKILL.md +0 -95
  60. tapps_agents/agents/enhancer/SKILL.md +0 -189
  61. tapps_agents/agents/implementer/SKILL.md +0 -117
  62. tapps_agents/agents/improver/SKILL.md +0 -55
  63. tapps_agents/agents/ops/SKILL.md +0 -64
  64. tapps_agents/agents/orchestrator/SKILL.md +0 -238
  65. tapps_agents/agents/planner/story_template.md +0 -37
  66. tapps_agents/agents/reviewer/templates/quality-dashboard.html.j2 +0 -150
  67. tapps_agents/agents/tester/SKILL.md +0 -71
  68. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/WHEEL +0 -0
  69. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/entry_points.txt +0 -0
  70. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/licenses/LICENSE +0 -0
  71. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.41.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,382 @@
1
+ """
2
+ Code Snippet Handler - Detect and handle pasted code in user input.
3
+
4
+ Detects markdown code blocks, creates temporary files in scratchpad directory,
5
+ and integrates with workflow suggester for automatic *fix workflow invocation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import logging
12
+ import re
13
+ import time
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ # Language to file extension mapping
22
+ LANGUAGE_EXTENSIONS: dict[str, str] = {
23
+ "python": ".py",
24
+ "py": ".py",
25
+ "javascript": ".js",
26
+ "js": ".js",
27
+ "typescript": ".ts",
28
+ "ts": ".ts",
29
+ "java": ".java",
30
+ "go": ".go",
31
+ "rust": ".rs",
32
+ "rs": ".rs",
33
+ "c": ".c",
34
+ "cpp": ".cpp",
35
+ "c++": ".cpp",
36
+ "csharp": ".cs",
37
+ "cs": ".cs",
38
+ "ruby": ".rb",
39
+ "rb": ".rb",
40
+ "php": ".php",
41
+ "swift": ".swift",
42
+ "kotlin": ".kt",
43
+ "scala": ".scala",
44
+ "shell": ".sh",
45
+ "bash": ".sh",
46
+ "sh": ".sh",
47
+ "sql": ".sql",
48
+ "html": ".html",
49
+ "css": ".css",
50
+ "json": ".json",
51
+ "yaml": ".yaml",
52
+ "yml": ".yml",
53
+ "xml": ".xml",
54
+ "markdown": ".md",
55
+ "md": ".md",
56
+ "text": ".txt",
57
+ "txt": ".txt",
58
+ }
59
+
60
+
61
+ # Markdown code fence pattern
62
+ MARKDOWN_CODE_FENCE_PATTERN = r"```(?P<lang>\w+)?\s*\n(?P<code>.*?)```"
63
+
64
+
65
+ @dataclass(frozen=True, slots=True)
66
+ class CodeSnippet:
67
+ """
68
+ Detected code snippet with metadata.
69
+
70
+ Attributes:
71
+ code: The extracted code content
72
+ language: Detected language (or 'txt' if unknown)
73
+ extension: File extension for the language
74
+ confidence: Detection confidence (0.0-1.0)
75
+ """
76
+
77
+ code: str
78
+ language: str
79
+ extension: str
80
+ confidence: float
81
+
82
+
83
+ @dataclass(frozen=True, slots=True)
84
+ class TempFile:
85
+ """
86
+ Temporary file information.
87
+
88
+ Attributes:
89
+ path: Full path to the temporary file
90
+ filename: Just the filename
91
+ language: Detected language
92
+ created_at: Timestamp when file was created
93
+ """
94
+
95
+ path: Path
96
+ filename: str
97
+ language: str
98
+ created_at: float
99
+
100
+
101
+ class CodeSnippetHandler:
102
+ """
103
+ Handler for detecting and processing pasted code snippets.
104
+
105
+ Detects markdown code blocks in user input, creates temporary files
106
+ in the scratchpad directory, and prepares for workflow integration.
107
+ """
108
+
109
+ def __init__(self, scratchpad_dir: Optional[Path] = None):
110
+ """
111
+ Initialize code snippet handler.
112
+
113
+ Args:
114
+ scratchpad_dir: Path to scratchpad directory for temp files.
115
+ If None, uses default Claude Code scratchpad location.
116
+ """
117
+ self._logger = logging.getLogger(__name__)
118
+
119
+ # Use provided scratchpad or default location
120
+ if scratchpad_dir is None:
121
+ # Default Claude Code scratchpad location
122
+ import tempfile
123
+ import os
124
+
125
+ base_temp = Path(tempfile.gettempdir())
126
+ claude_dir = base_temp / "claude"
127
+
128
+ # Try to find existing Claude directory with session ID
129
+ if claude_dir.exists():
130
+ # Look for subdirectories (session IDs)
131
+ session_dirs = [d for d in claude_dir.iterdir() if d.is_dir()]
132
+ if session_dirs:
133
+ # Use first session directory found
134
+ scratchpad_dir = session_dirs[0] / "scratchpad"
135
+ else:
136
+ # Create default session directory
137
+ scratchpad_dir = claude_dir / "default" / "scratchpad"
138
+ else:
139
+ # Create default Claude directory structure
140
+ scratchpad_dir = claude_dir / "default" / "scratchpad"
141
+
142
+ self.scratchpad_dir = Path(scratchpad_dir)
143
+ self._logger.debug(f"Scratchpad directory: {self.scratchpad_dir}")
144
+
145
+ # Ensure scratchpad directory exists
146
+ try:
147
+ self.scratchpad_dir.mkdir(parents=True, exist_ok=True)
148
+ except Exception as e:
149
+ self._logger.warning(
150
+ f"Could not create scratchpad directory {self.scratchpad_dir}: {e}"
151
+ )
152
+
153
+ def detect_code_snippet(self, user_input: str) -> Optional[CodeSnippet]:
154
+ """
155
+ Detect code snippet in user input.
156
+
157
+ Searches for markdown code fences (```lang...```) and extracts
158
+ the code content and language.
159
+
160
+ Args:
161
+ user_input: User's natural language input
162
+
163
+ Returns:
164
+ CodeSnippet if code block detected, None otherwise
165
+
166
+ Example:
167
+ >>> handler = CodeSnippetHandler()
168
+ >>> result = handler.detect_code_snippet('''
169
+ ... Fix this code:
170
+ ... ```python
171
+ ... def add(a, b):
172
+ ... return a / b
173
+ ... ```
174
+ ... ''')
175
+ >>> result.language
176
+ 'python'
177
+ >>> result.code
178
+ 'def add(a, b):\\n return a / b\\n'
179
+ """
180
+ if not user_input or not isinstance(user_input, str):
181
+ return None
182
+
183
+ try:
184
+ # Search for markdown code fence
185
+ match = re.search(
186
+ MARKDOWN_CODE_FENCE_PATTERN,
187
+ user_input,
188
+ re.DOTALL | re.IGNORECASE
189
+ )
190
+
191
+ if not match:
192
+ return None
193
+
194
+ # Extract language and code
195
+ language = match.group("lang")
196
+ code = match.group("code")
197
+
198
+ # Normalize language
199
+ if language:
200
+ language = language.lower().strip()
201
+ else:
202
+ language = "txt" # Default if no language specified
203
+
204
+ # Get file extension
205
+ extension = LANGUAGE_EXTENSIONS.get(language, ".txt")
206
+
207
+ # Validate code is not empty
208
+ if not code or not code.strip():
209
+ self._logger.debug("Code block detected but empty")
210
+ return None
211
+
212
+ # High confidence if language specified and code non-empty
213
+ confidence = 0.95 if match.group("lang") else 0.80
214
+
215
+ return CodeSnippet(
216
+ code=code.strip(),
217
+ language=language,
218
+ extension=extension,
219
+ confidence=confidence
220
+ )
221
+
222
+ except Exception as e:
223
+ self._logger.error(f"Error detecting code snippet: {e}", exc_info=True)
224
+ return None
225
+
226
+ def generate_temp_filename(
227
+ self,
228
+ language: str,
229
+ extension: str,
230
+ code_content: str
231
+ ) -> str:
232
+ """
233
+ Generate unique temporary filename.
234
+
235
+ Uses timestamp and hash of code content to ensure uniqueness.
236
+
237
+ Args:
238
+ language: Detected language
239
+ extension: File extension
240
+ code_content: The code content (used for hash)
241
+
242
+ Returns:
243
+ Unique filename like "pasted_code_1234567890_abc123.py"
244
+
245
+ Example:
246
+ >>> handler = CodeSnippetHandler()
247
+ >>> filename = handler.generate_temp_filename("python", ".py", "code")
248
+ >>> filename.startswith("pasted_code_")
249
+ True
250
+ >>> filename.endswith(".py")
251
+ True
252
+ """
253
+ # Generate timestamp
254
+ timestamp = int(time.time())
255
+
256
+ # Generate hash of code content
257
+ code_hash = hashlib.md5(code_content.encode('utf-8')).hexdigest()[:8]
258
+
259
+ # Construct filename
260
+ filename = f"pasted_code_{timestamp}_{code_hash}{extension}"
261
+
262
+ return filename
263
+
264
+ def create_temp_file(self, snippet: CodeSnippet) -> Optional[TempFile]:
265
+ """
266
+ Create temporary file with code snippet content.
267
+
268
+ Writes the code snippet to a uniquely named file in the
269
+ scratchpad directory.
270
+
271
+ Args:
272
+ snippet: CodeSnippet to write to file
273
+
274
+ Returns:
275
+ TempFile with file information, or None if creation failed
276
+
277
+ Example:
278
+ >>> handler = CodeSnippetHandler()
279
+ >>> snippet = CodeSnippet(
280
+ ... code="print('hello')",
281
+ ... language="python",
282
+ ... extension=".py",
283
+ ... confidence=0.95
284
+ ... )
285
+ >>> temp_file = handler.create_temp_file(snippet)
286
+ >>> temp_file.path.exists()
287
+ True
288
+ """
289
+ try:
290
+ # Generate unique filename
291
+ filename = self.generate_temp_filename(
292
+ snippet.language,
293
+ snippet.extension,
294
+ snippet.code
295
+ )
296
+
297
+ # Construct full path
298
+ file_path = self.scratchpad_dir / filename
299
+
300
+ # Write code to file
301
+ file_path.write_text(snippet.code, encoding='utf-8')
302
+
303
+ self._logger.info(f"Created temp file: {file_path}")
304
+
305
+ return TempFile(
306
+ path=file_path,
307
+ filename=filename,
308
+ language=snippet.language,
309
+ created_at=time.time()
310
+ )
311
+
312
+ except Exception as e:
313
+ self._logger.error(
314
+ f"Failed to create temp file: {e}",
315
+ exc_info=True
316
+ )
317
+ return None
318
+
319
+ def detect_and_create_temp_file(
320
+ self,
321
+ user_input: str
322
+ ) -> Optional[TempFile]:
323
+ """
324
+ Detect code snippet and create temporary file in one step.
325
+
326
+ Convenience method that combines detection and file creation.
327
+
328
+ Args:
329
+ user_input: User's natural language input
330
+
331
+ Returns:
332
+ TempFile if code detected and file created, None otherwise
333
+
334
+ Example:
335
+ >>> handler = CodeSnippetHandler()
336
+ >>> temp_file = handler.detect_and_create_temp_file('''
337
+ ... ```python
338
+ ... def hello():
339
+ ... print("world")
340
+ ... ```
341
+ ... ''')
342
+ >>> temp_file is not None
343
+ True
344
+ """
345
+ # Detect code snippet
346
+ snippet = self.detect_code_snippet(user_input)
347
+ if snippet is None:
348
+ return None
349
+
350
+ # Create temp file
351
+ temp_file = self.create_temp_file(snippet)
352
+ return temp_file
353
+
354
+
355
+ # Convenience function for workflow integration
356
+ def detect_pasted_code(user_input: str) -> Optional[TempFile]:
357
+ """
358
+ Detect pasted code and create temporary file.
359
+
360
+ Convenience function for use in workflow suggester and other modules.
361
+
362
+ Args:
363
+ user_input: User's natural language input
364
+
365
+ Returns:
366
+ TempFile if code detected and file created, None otherwise
367
+
368
+ Example:
369
+ >>> temp_file = detect_pasted_code('''
370
+ ... Fix this code:
371
+ ... ```python
372
+ ... def bad_code():
373
+ ... return 1 / 0
374
+ ... ```
375
+ ... ''')
376
+ >>> temp_file is not None
377
+ True
378
+ >>> temp_file.language
379
+ 'python'
380
+ """
381
+ handler = CodeSnippetHandler()
382
+ return handler.detect_and_create_temp_file(user_input)
@@ -379,17 +379,42 @@ class IntentParser:
379
379
  intent_type = IntentType.UNKNOWN
380
380
  confidence = 0.0
381
381
 
382
- # Detect "compare to codebase" intent
382
+ # Detect "compare to codebase" intent with enhanced semantics
383
383
  compare_to_codebase = False
384
384
  compare_phrases = [
385
+ # Direct compare phrases
385
386
  "compare to",
386
387
  "compare with",
388
+ "compare against",
389
+ "compare this to",
390
+ "compare this with",
391
+ "compare to codebase",
392
+ "compare to our",
393
+ "compare to project",
394
+ # Match/align phrases
387
395
  "match our",
396
+ "match the",
397
+ "match patterns",
398
+ "match codebase",
388
399
  "align with",
400
+ "align to",
389
401
  "follow patterns",
390
- "match patterns",
391
- "compare to codebase",
392
- "compare to our",
402
+ "follow our patterns",
403
+ "follow project patterns",
404
+ # Make match phrases (implicit compare)
405
+ "make match",
406
+ "make this match",
407
+ "make it match",
408
+ # Consistency phrases
409
+ "consistent with",
410
+ "consistency with",
411
+ "conform to",
412
+ "conform with",
413
+ # Standard phrases
414
+ "match standards",
415
+ "follow standards",
416
+ "meet standards",
417
+ "adhere to",
393
418
  ]
394
419
  if any(phrase in input_lower for phrase in compare_phrases):
395
420
  compare_to_codebase = True
@@ -1,59 +1,185 @@
1
- """
2
- Base orchestrator interface for Simple Mode.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- from abc import ABC, abstractmethod
8
- from pathlib import Path
9
- from typing import TYPE_CHECKING, Any
10
-
11
- from tapps_agents.core.config import ProjectConfig
12
-
13
- if TYPE_CHECKING:
14
- from ..intent_parser import Intent
15
-
16
-
17
- class SimpleModeOrchestrator(ABC):
18
- """Base class for Simple Mode orchestrators."""
19
-
20
- def __init__(
21
- self,
22
- project_root: Path | None = None,
23
- config: ProjectConfig | None = None,
24
- ):
25
- """
26
- Initialize orchestrator.
27
-
28
- Args:
29
- project_root: Project root directory
30
- config: Optional project configuration
31
- """
32
- self.project_root = project_root or Path.cwd()
33
- self.config = config
34
-
35
- @abstractmethod
36
- async def execute(
37
- self, intent: Intent, parameters: dict[str, Any] | None = None
38
- ) -> dict[str, Any]:
39
- """
40
- Execute the orchestrator's workflow.
41
-
42
- Args:
43
- intent: Parsed user intent
44
- parameters: Additional parameters from user input
45
-
46
- Returns:
47
- Dictionary with execution results
48
- """
49
- pass
50
-
51
- def get_agent_sequence(self) -> list[str]:
52
- """
53
- Get the sequence of agents this orchestrator coordinates.
54
-
55
- Returns:
56
- List of agent names in execution order
57
- """
58
- return []
59
-
1
+ """
2
+ Base orchestrator interface for Simple Mode.
3
+
4
+ Integrates optional hook system: UserPromptSubmit before workflow,
5
+ PostToolUse after implementer tool use, WorkflowComplete after workflow ends.
6
+ Hooks are opt-in (no behavior change when hooks disabled).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from abc import ABC, abstractmethod
13
+ from pathlib import Path
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from tapps_agents.core.config import ProjectConfig
17
+
18
+ if TYPE_CHECKING:
19
+ from ..intent_parser import Intent
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def _get_hook_manager(project_root: Path) -> Any:
25
+ """
26
+ Load HookManager for project if hooks are configured.
27
+
28
+ Returns None when hooks.yaml is missing or has no enabled hooks (opt-in).
29
+ """
30
+ try:
31
+ from tapps_agents.hooks.config import load_hooks_config
32
+ from tapps_agents.hooks.manager import HookManager
33
+
34
+ config = load_hooks_config(project_root=project_root)
35
+ has_any = any(
36
+ any(h.enabled for h in hooks)
37
+ for hooks in config.hooks.values()
38
+ )
39
+ if not has_any:
40
+ return None
41
+ return HookManager(project_root=project_root)
42
+ except Exception as e: # pylint: disable=broad-except
43
+ logger.debug("Hooks not loaded (opt-in): %s", e)
44
+ return None
45
+
46
+
47
+ class SimpleModeOrchestrator(ABC):
48
+ """Base class for Simple Mode orchestrators."""
49
+
50
+ def __init__(
51
+ self,
52
+ project_root: Path | None = None,
53
+ config: ProjectConfig | None = None,
54
+ ):
55
+ """
56
+ Initialize orchestrator.
57
+
58
+ Args:
59
+ project_root: Project root directory
60
+ config: Optional project configuration
61
+ """
62
+ self.project_root = project_root or Path.cwd()
63
+ self.config = config
64
+ self._hook_manager = None # Lazy-loaded when needed
65
+
66
+ def _get_hook_manager(self) -> Any:
67
+ """Return HookManager if hooks are enabled for this project; else None."""
68
+ if self._hook_manager is None:
69
+ self._hook_manager = _get_hook_manager(self.project_root)
70
+ return self._hook_manager
71
+
72
+ def _trigger_user_prompt_submit(
73
+ self,
74
+ prompt: str,
75
+ workflow_type: str | None = None,
76
+ ) -> None:
77
+ """
78
+ Fire UserPromptSubmit hook before workflow execution.
79
+
80
+ No-op when hooks disabled or no hooks configured for this event.
81
+ """
82
+ mgr = self._get_hook_manager()
83
+ if not mgr:
84
+ return
85
+ try:
86
+ from tapps_agents.hooks.events import UserPromptSubmitEvent
87
+
88
+ payload = UserPromptSubmitEvent(
89
+ prompt=prompt,
90
+ project_root=str(self.project_root),
91
+ workflow_type=workflow_type,
92
+ )
93
+ mgr.trigger("UserPromptSubmit", payload)
94
+ except Exception as e: # pylint: disable=broad-except
95
+ logger.debug("UserPromptSubmit hook error (non-fatal): %s", e)
96
+
97
+ def _trigger_post_tool_use(
98
+ self,
99
+ tool_name: str,
100
+ file_path: str | None,
101
+ file_paths: list[str] | None = None,
102
+ workflow_id: str | None = None,
103
+ ) -> None:
104
+ """
105
+ Fire PostToolUse hook after implementer Write/Edit.
106
+
107
+ No-op when hooks disabled or no matching hooks.
108
+ """
109
+ mgr = self._get_hook_manager()
110
+ if not mgr:
111
+ return
112
+ try:
113
+ from tapps_agents.hooks.events import PostToolUseEvent
114
+
115
+ payload = PostToolUseEvent(
116
+ file_path=file_path,
117
+ file_paths=file_paths or ([file_path] if file_path else []),
118
+ tool_name=tool_name,
119
+ project_root=str(self.project_root),
120
+ workflow_id=workflow_id,
121
+ )
122
+ mgr.trigger(
123
+ "PostToolUse",
124
+ payload,
125
+ tool_name=tool_name,
126
+ file_path=file_path,
127
+ )
128
+ except Exception as e: # pylint: disable=broad-except
129
+ logger.debug("PostToolUse hook error (non-fatal): %s", e)
130
+
131
+ def _trigger_workflow_complete(
132
+ self,
133
+ workflow_type: str,
134
+ workflow_id: str,
135
+ status: str,
136
+ beads_issue_id: str | None = None,
137
+ ) -> None:
138
+ """
139
+ Fire WorkflowComplete hook after workflow ends.
140
+
141
+ status: 'completed', 'failed', or 'cancelled'.
142
+ No-op when hooks disabled.
143
+ """
144
+ mgr = self._get_hook_manager()
145
+ if not mgr:
146
+ return
147
+ try:
148
+ from tapps_agents.hooks.events import WorkflowCompleteEvent
149
+
150
+ payload = WorkflowCompleteEvent(
151
+ workflow_type=workflow_type,
152
+ workflow_id=workflow_id,
153
+ status=status,
154
+ project_root=str(self.project_root),
155
+ beads_issue_id=beads_issue_id,
156
+ )
157
+ mgr.trigger("WorkflowComplete", payload)
158
+ except Exception as e: # pylint: disable=broad-except
159
+ logger.debug("WorkflowComplete hook error (non-fatal): %s", e)
160
+
161
+ @abstractmethod
162
+ async def execute(
163
+ self, intent: Intent, parameters: dict[str, Any] | None = None
164
+ ) -> dict[str, Any]:
165
+ """
166
+ Execute the orchestrator's workflow.
167
+
168
+ Args:
169
+ intent: Parsed user intent
170
+ parameters: Additional parameters from user input
171
+
172
+ Returns:
173
+ Dictionary with execution results
174
+ """
175
+ pass
176
+
177
+ def get_agent_sequence(self) -> list[str]:
178
+ """
179
+ Get the sequence of agents this orchestrator coordinates.
180
+
181
+ Returns:
182
+ List of agent names in execution order
183
+ """
184
+ return []
185
+