ref-agents 1.0.0__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.
- ref_agents/__init__.py +9 -0
- ref_agents/api_keys.json.example +8 -0
- ref_agents/auth.py +129 -0
- ref_agents/codemap/..md +62 -0
- ref_agents/codemap/CODE_MAP.md +37 -0
- ref_agents/codemap/core.md +43 -0
- ref_agents/codemap/models.md +43 -0
- ref_agents/codemap/prompts.md +40 -0
- ref_agents/codemap/security.md +45 -0
- ref_agents/codemap/tools.md +94 -0
- ref_agents/codemap/tools_browser.md +44 -0
- ref_agents/codemap/utils.md +42 -0
- ref_agents/codemap/workflow.md +42 -0
- ref_agents/config/ai_patterns.yaml +101 -0
- ref_agents/config/frameworks/angular.yaml +104 -0
- ref_agents/config/frameworks/aspnet.yaml +84 -0
- ref_agents/config/frameworks/ef_core.yaml +81 -0
- ref_agents/config/frameworks/react.yaml +111 -0
- ref_agents/config/frameworks/spring_boot.yaml +117 -0
- ref_agents/config/languages/csharp.yaml +153 -0
- ref_agents/config/languages/java.yaml +188 -0
- ref_agents/config/languages/javascript.yaml +172 -0
- ref_agents/config/languages/python.yaml +153 -0
- ref_agents/config/languages/typescript.yaml +193 -0
- ref_agents/constants.py +553 -0
- ref_agents/core/__init__.py +15 -0
- ref_agents/core/config_loader.py +160 -0
- ref_agents/core/config_models.py +167 -0
- ref_agents/core/config_parsing.py +84 -0
- ref_agents/core/language_detector.py +388 -0
- ref_agents/core/validation_models.py +66 -0
- ref_agents/core/validation_primitives.py +176 -0
- ref_agents/errors.py +34 -0
- ref_agents/license_client.py +247 -0
- ref_agents/models/__init__.py +22 -0
- ref_agents/models/gherkin.py +45 -0
- ref_agents/models/hierarchy.py +80 -0
- ref_agents/models/invest.py +59 -0
- ref_agents/models/version.py +49 -0
- ref_agents/prompts/__init__.py +9 -0
- ref_agents/prompts/start_agent.py +772 -0
- ref_agents/rules/architecture/backend_patterns.md +43 -0
- ref_agents/rules/architecture/diagramming.md +100 -0
- ref_agents/rules/architecture/frontend_patterns.md +40 -0
- ref_agents/rules/architecture/impact_analysis.md +129 -0
- ref_agents/rules/architecture/migration_strategy.md +208 -0
- ref_agents/rules/architecture/regression_protocol.md +77 -0
- ref_agents/rules/architecture/system_design.md +97 -0
- ref_agents/rules/common/codemap_standard.md +97 -0
- ref_agents/rules/common/core_protocol.md +59 -0
- ref_agents/rules/common/prompt_engineering.md +294 -0
- ref_agents/rules/development/debugging.md +32 -0
- ref_agents/rules/development/implementation.md +205 -0
- ref_agents/rules/operations/completion.md +119 -0
- ref_agents/rules/operations/cutover_protocol.md +218 -0
- ref_agents/rules/operations/discovery.md +179 -0
- ref_agents/rules/operations/fix_workflow.md +87 -0
- ref_agents/rules/operations/forensics.md +278 -0
- ref_agents/rules/operations/platform.md +263 -0
- ref_agents/rules/operations/synchronous_flow.md +25 -0
- ref_agents/rules/product/ac_validation.md +25 -0
- ref_agents/rules/product/brainstorming.md +27 -0
- ref_agents/rules/product/ref_flow.md +101 -0
- ref_agents/rules/product/requirements_std.md +114 -0
- ref_agents/rules/product/spec_writing.md +235 -0
- ref_agents/rules/product/strategy.md +96 -0
- ref_agents/rules/quality/documentation_standards.md +46 -0
- ref_agents/rules/quality/parity_testing.md +234 -0
- ref_agents/rules/quality/project_documentation.md +56 -0
- ref_agents/rules/quality/qa_lead.md +111 -0
- ref_agents/rules/quality/test_design.md +146 -0
- ref_agents/rules/quality/testing_standards.md +293 -0
- ref_agents/rules/review/pr_review.md +116 -0
- ref_agents/rules/security/security_audit.md +83 -0
- ref_agents/security/__init__.py +22 -0
- ref_agents/security/dependency_audit.py +188 -0
- ref_agents/security/file_audit.py +208 -0
- ref_agents/security/network_scan.py +179 -0
- ref_agents/security/report_generator.py +313 -0
- ref_agents/security/secret_scan.py +252 -0
- ref_agents/security/url_scan.py +240 -0
- ref_agents/security_scan.py +236 -0
- ref_agents/server.py +1586 -0
- ref_agents/session.py +100 -0
- ref_agents/tool_names.py +55 -0
- ref_agents/tools/__init__.py +8 -0
- ref_agents/tools/agents_generator.py +315 -0
- ref_agents/tools/ai_pattern_detector.py +815 -0
- ref_agents/tools/brownfield_populator.py +529 -0
- ref_agents/tools/browser/__init__.py +50 -0
- ref_agents/tools/browser/evidence_verifier.py +302 -0
- ref_agents/tools/browser/execution_logger.py +249 -0
- ref_agents/tools/browser/playwright_mcp_client.py +259 -0
- ref_agents/tools/browser/screenshot_utils.py +184 -0
- ref_agents/tools/browser/test_executor.py +537 -0
- ref_agents/tools/code_quality_scanner.py +629 -0
- ref_agents/tools/codemap/..md +93 -0
- ref_agents/tools/codemap/CODE_MAP.md +30 -0
- ref_agents/tools/codemap/browser.md +44 -0
- ref_agents/tools/codemap.py +403 -0
- ref_agents/tools/codemap_freshness.py +234 -0
- ref_agents/tools/comment_smell_scanner.py +346 -0
- ref_agents/tools/complexity.py +436 -0
- ref_agents/tools/complexity_ast.py +333 -0
- ref_agents/tools/compliance.py +246 -0
- ref_agents/tools/compliance_remediation.py +846 -0
- ref_agents/tools/context_graph.py +839 -0
- ref_agents/tools/context_manager.py +550 -0
- ref_agents/tools/context_tools.py +121 -0
- ref_agents/tools/cross_repo_linker.py +393 -0
- ref_agents/tools/dead_code_scanner.py +637 -0
- ref_agents/tools/debt_scanner.py +1092 -0
- ref_agents/tools/dependency_graph.py +272 -0
- ref_agents/tools/discovery_audit.py +372 -0
- ref_agents/tools/docs_scanner.py +600 -0
- ref_agents/tools/evaluate_gate.py +119 -0
- ref_agents/tools/external_detector.py +524 -0
- ref_agents/tools/features_generator.py +282 -0
- ref_agents/tools/flow_gap_detector.py +373 -0
- ref_agents/tools/flow_mapper.py +327 -0
- ref_agents/tools/full_suite_runner.py +740 -0
- ref_agents/tools/gherkin_parser.py +227 -0
- ref_agents/tools/guard_tools.py +139 -0
- ref_agents/tools/handoff_tools.py +282 -0
- ref_agents/tools/health_scanner.py +1211 -0
- ref_agents/tools/hierarchy_manager.py +289 -0
- ref_agents/tools/invest_scorer.py +249 -0
- ref_agents/tools/jira_confluence_export.py +306 -0
- ref_agents/tools/json_output.py +76 -0
- ref_agents/tools/migration_mapper.py +946 -0
- ref_agents/tools/migration_readiness_scanner.py +209 -0
- ref_agents/tools/pattern_learner.py +522 -0
- ref_agents/tools/report_utils.py +155 -0
- ref_agents/tools/requirements_serializer.py +225 -0
- ref_agents/tools/security_audit_tool.py +106 -0
- ref_agents/tools/sequencing_engine.py +288 -0
- ref_agents/tools/summary_generator.py +275 -0
- ref_agents/tools/symbol_resolver.py +306 -0
- ref_agents/tools/symbol_smoke_runner.py +336 -0
- ref_agents/tools/test_plan_validator.py +189 -0
- ref_agents/tools/test_smell_walker.py +902 -0
- ref_agents/tools/tier1_fixer.py +502 -0
- ref_agents/tools/validators/__init__.py +419 -0
- ref_agents/tools/validators/architect.py +268 -0
- ref_agents/tools/validators/cutover_engineer.py +167 -0
- ref_agents/tools/validators/developer.py +180 -0
- ref_agents/tools/validators/discovery.py +150 -0
- ref_agents/tools/validators/forensic_engineer.py +191 -0
- ref_agents/tools/validators/impact_architect.py +181 -0
- ref_agents/tools/validators/migration_planner.py +166 -0
- ref_agents/tools/validators/parity_tester.py +155 -0
- ref_agents/tools/validators/platform_engineer.py +134 -0
- ref_agents/tools/validators/pr_reviewer.py +129 -0
- ref_agents/tools/validators/product_manager.py +291 -0
- ref_agents/tools/validators/qa_lead.py +172 -0
- ref_agents/tools/validators/scrum_master.py +212 -0
- ref_agents/tools/validators/security_owner.py +162 -0
- ref_agents/tools/validators/specifier.py +134 -0
- ref_agents/tools/validators/strategist.py +149 -0
- ref_agents/tools/validators/tester.py +121 -0
- ref_agents/tools/version_manager.py +202 -0
- ref_agents/tools/workflow_tools.py +1549 -0
- ref_agents/utils/__init__.py +21 -0
- ref_agents/utils/git_utils.py +351 -0
- ref_agents/utils/handoff_logger.py +368 -0
- ref_agents/utils/ignore_matcher.py +270 -0
- ref_agents/workflow/__init__.py +19 -0
- ref_agents/workflow/capabilities.py +328 -0
- ref_agents/workflow/state_machine.py +708 -0
- ref_agents/workflow/transitions.py +658 -0
- ref_agents-1.0.0.dist-info/METADATA +365 -0
- ref_agents-1.0.0.dist-info/RECORD +175 -0
- ref_agents-1.0.0.dist-info/WHEEL +4 -0
- ref_agents-1.0.0.dist-info/entry_points.txt +2 -0
- ref_agents-1.0.0.dist-info/licenses/LICENSE +115 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Capability Detection for REF Workflow.
|
|
2
|
+
|
|
3
|
+
Detects available tools (Playwright, etc.) and story type classification
|
|
4
|
+
for appropriate testing strategy selection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import structlog
|
|
11
|
+
|
|
12
|
+
from ref_agents.constants import (
|
|
13
|
+
BACKEND_KEYWORDS,
|
|
14
|
+
FRONTEND_KEYWORDS,
|
|
15
|
+
StoryType,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
logger = structlog.get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CapabilityDetector:
|
|
22
|
+
"""Detects tool capabilities and story type.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
project_root: Root directory for file scanning.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, project_root: Path | None = None) -> None:
|
|
29
|
+
"""Initialize capability detector.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
project_root: Project root for file scanning.
|
|
33
|
+
"""
|
|
34
|
+
self.project_root = project_root or Path(".")
|
|
35
|
+
|
|
36
|
+
def detect_browser_tools(self) -> bool:
|
|
37
|
+
"""Detect if browser/Playwright MCP tools are available.
|
|
38
|
+
|
|
39
|
+
Checks for MCP browser tools availability via:
|
|
40
|
+
1. Check ref-agents browser tools module
|
|
41
|
+
2. Fall back to file-based indicators
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
True if browser tools are available.
|
|
45
|
+
"""
|
|
46
|
+
# Primary: Check ref-agents browser tools module
|
|
47
|
+
try:
|
|
48
|
+
from ref_agents.tools.browser.playwright_mcp_client import (
|
|
49
|
+
check_browser_tools_available,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if check_browser_tools_available():
|
|
53
|
+
logger.info("browser_tools_detected", source="playwright_mcp_client")
|
|
54
|
+
return True
|
|
55
|
+
except ImportError:
|
|
56
|
+
logger.debug("playwright_mcp_client not available, using fallback")
|
|
57
|
+
|
|
58
|
+
# Fallback: Check for file-based indicators
|
|
59
|
+
indicators = [
|
|
60
|
+
self.project_root / "playwright.config.ts",
|
|
61
|
+
self.project_root / "playwright.config.js",
|
|
62
|
+
self.project_root / "e2e",
|
|
63
|
+
self.project_root / "tests" / "e2e",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
for indicator in indicators:
|
|
67
|
+
if indicator.exists():
|
|
68
|
+
logger.info("browser_tools_detected", indicator=str(indicator))
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
# Check package.json for playwright
|
|
72
|
+
package_json = self.project_root / "package.json"
|
|
73
|
+
if package_json.exists():
|
|
74
|
+
try:
|
|
75
|
+
content = package_json.read_text()
|
|
76
|
+
if "playwright" in content.lower():
|
|
77
|
+
logger.info("browser_tools_detected", source="package.json")
|
|
78
|
+
return True
|
|
79
|
+
except OSError:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
# Check pyproject.toml for playwright
|
|
83
|
+
pyproject = self.project_root / "pyproject.toml"
|
|
84
|
+
if pyproject.exists():
|
|
85
|
+
try:
|
|
86
|
+
content = pyproject.read_text()
|
|
87
|
+
if "playwright" in content.lower():
|
|
88
|
+
logger.info("browser_tools_detected", source="pyproject.toml")
|
|
89
|
+
return True
|
|
90
|
+
except OSError:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
logger.info("browser_tools_not_detected")
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
def classify_story_type(self, requirements_content: str) -> str:
|
|
97
|
+
"""Classify story type from requirements content.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
requirements_content: Full text of requirements file.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
StoryType value (backend, frontend, fullstack, unknown).
|
|
104
|
+
"""
|
|
105
|
+
content_lower = requirements_content.lower()
|
|
106
|
+
|
|
107
|
+
frontend_score = sum(1 for kw in FRONTEND_KEYWORDS if kw in content_lower)
|
|
108
|
+
backend_score = sum(1 for kw in BACKEND_KEYWORDS if kw in content_lower)
|
|
109
|
+
|
|
110
|
+
logger.info(
|
|
111
|
+
"story_type_classification",
|
|
112
|
+
frontend_score=frontend_score,
|
|
113
|
+
backend_score=backend_score,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if frontend_score > 0 and backend_score > 0:
|
|
117
|
+
return StoryType.FULLSTACK.value
|
|
118
|
+
elif frontend_score > 0:
|
|
119
|
+
return StoryType.FRONTEND.value
|
|
120
|
+
elif backend_score > 0:
|
|
121
|
+
return StoryType.BACKEND.value
|
|
122
|
+
else:
|
|
123
|
+
return StoryType.UNKNOWN.value
|
|
124
|
+
|
|
125
|
+
def classify_from_file(self, requirements_path: Path) -> str:
|
|
126
|
+
"""Classify story type from requirements file.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
requirements_path: Path to requirements markdown file.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
StoryType value.
|
|
133
|
+
"""
|
|
134
|
+
if not requirements_path.exists():
|
|
135
|
+
logger.warning("requirements_file_not_found", path=str(requirements_path))
|
|
136
|
+
return StoryType.UNKNOWN.value
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
content = requirements_path.read_text()
|
|
140
|
+
return self.classify_story_type(content)
|
|
141
|
+
except OSError as e:
|
|
142
|
+
logger.error("requirements_read_failed", error=str(e))
|
|
143
|
+
return StoryType.UNKNOWN.value
|
|
144
|
+
|
|
145
|
+
def find_requirements_file(self, story_id: str) -> Path | None:
|
|
146
|
+
"""Find requirements file for a story.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
story_id: Story identifier.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Path to requirements file if found.
|
|
153
|
+
"""
|
|
154
|
+
# Check common locations
|
|
155
|
+
patterns = [
|
|
156
|
+
f"docs/requirements/REQ-{story_id}.md",
|
|
157
|
+
f"docs/requirements/{story_id}.md",
|
|
158
|
+
f"requirements/{story_id}.md",
|
|
159
|
+
"docs/requirements/REQ-*.md", # Glob pattern
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
for pattern in patterns:
|
|
163
|
+
if "*" in pattern:
|
|
164
|
+
matches = list(self.project_root.glob(pattern))
|
|
165
|
+
if matches:
|
|
166
|
+
for match in matches:
|
|
167
|
+
if story_id.lower() in match.name.lower():
|
|
168
|
+
return match
|
|
169
|
+
return matches[0]
|
|
170
|
+
else:
|
|
171
|
+
path = self.project_root / pattern
|
|
172
|
+
if path.exists():
|
|
173
|
+
return path
|
|
174
|
+
|
|
175
|
+
# Fallback: requirements.md anywhere within depth 3
|
|
176
|
+
for match in sorted(self.project_root.rglob("requirements.md")):
|
|
177
|
+
parts = match.relative_to(self.project_root).parts
|
|
178
|
+
if (
|
|
179
|
+
len(parts) <= 4 # noqa: PLR2004 # TECH_DEBT: extract to named constant — G25
|
|
180
|
+
): # max 3 dirs deep + filename # noqa: PLR2004 # TECH_DEBT: extract to named constant — G25
|
|
181
|
+
logger.info("requirements_file_found_via_rglob", path=str(match))
|
|
182
|
+
return match
|
|
183
|
+
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
def check_capabilities(
|
|
187
|
+
self,
|
|
188
|
+
story_id: str,
|
|
189
|
+
story_type: str | None = None,
|
|
190
|
+
) -> dict:
|
|
191
|
+
"""Check all capabilities for a story.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
story_id: Story identifier.
|
|
195
|
+
story_type: Optional pre-classified story type.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Dict with capability check results.
|
|
199
|
+
"""
|
|
200
|
+
# Detect story type if not provided
|
|
201
|
+
if not story_type or story_type == StoryType.UNKNOWN.value:
|
|
202
|
+
req_file = self.find_requirements_file(story_id)
|
|
203
|
+
if req_file:
|
|
204
|
+
story_type = self.classify_from_file(req_file)
|
|
205
|
+
else:
|
|
206
|
+
story_type = StoryType.UNKNOWN.value
|
|
207
|
+
|
|
208
|
+
# Detect available tools
|
|
209
|
+
browser_available = self.detect_browser_tools()
|
|
210
|
+
|
|
211
|
+
# Determine required capabilities
|
|
212
|
+
requires_browser = story_type in (
|
|
213
|
+
StoryType.FRONTEND.value,
|
|
214
|
+
StoryType.FULLSTACK.value,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Build capability status
|
|
218
|
+
capabilities = {
|
|
219
|
+
"browser_tools": browser_available,
|
|
220
|
+
"git_tools": True, # Always assume available
|
|
221
|
+
"file_tools": True, # Always available in MCP
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# Check for capability gaps
|
|
225
|
+
gaps = []
|
|
226
|
+
if requires_browser and not browser_available:
|
|
227
|
+
gaps.append(
|
|
228
|
+
{
|
|
229
|
+
"capability": "browser_tools",
|
|
230
|
+
"required": True,
|
|
231
|
+
"available": False,
|
|
232
|
+
"reason": "Frontend story requires Playwright for E2E tests",
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
"story_id": story_id,
|
|
238
|
+
"story_type": story_type,
|
|
239
|
+
"requires_browser": requires_browser,
|
|
240
|
+
"browser_available": browser_available,
|
|
241
|
+
"capabilities": capabilities,
|
|
242
|
+
"gaps": gaps,
|
|
243
|
+
"all_met": len(gaps) == 0,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
def format_capability_report(self, check_result: dict) -> str:
|
|
247
|
+
"""Format capability check as user-friendly report.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
check_result: Result from check_capabilities().
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Formatted string report.
|
|
254
|
+
"""
|
|
255
|
+
lines = [
|
|
256
|
+
f"📋 Capability Check: {check_result['story_id']}",
|
|
257
|
+
"",
|
|
258
|
+
f"Story Type: {check_result['story_type']}",
|
|
259
|
+
"",
|
|
260
|
+
"| Capability | Required | Available |",
|
|
261
|
+
"|------------|----------|-----------|",
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
caps = check_result["capabilities"]
|
|
265
|
+
requires_browser = check_result["requires_browser"]
|
|
266
|
+
|
|
267
|
+
lines.append(
|
|
268
|
+
f"| Browser/Playwright | "
|
|
269
|
+
f"{'✅ Yes' if requires_browser else '➖ No'} | "
|
|
270
|
+
f"{'✅ Yes' if caps['browser_tools'] else '❌ No'} |"
|
|
271
|
+
)
|
|
272
|
+
lines.append("| Git Tools | ✅ Yes | ✅ Yes |")
|
|
273
|
+
lines.append("| File Tools | ✅ Yes | ✅ Yes |")
|
|
274
|
+
|
|
275
|
+
if check_result["gaps"]:
|
|
276
|
+
lines.append("")
|
|
277
|
+
lines.append("⚠️ WARNING: Capability gaps detected.")
|
|
278
|
+
lines.append("")
|
|
279
|
+
lines.append("Options:")
|
|
280
|
+
lines.append("1. Enable Playwright MCP in Cursor settings (recommended)")
|
|
281
|
+
lines.append("2. Run manual E2E tests, document in ui_test_report.md")
|
|
282
|
+
lines.append("3. Log to TECH_DEBT.md as 'E2E_PENDING' and continue")
|
|
283
|
+
lines.append("")
|
|
284
|
+
lines.append("Select option (1/2/3):")
|
|
285
|
+
else:
|
|
286
|
+
lines.append("")
|
|
287
|
+
lines.append("✅ All required capabilities available.")
|
|
288
|
+
|
|
289
|
+
return "\n".join(lines)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def detect_story_type_from_diff(diff_content: str) -> str:
|
|
293
|
+
"""Classify story type from git diff content.
|
|
294
|
+
|
|
295
|
+
Useful for developer to detect frontend work mid-implementation.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
diff_content: Git diff output.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
StoryType value.
|
|
302
|
+
"""
|
|
303
|
+
# Check file extensions in diff
|
|
304
|
+
frontend_extensions = {".tsx", ".jsx", ".vue", ".svelte", ".css", ".scss"}
|
|
305
|
+
backend_extensions = {".py", ".java", ".go", ".rs", ".sql"}
|
|
306
|
+
|
|
307
|
+
frontend_files = 0
|
|
308
|
+
backend_files = 0
|
|
309
|
+
|
|
310
|
+
# Parse diff for file paths
|
|
311
|
+
file_pattern = re.compile(r"^\+\+\+ b/(.+)$", re.MULTILINE)
|
|
312
|
+
matches = file_pattern.findall(diff_content)
|
|
313
|
+
|
|
314
|
+
for filepath in matches:
|
|
315
|
+
ext = Path(filepath).suffix.lower()
|
|
316
|
+
if ext in frontend_extensions:
|
|
317
|
+
frontend_files += 1
|
|
318
|
+
elif ext in backend_extensions:
|
|
319
|
+
backend_files += 1
|
|
320
|
+
|
|
321
|
+
if frontend_files > 0 and backend_files > 0:
|
|
322
|
+
return StoryType.FULLSTACK.value
|
|
323
|
+
elif frontend_files > 0:
|
|
324
|
+
return StoryType.FRONTEND.value
|
|
325
|
+
elif backend_files > 0:
|
|
326
|
+
return StoryType.BACKEND.value
|
|
327
|
+
|
|
328
|
+
return StoryType.UNKNOWN.value
|