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.
- tapps_agents/__init__.py +2 -2
- tapps_agents/agents/enhancer/agent.py +2728 -2728
- tapps_agents/agents/implementer/agent.py +35 -13
- tapps_agents/agents/reviewer/agent.py +43 -10
- tapps_agents/agents/reviewer/scoring.py +59 -68
- tapps_agents/agents/reviewer/tools/__init__.py +24 -0
- tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -0
- tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -0
- tapps_agents/beads/__init__.py +11 -0
- tapps_agents/beads/hydration.py +213 -0
- tapps_agents/beads/specs.py +206 -0
- tapps_agents/cli/commands/health.py +19 -3
- tapps_agents/cli/commands/simple_mode.py +842 -676
- tapps_agents/cli/commands/task.py +219 -0
- tapps_agents/cli/commands/top_level.py +13 -0
- tapps_agents/cli/main.py +658 -651
- tapps_agents/cli/parsers/top_level.py +1978 -1881
- tapps_agents/core/config.py +1622 -1622
- tapps_agents/core/init_project.py +3012 -2897
- tapps_agents/epic/markdown_sync.py +105 -0
- tapps_agents/epic/orchestrator.py +1 -2
- tapps_agents/epic/parser.py +427 -423
- tapps_agents/experts/adaptive_domain_detector.py +0 -2
- tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +15 -15
- tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +19 -44
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
- tapps_agents/health/checks/outcomes.py +134 -46
- tapps_agents/health/orchestrator.py +12 -4
- tapps_agents/hooks/__init__.py +33 -0
- tapps_agents/hooks/config.py +140 -0
- tapps_agents/hooks/events.py +135 -0
- tapps_agents/hooks/executor.py +128 -0
- tapps_agents/hooks/manager.py +143 -0
- tapps_agents/session/__init__.py +19 -0
- tapps_agents/session/manager.py +256 -0
- tapps_agents/simple_mode/code_snippet_handler.py +382 -0
- tapps_agents/simple_mode/intent_parser.py +29 -4
- tapps_agents/simple_mode/orchestrators/base.py +185 -59
- tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2667 -2642
- tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +2 -2
- tapps_agents/simple_mode/workflow_suggester.py +37 -3
- tapps_agents/workflow/agent_handlers/implementer_handler.py +18 -3
- tapps_agents/workflow/cursor_executor.py +2196 -2118
- tapps_agents/workflow/direct_execution_fallback.py +16 -3
- tapps_agents/workflow/message_formatter.py +2 -1
- tapps_agents/workflow/parallel_executor.py +43 -4
- tapps_agents/workflow/parser.py +375 -357
- tapps_agents/workflow/rules_generator.py +337 -337
- tapps_agents/workflow/skill_invoker.py +9 -3
- {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/METADATA +5 -1
- {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/RECORD +57 -53
- tapps_agents/agents/analyst/SKILL.md +0 -85
- tapps_agents/agents/architect/SKILL.md +0 -80
- tapps_agents/agents/debugger/SKILL.md +0 -66
- tapps_agents/agents/designer/SKILL.md +0 -78
- tapps_agents/agents/documenter/SKILL.md +0 -95
- tapps_agents/agents/enhancer/SKILL.md +0 -189
- tapps_agents/agents/implementer/SKILL.md +0 -117
- tapps_agents/agents/improver/SKILL.md +0 -55
- tapps_agents/agents/ops/SKILL.md +0 -64
- tapps_agents/agents/orchestrator/SKILL.md +0 -238
- tapps_agents/agents/planner/story_template.md +0 -37
- tapps_agents/agents/reviewer/templates/quality-dashboard.html.j2 +0 -150
- tapps_agents/agents/tester/SKILL.md +0 -71
- {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/WHEEL +0 -0
- {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
|