tapps-agents 3.5.39__py3-none-any.whl → 3.5.40__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 (70) 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 +219 -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 +2196 -2118
  46. tapps_agents/workflow/direct_execution_fallback.py +16 -3
  47. tapps_agents/workflow/message_formatter.py +2 -1
  48. tapps_agents/workflow/parallel_executor.py +43 -4
  49. tapps_agents/workflow/parser.py +375 -357
  50. tapps_agents/workflow/rules_generator.py +337 -337
  51. tapps_agents/workflow/skill_invoker.py +9 -3
  52. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/METADATA +5 -1
  53. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/RECORD +57 -53
  54. tapps_agents/agents/analyst/SKILL.md +0 -85
  55. tapps_agents/agents/architect/SKILL.md +0 -80
  56. tapps_agents/agents/debugger/SKILL.md +0 -66
  57. tapps_agents/agents/designer/SKILL.md +0 -78
  58. tapps_agents/agents/documenter/SKILL.md +0 -95
  59. tapps_agents/agents/enhancer/SKILL.md +0 -189
  60. tapps_agents/agents/implementer/SKILL.md +0 -117
  61. tapps_agents/agents/improver/SKILL.md +0 -55
  62. tapps_agents/agents/ops/SKILL.md +0 -64
  63. tapps_agents/agents/orchestrator/SKILL.md +0 -238
  64. tapps_agents/agents/planner/story_template.md +0 -37
  65. tapps_agents/agents/reviewer/templates/quality-dashboard.html.j2 +0 -150
  66. tapps_agents/agents/tester/SKILL.md +0 -71
  67. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/WHEEL +0 -0
  68. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/entry_points.txt +0 -0
  69. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/licenses/LICENSE +0 -0
  70. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/top_level.txt +0 -0
@@ -1,337 +1,337 @@
1
- """
2
- Cursor Rules Generator - Generate workflow documentation from YAML definitions.
3
- """
4
-
5
- import logging
6
- from pathlib import Path
7
- from typing import Any
8
-
9
- from .models import Workflow
10
- from .parser import WorkflowParser
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- class CursorRulesGenerator:
16
- """Generate Cursor Rules documentation from workflow YAML files."""
17
-
18
- def __init__(self, project_root: Path | None = None):
19
- """
20
- Initialize the rules generator.
21
-
22
- Args:
23
- project_root: Project root directory (defaults to cwd)
24
- """
25
- if project_root is None:
26
- project_root = Path.cwd()
27
- self.project_root = project_root
28
-
29
- def find_workflow_files(self) -> list[tuple[Path, str | None]]:
30
- """
31
- Find all workflow YAML files in preset directories.
32
-
33
- Returns:
34
- List of tuples (file_path, resource_name) where resource_name is None for regular files
35
- """
36
- workflow_files: list[tuple[Path, str | None]] = []
37
-
38
- # Check project workflows/presets first
39
- project_presets = self.project_root / "workflows" / "presets"
40
- if project_presets.exists():
41
- for yaml_file in project_presets.glob("*.yaml"):
42
- workflow_files.append((yaml_file, None))
43
-
44
- # Check framework resources if project presets not found
45
- if not workflow_files:
46
- try:
47
- from importlib import resources as importlib_resources
48
-
49
- resource_path = importlib_resources.files("tapps_agents.resources.workflows.presets")
50
- if resource_path.is_dir():
51
- for entry in resource_path.iterdir():
52
- if entry.name.endswith(".yaml") and not entry.is_dir():
53
- # Store as resource name for later reading
54
- workflow_files.append((Path(entry.name), entry.name))
55
- except (ImportError, Exception) as e:
56
- logger.debug(f"Could not load workflow files from resources: {e}")
57
-
58
- return sorted(workflow_files, key=lambda x: x[0].name)
59
-
60
- def extract_quality_gates(self, workflow: Workflow) -> dict[str, Any]:
61
- """
62
- Extract quality gate thresholds from workflow steps.
63
-
64
- Args:
65
- workflow: Parsed Workflow object
66
-
67
- Returns:
68
- Dictionary with quality gate information
69
- """
70
- gates: dict[str, Any] = {}
71
-
72
- for step in workflow.steps:
73
- if step.scoring and isinstance(step.scoring, dict):
74
- thresholds = step.scoring.get("thresholds", {})
75
- if thresholds:
76
- if "overall_min" in thresholds:
77
- gates["overall"] = thresholds["overall_min"]
78
- if "security_min" in thresholds:
79
- gates["security"] = thresholds["security_min"]
80
- if "maintainability_min" in thresholds:
81
- gates["maintainability"] = thresholds["maintainability_min"]
82
-
83
- return gates
84
-
85
- def get_workflow_aliases(self, workflow_id: str) -> list[str]:
86
- """
87
- Get command aliases for a workflow ID.
88
-
89
- Args:
90
- workflow_id: Workflow ID
91
-
92
- Returns:
93
- List of aliases
94
- """
95
- alias_map: dict[str, list[str]] = {
96
- "full-sdlc": ["full", "enterprise"],
97
- "rapid-dev": ["rapid", "feature"],
98
- "fix": ["fix", "maintenance", "hotfix", "urgent", "refactor", "quick-fix"],
99
- "quality": ["quality", "improve"],
100
- "brownfield-analysis": ["brownfield"],
101
- }
102
- return alias_map.get(workflow_id, [])
103
-
104
- def generate(self, output_path: Path | None = None) -> str:
105
- """
106
- Generate Cursor Rules markdown from workflow YAML files.
107
-
108
- Args:
109
- output_path: Output file path (unused, kept for API compatibility)
110
-
111
- Returns:
112
- Generated markdown content
113
-
114
- Raises:
115
- ValueError: If no workflows are found or all workflows fail to parse
116
- """
117
- # Find workflow files
118
- workflow_files = self.find_workflow_files()
119
- if not workflow_files:
120
- logger.warning("No workflow YAML files found")
121
- raise ValueError(
122
- "No workflow YAML files found. "
123
- "Ensure workflows exist in workflows/presets/ or framework resources."
124
- )
125
-
126
- # Parse workflows
127
- workflows: list[Workflow] = []
128
- parse_errors: list[str] = []
129
-
130
- for workflow_file, resource_name in workflow_files:
131
- try:
132
- if resource_name:
133
- # Handle resource paths (from importlib.resources)
134
- try:
135
- from importlib import resources as importlib_resources
136
-
137
- resource = importlib_resources.files("tapps_agents.resources.workflows.presets")
138
- content_file = resource / resource_name
139
- if content_file.exists():
140
- content = content_file.read_text(encoding="utf-8")
141
- import yaml
142
-
143
- yaml_content = yaml.safe_load(content)
144
- workflow = WorkflowParser.parse(yaml_content, file_path=workflow_file)
145
- workflows.append(workflow)
146
- else:
147
- parse_errors.append(f"Resource file not found: {resource_name}")
148
- except Exception as e:
149
- error_msg = f"Could not parse resource workflow {workflow_file}: {e}"
150
- logger.debug(error_msg)
151
- parse_errors.append(error_msg)
152
- else:
153
- # Regular file path
154
- workflow = WorkflowParser.parse_file(workflow_file)
155
- workflows.append(workflow)
156
- except Exception as e:
157
- error_msg = f"Could not parse workflow {workflow_file}: {e}"
158
- logger.warning(error_msg)
159
- parse_errors.append(error_msg)
160
-
161
- if not workflows:
162
- error_message = "No valid workflows found"
163
- if parse_errors:
164
- error_message += f". Parse errors: {'; '.join(parse_errors[:3])}"
165
- logger.error(error_message)
166
- raise ValueError(error_message)
167
-
168
- # Sort workflows by a consistent order (5 presets)
169
- workflow_order = ["full-sdlc", "rapid-dev", "fix", "quality", "brownfield-analysis"]
170
- workflows.sort(key=lambda w: (
171
- workflow_order.index(w.id) if w.id in workflow_order else 999,
172
- w.id
173
- ))
174
-
175
- # Generate markdown
176
- lines: list[str] = []
177
- lines.append("# Workflow Presets - Quick Commands")
178
- lines.append("")
179
- lines.append("## Overview")
180
- lines.append("")
181
- lines.append(
182
- "TappsCodingAgents provides preset workflows that can be executed with simple one-word commands. "
183
- "These workflows automatically orchestrate multiple agents to complete common SDLC tasks."
184
- )
185
- lines.append("")
186
- lines.append("## Available Workflow Presets")
187
- lines.append("")
188
-
189
- # Generate sections for each workflow
190
- for i, workflow in enumerate(workflows, 1):
191
- lines.append(f"### {i}. {workflow.name}")
192
- aliases = self.get_workflow_aliases(workflow.id)
193
- if aliases:
194
- alias_text = f" (`{'` / `'.join(aliases)}`)"
195
- lines[-1] += alias_text
196
- lines.append("")
197
- lines.append(f"**Command:** `python -m tapps_agents.cli workflow {aliases[0] if aliases else workflow.id}`")
198
- lines.append("")
199
- if workflow.description:
200
- lines.append(f"**Best for:** {workflow.description}")
201
- lines.append("")
202
- lines.append("**Agent Sequence:**")
203
- for j, step in enumerate(workflow.steps, 1):
204
- agent_name = step.agent.title()
205
- action_desc = step.action.replace("_", " ").title()
206
- lines.append(f"{j}. {agent_name} - {action_desc}")
207
- lines.append("")
208
- gates = self.extract_quality_gates(workflow)
209
- if gates:
210
- lines.append("**Quality Gates:**")
211
- if "overall" in gates:
212
- lines.append(f"- Overall score: ≥{gates['overall']}")
213
- if "security" in gates:
214
- lines.append(f"- Security: ≥{gates['security']}")
215
- if "maintainability" in gates:
216
- lines.append(f"- Maintainability: ≥{gates['maintainability']}")
217
- lines.append("")
218
-
219
- # Add usage section
220
- lines.append("## Usage in Cursor AI")
221
- lines.append("")
222
- lines.append("When working in Cursor AI, you can:")
223
- lines.append("")
224
- lines.append("1. **Run workflows directly:**")
225
- lines.append(" - Type: \"Run rapid development workflow\"")
226
- lines.append(" - Or: \"Execute full SDLC pipeline\"")
227
- lines.append(" - Or: \"Start quality improvement\"")
228
- lines.append("")
229
- lines.append("2. **Use voice commands:**")
230
- lines.append(" - \"run rapid dev\"")
231
- lines.append(" - \"execute enterprise workflow\"")
232
- lines.append(" - \"start quick fix\"")
233
- lines.append("")
234
- lines.append("3. **Reference in conversations:**")
235
- lines.append(" - \"Use the rapid workflow for this feature\"")
236
- lines.append(" - \"This needs the full enterprise workflow\"")
237
- lines.append(" - \"Run the quality improvement cycle\"")
238
- lines.append("")
239
-
240
- # Add when to use section
241
- lines.append("## When to Use Each Workflow")
242
- lines.append("")
243
- for workflow in workflows:
244
- aliases = self.get_workflow_aliases(workflow.id)
245
- primary_alias = aliases[0] if aliases else workflow.id
246
- if workflow.description:
247
- # Extract use case from description
248
- use_case = workflow.description.split(".")[0] if "." in workflow.description else workflow.description
249
- lines.append(f"- **{use_case}** → `workflow {primary_alias}`")
250
- lines.append("")
251
-
252
- # Add integration section
253
- lines.append("## Integration")
254
- lines.append("")
255
- lines.append("These workflows integrate with:")
256
- lines.append("- ✅ Expert consultation (domain experts)")
257
- lines.append("- ✅ Quality gates (scoring thresholds)")
258
- lines.append("- ✅ Context tiers (token optimization)")
259
- lines.append("- ✅ Multi-agent orchestration")
260
- lines.append("- ✅ Git worktree isolation")
261
- lines.append("")
262
-
263
- # Add list command
264
- lines.append("## List All Presets")
265
- lines.append("")
266
- lines.append("```bash")
267
- lines.append("python -m tapps_agents.cli workflow list")
268
- lines.append("```")
269
- lines.append("")
270
-
271
- # Add customization section
272
- lines.append("## Customization")
273
- lines.append("")
274
- lines.append("Workflow presets are defined in `workflows/presets/`:")
275
- for workflow in workflows:
276
- lines.append(f"- `{workflow.id}.yaml`")
277
- lines.append("")
278
- lines.append("You can modify these files or create your own presets.")
279
- lines.append("")
280
-
281
- content = "\n".join(lines)
282
- return content
283
-
284
- def write(self, output_path: Path | None = None, backup: bool = True) -> Path:
285
- """
286
- Generate and write Cursor Rules markdown to file.
287
-
288
- Args:
289
- output_path: Output file path (defaults to .cursor/rules/workflow-presets.mdc)
290
- backup: Whether to backup existing file
291
-
292
- Returns:
293
- Path to written file
294
- """
295
- if output_path is None:
296
- output_path = self.project_root / ".cursor" / "rules" / "workflow-presets.mdc"
297
-
298
- # Create directory if needed
299
- output_path.parent.mkdir(parents=True, exist_ok=True)
300
-
301
- # Backup existing file
302
- if backup and output_path.exists():
303
- backup_path = output_path.with_suffix(f".mdc.backup")
304
- try:
305
- import shutil
306
-
307
- shutil.copy2(output_path, backup_path)
308
- logger.debug(f"Backed up existing rules to {backup_path}")
309
- except Exception as e:
310
- logger.warning(f"Could not backup existing rules: {e}")
311
-
312
- # Generate content
313
- try:
314
- content = self.generate()
315
- except ValueError as e:
316
- msg = str(e)
317
- if "No workflow YAML files found" in msg:
318
- logger.warning(
319
- "Could not generate workflow-presets.mdc (no YAML found). %s", msg
320
- )
321
- else:
322
- logger.error(f"Failed to generate rules: {e}")
323
- raise
324
-
325
- if not content or not content.strip():
326
- raise ValueError("Generated content is empty")
327
-
328
- # Write file
329
- try:
330
- output_path.write_text(content, encoding="utf-8")
331
- logger.info(f"Generated Cursor Rules at {output_path}")
332
- except OSError as e:
333
- logger.error(f"Failed to write rules file: {e}")
334
- raise
335
-
336
- return output_path
337
-
1
+ """
2
+ Cursor Rules Generator - Generate workflow documentation from YAML definitions.
3
+ """
4
+
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from .models import Workflow
10
+ from .parser import WorkflowParser
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CursorRulesGenerator:
16
+ """Generate Cursor Rules documentation from workflow YAML files."""
17
+
18
+ def __init__(self, project_root: Path | None = None):
19
+ """
20
+ Initialize the rules generator.
21
+
22
+ Args:
23
+ project_root: Project root directory (defaults to cwd)
24
+ """
25
+ if project_root is None:
26
+ project_root = Path.cwd()
27
+ self.project_root = project_root
28
+
29
+ def find_workflow_files(self) -> list[tuple[Path, str | None]]:
30
+ """
31
+ Find all workflow YAML files in preset directories.
32
+
33
+ Returns:
34
+ List of tuples (file_path, resource_name) where resource_name is None for regular files
35
+ """
36
+ workflow_files: list[tuple[Path, str | None]] = []
37
+
38
+ # Check project workflows/presets first
39
+ project_presets = self.project_root / "workflows" / "presets"
40
+ if project_presets.exists():
41
+ for yaml_file in project_presets.glob("*.yaml"):
42
+ workflow_files.append((yaml_file, None))
43
+
44
+ # Check framework resources if project presets not found
45
+ if not workflow_files:
46
+ try:
47
+ from importlib import resources as importlib_resources
48
+
49
+ resource_path = importlib_resources.files("tapps_agents.resources.workflows.presets")
50
+ if resource_path.is_dir():
51
+ for entry in resource_path.iterdir():
52
+ if entry.name.endswith(".yaml") and not entry.is_dir():
53
+ # Store as resource name for later reading
54
+ workflow_files.append((Path(entry.name), entry.name))
55
+ except (ImportError, Exception) as e:
56
+ logger.debug(f"Could not load workflow files from resources: {e}")
57
+
58
+ return sorted(workflow_files, key=lambda x: x[0].name)
59
+
60
+ def extract_quality_gates(self, workflow: Workflow) -> dict[str, Any]:
61
+ """
62
+ Extract quality gate thresholds from workflow steps.
63
+
64
+ Args:
65
+ workflow: Parsed Workflow object
66
+
67
+ Returns:
68
+ Dictionary with quality gate information
69
+ """
70
+ gates: dict[str, Any] = {}
71
+
72
+ for step in workflow.steps:
73
+ if step.scoring and isinstance(step.scoring, dict):
74
+ thresholds = step.scoring.get("thresholds", {})
75
+ if thresholds:
76
+ if "overall_min" in thresholds:
77
+ gates["overall"] = thresholds["overall_min"]
78
+ if "security_min" in thresholds:
79
+ gates["security"] = thresholds["security_min"]
80
+ if "maintainability_min" in thresholds:
81
+ gates["maintainability"] = thresholds["maintainability_min"]
82
+
83
+ return gates
84
+
85
+ def get_workflow_aliases(self, workflow_id: str) -> list[str]:
86
+ """
87
+ Get command aliases for a workflow ID.
88
+
89
+ Args:
90
+ workflow_id: Workflow ID
91
+
92
+ Returns:
93
+ List of aliases
94
+ """
95
+ alias_map: dict[str, list[str]] = {
96
+ "full-sdlc": ["full", "enterprise"],
97
+ "rapid-dev": ["rapid", "feature"],
98
+ "fix": ["fix", "maintenance", "hotfix", "urgent", "refactor", "quick-fix"],
99
+ "quality": ["quality", "improve"],
100
+ "brownfield-analysis": ["brownfield"],
101
+ }
102
+ return alias_map.get(workflow_id, [])
103
+
104
+ def generate(self, output_path: Path | None = None) -> str:
105
+ """
106
+ Generate Cursor Rules markdown from workflow YAML files.
107
+
108
+ Args:
109
+ output_path: Output file path (unused, kept for API compatibility)
110
+
111
+ Returns:
112
+ Generated markdown content
113
+
114
+ Raises:
115
+ ValueError: If no workflows are found or all workflows fail to parse
116
+ """
117
+ # Find workflow files
118
+ workflow_files = self.find_workflow_files()
119
+ if not workflow_files:
120
+ logger.warning("No workflow YAML files found")
121
+ raise ValueError(
122
+ "No workflow YAML files found. "
123
+ "Ensure workflows exist in workflows/presets/ or framework resources."
124
+ )
125
+
126
+ # Parse workflows
127
+ workflows: list[Workflow] = []
128
+ parse_errors: list[str] = []
129
+
130
+ for workflow_file, resource_name in workflow_files:
131
+ try:
132
+ if resource_name:
133
+ # Handle resource paths (from importlib.resources)
134
+ try:
135
+ from importlib import resources as importlib_resources
136
+
137
+ resource = importlib_resources.files("tapps_agents.resources.workflows.presets")
138
+ content_file = resource / resource_name
139
+ if content_file.exists():
140
+ content = content_file.read_text(encoding="utf-8")
141
+ import yaml
142
+
143
+ yaml_content = yaml.safe_load(content)
144
+ workflow = WorkflowParser.parse(yaml_content, file_path=workflow_file)
145
+ workflows.append(workflow)
146
+ else:
147
+ parse_errors.append(f"Resource file not found: {resource_name}")
148
+ except Exception as e:
149
+ error_msg = f"Could not parse resource workflow {workflow_file}: {e}"
150
+ logger.debug(error_msg)
151
+ parse_errors.append(error_msg)
152
+ else:
153
+ # Regular file path
154
+ workflow = WorkflowParser.parse_file(workflow_file)
155
+ workflows.append(workflow)
156
+ except Exception as e:
157
+ error_msg = f"Could not parse workflow {workflow_file}: {e}"
158
+ logger.warning(error_msg)
159
+ parse_errors.append(error_msg)
160
+
161
+ if not workflows:
162
+ error_message = "No valid workflows found"
163
+ if parse_errors:
164
+ error_message += f". Parse errors: {'; '.join(parse_errors[:3])}"
165
+ logger.error(error_message)
166
+ raise ValueError(error_message)
167
+
168
+ # Sort workflows by a consistent order (5 presets)
169
+ workflow_order = ["full-sdlc", "rapid-dev", "fix", "quality", "brownfield-analysis"]
170
+ workflows.sort(key=lambda w: (
171
+ workflow_order.index(w.id) if w.id in workflow_order else 999,
172
+ w.id
173
+ ))
174
+
175
+ # Generate markdown
176
+ lines: list[str] = []
177
+ lines.append("# Workflow Presets - Quick Commands")
178
+ lines.append("")
179
+ lines.append("## Overview")
180
+ lines.append("")
181
+ lines.append(
182
+ "TappsCodingAgents provides preset workflows that can be executed with simple one-word commands. "
183
+ "These workflows automatically orchestrate multiple agents to complete common SDLC tasks."
184
+ )
185
+ lines.append("")
186
+ lines.append("## Available Workflow Presets")
187
+ lines.append("")
188
+
189
+ # Generate sections for each workflow
190
+ for i, workflow in enumerate(workflows, 1):
191
+ lines.append(f"### {i}. {workflow.name}")
192
+ aliases = self.get_workflow_aliases(workflow.id)
193
+ if aliases:
194
+ alias_text = f" (`{'` / `'.join(aliases)}`)"
195
+ lines[-1] += alias_text
196
+ lines.append("")
197
+ lines.append(f"**Command:** `python -m tapps_agents.cli workflow {aliases[0] if aliases else workflow.id}`")
198
+ lines.append("")
199
+ if workflow.description:
200
+ lines.append(f"**Best for:** {workflow.description}")
201
+ lines.append("")
202
+ lines.append("**Agent Sequence:**")
203
+ for j, step in enumerate(workflow.steps, 1):
204
+ agent_name = step.agent.title()
205
+ action_desc = step.action.replace("_", " ").title()
206
+ lines.append(f"{j}. {agent_name} - {action_desc}")
207
+ lines.append("")
208
+ gates = self.extract_quality_gates(workflow)
209
+ if gates:
210
+ lines.append("**Quality Gates:**")
211
+ if "overall" in gates:
212
+ lines.append(f"- Overall score: ≥{gates['overall']}")
213
+ if "security" in gates:
214
+ lines.append(f"- Security: ≥{gates['security']}")
215
+ if "maintainability" in gates:
216
+ lines.append(f"- Maintainability: ≥{gates['maintainability']}")
217
+ lines.append("")
218
+
219
+ # Add usage section
220
+ lines.append("## Usage in Cursor AI")
221
+ lines.append("")
222
+ lines.append("When working in Cursor AI, you can:")
223
+ lines.append("")
224
+ lines.append("1. **Run workflows directly:**")
225
+ lines.append(" - Type: \"Run rapid development workflow\"")
226
+ lines.append(" - Or: \"Execute full SDLC pipeline\"")
227
+ lines.append(" - Or: \"Start quality improvement\"")
228
+ lines.append("")
229
+ lines.append("2. **Use voice commands:**")
230
+ lines.append(" - \"run rapid dev\"")
231
+ lines.append(" - \"execute enterprise workflow\"")
232
+ lines.append(" - \"start quick fix\"")
233
+ lines.append("")
234
+ lines.append("3. **Reference in conversations:**")
235
+ lines.append(" - \"Use the rapid workflow for this feature\"")
236
+ lines.append(" - \"This needs the full enterprise workflow\"")
237
+ lines.append(" - \"Run the quality improvement cycle\"")
238
+ lines.append("")
239
+
240
+ # Add when to use section
241
+ lines.append("## When to Use Each Workflow")
242
+ lines.append("")
243
+ for workflow in workflows:
244
+ aliases = self.get_workflow_aliases(workflow.id)
245
+ primary_alias = aliases[0] if aliases else workflow.id
246
+ if workflow.description:
247
+ # Extract use case from description
248
+ use_case = workflow.description.split(".")[0] if "." in workflow.description else workflow.description
249
+ lines.append(f"- **{use_case}** → `workflow {primary_alias}`")
250
+ lines.append("")
251
+
252
+ # Add integration section
253
+ lines.append("## Integration")
254
+ lines.append("")
255
+ lines.append("These workflows integrate with:")
256
+ lines.append("- ✅ Expert consultation (domain experts)")
257
+ lines.append("- ✅ Quality gates (scoring thresholds)")
258
+ lines.append("- ✅ Context tiers (token optimization)")
259
+ lines.append("- ✅ Multi-agent orchestration")
260
+ lines.append("- ✅ Git worktree isolation")
261
+ lines.append("")
262
+
263
+ # Add list command
264
+ lines.append("## List All Presets")
265
+ lines.append("")
266
+ lines.append("```bash")
267
+ lines.append("python -m tapps_agents.cli workflow list")
268
+ lines.append("```")
269
+ lines.append("")
270
+
271
+ # Add customization section
272
+ lines.append("## Customization")
273
+ lines.append("")
274
+ lines.append("Workflow presets are defined in `workflows/presets/`:")
275
+ for workflow in workflows:
276
+ lines.append(f"- `{workflow.id}.yaml`")
277
+ lines.append("")
278
+ lines.append("You can modify these files or create your own presets.")
279
+ lines.append("")
280
+
281
+ content = "\n".join(lines)
282
+ return content
283
+
284
+ def write(self, output_path: Path | None = None, backup: bool = True) -> Path:
285
+ """
286
+ Generate and write Cursor Rules markdown to file.
287
+
288
+ Args:
289
+ output_path: Output file path (defaults to .cursor/rules/workflow-presets.mdc)
290
+ backup: Whether to backup existing file
291
+
292
+ Returns:
293
+ Path to written file
294
+ """
295
+ if output_path is None:
296
+ output_path = self.project_root / ".cursor" / "rules" / "workflow-presets.mdc"
297
+
298
+ # Create directory if needed
299
+ output_path.parent.mkdir(parents=True, exist_ok=True)
300
+
301
+ # Backup existing file
302
+ if backup and output_path.exists():
303
+ backup_path = output_path.with_suffix(f".mdc.backup")
304
+ try:
305
+ import shutil
306
+
307
+ shutil.copy2(output_path, backup_path)
308
+ logger.debug(f"Backed up existing rules to {backup_path}")
309
+ except Exception as e:
310
+ logger.warning(f"Could not backup existing rules: {e}")
311
+
312
+ # Generate content
313
+ try:
314
+ content = self.generate()
315
+ except ValueError as e:
316
+ msg = str(e)
317
+ if "No workflow YAML files found" in msg:
318
+ logger.warning(
319
+ "Could not generate workflow-presets.mdc (no YAML found). %s", msg
320
+ )
321
+ else:
322
+ logger.error(f"Failed to generate rules: {e}")
323
+ raise
324
+
325
+ if not content or not content.strip():
326
+ raise ValueError("Generated content is empty")
327
+
328
+ # Write file
329
+ try:
330
+ output_path.write_text(content, encoding="utf-8")
331
+ logger.info(f"Generated Cursor Rules at {output_path}")
332
+ except OSError as e:
333
+ logger.error(f"Failed to write rules file: {e}")
334
+ raise
335
+
336
+ return output_path
337
+