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,306 @@
|
|
|
1
|
+
"""JIRA and Confluence export functions for requirements.
|
|
2
|
+
|
|
3
|
+
Provides functions to convert REF-Agents requirements to JIRA/Confluence formats.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import structlog
|
|
9
|
+
|
|
10
|
+
from ref_agents.models.hierarchy import Epic, Feature, Story
|
|
11
|
+
from ref_agents.models.invest import INVESTScores
|
|
12
|
+
|
|
13
|
+
logger = structlog.get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def export_to_jira_format(
|
|
17
|
+
story: Story,
|
|
18
|
+
invest_scores: INVESTScores | None = None,
|
|
19
|
+
acceptance_criteria: list[dict[str, Any]] | None = None,
|
|
20
|
+
) -> dict[str, Any]:
|
|
21
|
+
"""Export story to JIRA issue format.
|
|
22
|
+
|
|
23
|
+
Compatible with JIRA REST API for issue creation.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
story: Story model
|
|
27
|
+
invest_scores: Optional INVEST scores
|
|
28
|
+
acceptance_criteria: Optional structured acceptance criteria
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary compatible with JIRA REST API createIssue endpoint
|
|
32
|
+
"""
|
|
33
|
+
fields: dict[str, Any] = {
|
|
34
|
+
"summary": story.title,
|
|
35
|
+
"description": {
|
|
36
|
+
"type": "doc",
|
|
37
|
+
"version": 1,
|
|
38
|
+
"content": [
|
|
39
|
+
{
|
|
40
|
+
"type": "paragraph",
|
|
41
|
+
"content": [{"type": "text", "text": story.description or ""}],
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
"issuetype": {"name": "Story"},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Add user story
|
|
49
|
+
if story.user_story:
|
|
50
|
+
fields["description"]["content"].append(
|
|
51
|
+
{
|
|
52
|
+
"type": "paragraph",
|
|
53
|
+
"content": [
|
|
54
|
+
{"type": "text", "text": f"User Story: {story.user_story}"}
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Add epic link if present
|
|
60
|
+
if story.epic_id:
|
|
61
|
+
fields["customfield_10014"] = (
|
|
62
|
+
story.epic_id
|
|
63
|
+
) # Epic Link field (varies by JIRA instance)
|
|
64
|
+
|
|
65
|
+
# Add feature link if present (custom field)
|
|
66
|
+
if story.feature_id:
|
|
67
|
+
fields["customfield_10015"] = story.feature_id # Feature Link (custom field)
|
|
68
|
+
|
|
69
|
+
# Add INVEST scores as custom fields or labels
|
|
70
|
+
if invest_scores:
|
|
71
|
+
fields["labels"] = [
|
|
72
|
+
f"INVEST-{k}:{v}" for k, v in invest_scores.to_dict().items()
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
# Add acceptance criteria
|
|
76
|
+
if acceptance_criteria:
|
|
77
|
+
ac_content = [
|
|
78
|
+
{
|
|
79
|
+
"type": "heading",
|
|
80
|
+
"attrs": {"level": 3},
|
|
81
|
+
"content": [{"type": "text", "text": "Acceptance Criteria"}],
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
for idx, ac in enumerate(acceptance_criteria, 1):
|
|
85
|
+
scenario = ac.get("scenario", f"AC-{idx}")
|
|
86
|
+
steps = ac.get("steps", [])
|
|
87
|
+
ac_content.append(
|
|
88
|
+
{
|
|
89
|
+
"type": "paragraph",
|
|
90
|
+
"content": [
|
|
91
|
+
{"type": "text", "text": f"{scenario}:"},
|
|
92
|
+
],
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
for step in steps:
|
|
96
|
+
keyword = step.get("keyword", "")
|
|
97
|
+
text = step.get("text", "")
|
|
98
|
+
ac_content.append(
|
|
99
|
+
{
|
|
100
|
+
"type": "paragraph",
|
|
101
|
+
"content": [
|
|
102
|
+
{"type": "text", "text": f"{keyword} {text}"},
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
fields["description"]["content"].extend(ac_content)
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"fields": fields,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def export_to_confluence_format(
|
|
115
|
+
story: Story,
|
|
116
|
+
invest_scores: INVESTScores | None = None,
|
|
117
|
+
acceptance_criteria: list[dict[str, Any]] | None = None,
|
|
118
|
+
) -> dict[str, Any]:
|
|
119
|
+
"""Export story to Confluence page format.
|
|
120
|
+
|
|
121
|
+
Compatible with Confluence REST API for page creation.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
story: Story model
|
|
125
|
+
invest_scores: Optional INVEST scores
|
|
126
|
+
acceptance_criteria: Optional structured acceptance criteria
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Dictionary compatible with Confluence REST API createPage endpoint
|
|
130
|
+
"""
|
|
131
|
+
# Build Confluence storage format (Confluence Wiki or Storage format)
|
|
132
|
+
body_content: list[dict[str, Any]] = []
|
|
133
|
+
|
|
134
|
+
# Title and description
|
|
135
|
+
body_content.append(
|
|
136
|
+
{
|
|
137
|
+
"type": "paragraph",
|
|
138
|
+
"content": [
|
|
139
|
+
{"type": "text", "text": story.description or ""},
|
|
140
|
+
],
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# User story
|
|
145
|
+
if story.user_story:
|
|
146
|
+
body_content.append(
|
|
147
|
+
{
|
|
148
|
+
"type": "paragraph",
|
|
149
|
+
"content": [
|
|
150
|
+
{"type": "text", "text": f"User Story: {story.user_story}"},
|
|
151
|
+
],
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Epic and Feature links
|
|
156
|
+
if story.epic_id or story.feature_id:
|
|
157
|
+
links = []
|
|
158
|
+
if story.epic_id:
|
|
159
|
+
links.append(f"Epic: {story.epic_id}")
|
|
160
|
+
if story.feature_id:
|
|
161
|
+
links.append(f"Feature: {story.feature_id}")
|
|
162
|
+
body_content.append(
|
|
163
|
+
{
|
|
164
|
+
"type": "paragraph",
|
|
165
|
+
"content": [
|
|
166
|
+
{"type": "text", "text": " | ".join(links)},
|
|
167
|
+
],
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# INVEST scores
|
|
172
|
+
if invest_scores:
|
|
173
|
+
scores_text = ", ".join(f"{k}: {v}" for k, v in invest_scores.to_dict().items())
|
|
174
|
+
body_content.append(
|
|
175
|
+
{
|
|
176
|
+
"type": "paragraph",
|
|
177
|
+
"content": [
|
|
178
|
+
{"type": "text", "text": f"INVEST Scores: {scores_text}"},
|
|
179
|
+
],
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Acceptance criteria
|
|
184
|
+
if acceptance_criteria:
|
|
185
|
+
body_content.append(
|
|
186
|
+
{
|
|
187
|
+
"type": "heading",
|
|
188
|
+
"attrs": {"level": 3},
|
|
189
|
+
"content": [{"type": "text", "text": "Acceptance Criteria"}],
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
for idx, ac in enumerate(acceptance_criteria, 1):
|
|
193
|
+
scenario = ac.get("scenario", f"AC-{idx}")
|
|
194
|
+
steps = ac.get("steps", [])
|
|
195
|
+
body_content.append(
|
|
196
|
+
{
|
|
197
|
+
"type": "heading",
|
|
198
|
+
"attrs": {"level": 4},
|
|
199
|
+
"content": [{"type": "text", "text": scenario}],
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
for step in steps:
|
|
203
|
+
keyword = step.get("keyword", "")
|
|
204
|
+
text = step.get("text", "")
|
|
205
|
+
body_content.append(
|
|
206
|
+
{
|
|
207
|
+
"type": "paragraph",
|
|
208
|
+
"content": [
|
|
209
|
+
{"type": "text", "text": f"{keyword} {text}"},
|
|
210
|
+
],
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
"title": story.title,
|
|
216
|
+
"type": "page",
|
|
217
|
+
"space": {"key": "REF"}, # Default space, should be configurable
|
|
218
|
+
"body": {
|
|
219
|
+
"storage": {
|
|
220
|
+
"value": _convert_to_storage_format(body_content),
|
|
221
|
+
"representation": "storage",
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def export_epic_to_jira(epic: Epic) -> dict[str, Any]:
|
|
228
|
+
"""Export epic to JIRA format.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
epic: Epic model
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Dictionary compatible with JIRA REST API
|
|
235
|
+
"""
|
|
236
|
+
return {
|
|
237
|
+
"fields": {
|
|
238
|
+
"summary": epic.title,
|
|
239
|
+
"description": {
|
|
240
|
+
"type": "doc",
|
|
241
|
+
"version": 1,
|
|
242
|
+
"content": [
|
|
243
|
+
{
|
|
244
|
+
"type": "paragraph",
|
|
245
|
+
"content": [{"type": "text", "text": epic.description or ""}],
|
|
246
|
+
}
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
"issuetype": {"name": "Epic"},
|
|
250
|
+
"customfield_10011": epic.title, # Epic Name field
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def export_feature_to_jira(feature: Feature) -> dict[str, Any]:
|
|
256
|
+
"""Export feature to JIRA format.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
feature: Feature model
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Dictionary compatible with JIRA REST API
|
|
263
|
+
"""
|
|
264
|
+
return {
|
|
265
|
+
"fields": {
|
|
266
|
+
"summary": feature.title,
|
|
267
|
+
"description": {
|
|
268
|
+
"type": "doc",
|
|
269
|
+
"version": 1,
|
|
270
|
+
"content": [
|
|
271
|
+
{
|
|
272
|
+
"type": "paragraph",
|
|
273
|
+
"content": [
|
|
274
|
+
{"type": "text", "text": feature.description or ""}
|
|
275
|
+
],
|
|
276
|
+
}
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
"issuetype": {"name": "Feature"},
|
|
280
|
+
"customfield_10014": feature.epic_id, # Epic Link
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _convert_to_storage_format(content: list[dict[str, Any]]) -> str:
|
|
286
|
+
"""Convert Confluence content to storage format string.
|
|
287
|
+
|
|
288
|
+
Simplified implementation - in production, use Confluence API client.
|
|
289
|
+
"""
|
|
290
|
+
# This is a placeholder - actual implementation would use Confluence's
|
|
291
|
+
# storage format or use a Confluence API client library
|
|
292
|
+
lines = []
|
|
293
|
+
for item in content:
|
|
294
|
+
if item["type"] == "paragraph":
|
|
295
|
+
text = " ".join(
|
|
296
|
+
c.get("text", "") for c in item.get("content", []) if "text" in c
|
|
297
|
+
)
|
|
298
|
+
lines.append(text)
|
|
299
|
+
elif item["type"] == "heading":
|
|
300
|
+
level = item.get("attrs", {}).get("level", 1)
|
|
301
|
+
text = " ".join(
|
|
302
|
+
c.get("text", "") for c in item.get("content", []) if "text" in c
|
|
303
|
+
)
|
|
304
|
+
lines.append(f"{'#' * level} {text}")
|
|
305
|
+
|
|
306
|
+
return "\n\n".join(lines)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""JSON output utility for generating JSON artifacts alongside markdown."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import structlog
|
|
8
|
+
|
|
9
|
+
logger = structlog.get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_json_output(artifact_type: str, data: Any, output_path: Path) -> Path:
|
|
13
|
+
"""Generate JSON output file for an artifact.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
artifact_type: Type of artifact (story, epic, feature, report)
|
|
17
|
+
data: Data to serialize (dict or Pydantic model)
|
|
18
|
+
output_path: Path for JSON file (will replace .md with .json if needed)
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Path to generated JSON file
|
|
22
|
+
"""
|
|
23
|
+
# Ensure .json extension
|
|
24
|
+
if output_path.suffix == ".md":
|
|
25
|
+
json_path = output_path.with_suffix(".json")
|
|
26
|
+
elif output_path.suffix != ".json":
|
|
27
|
+
json_path = output_path.with_suffix(".json")
|
|
28
|
+
else:
|
|
29
|
+
json_path = output_path
|
|
30
|
+
|
|
31
|
+
# Convert Pydantic models to dict if needed
|
|
32
|
+
if hasattr(data, "model_dump"):
|
|
33
|
+
json_data = data.model_dump()
|
|
34
|
+
elif hasattr(data, "dict"):
|
|
35
|
+
json_data = data.dict()
|
|
36
|
+
elif isinstance(data, dict):
|
|
37
|
+
json_data = data
|
|
38
|
+
else:
|
|
39
|
+
json_data = {"data": str(data)}
|
|
40
|
+
|
|
41
|
+
# Ensure directory exists
|
|
42
|
+
json_path.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
# Write JSON file
|
|
45
|
+
json_path.write_text(
|
|
46
|
+
json.dumps(json_data, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
logger.info(
|
|
50
|
+
"JSON output generated",
|
|
51
|
+
artifact_type=artifact_type,
|
|
52
|
+
path=str(json_path),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return json_path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def serialize_to_json(data: Any) -> str:
|
|
59
|
+
"""Serialize data to JSON string.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
data: Data to serialize (dict, Pydantic model, or other)
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
JSON string
|
|
66
|
+
"""
|
|
67
|
+
if hasattr(data, "model_dump"):
|
|
68
|
+
json_data = data.model_dump()
|
|
69
|
+
elif hasattr(data, "dict"):
|
|
70
|
+
json_data = data.dict()
|
|
71
|
+
elif isinstance(data, dict):
|
|
72
|
+
json_data = data
|
|
73
|
+
else:
|
|
74
|
+
json_data = {"data": str(data)}
|
|
75
|
+
|
|
76
|
+
return json.dumps(json_data, indent=2, ensure_ascii=False)
|