super-dev 2.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.
- super_dev/__init__.py +11 -0
- super_dev/analyzer/__init__.py +34 -0
- super_dev/analyzer/analyzer.py +440 -0
- super_dev/analyzer/detectors.py +511 -0
- super_dev/analyzer/models.py +285 -0
- super_dev/cli.py +3257 -0
- super_dev/config/__init__.py +11 -0
- super_dev/config/frontend.py +557 -0
- super_dev/config/manager.py +281 -0
- super_dev/creators/__init__.py +26 -0
- super_dev/creators/creator.py +134 -0
- super_dev/creators/document_generator.py +2473 -0
- super_dev/creators/frontend_builder.py +371 -0
- super_dev/creators/implementation_builder.py +789 -0
- super_dev/creators/prompt_generator.py +289 -0
- super_dev/creators/requirement_parser.py +354 -0
- super_dev/creators/spec_builder.py +195 -0
- super_dev/deployers/__init__.py +20 -0
- super_dev/deployers/cicd.py +1269 -0
- super_dev/deployers/delivery.py +229 -0
- super_dev/deployers/migration.py +1032 -0
- super_dev/design/__init__.py +74 -0
- super_dev/design/aesthetics.py +530 -0
- super_dev/design/charts.py +396 -0
- super_dev/design/codegen.py +379 -0
- super_dev/design/engine.py +528 -0
- super_dev/design/generator.py +395 -0
- super_dev/design/landing.py +422 -0
- super_dev/design/tech_stack.py +524 -0
- super_dev/design/tokens.py +269 -0
- super_dev/design/ux_guide.py +391 -0
- super_dev/exceptions.py +119 -0
- super_dev/experts/__init__.py +19 -0
- super_dev/experts/service.py +161 -0
- super_dev/integrations/__init__.py +7 -0
- super_dev/integrations/manager.py +264 -0
- super_dev/orchestrator/__init__.py +12 -0
- super_dev/orchestrator/engine.py +958 -0
- super_dev/orchestrator/experts.py +423 -0
- super_dev/orchestrator/knowledge.py +352 -0
- super_dev/orchestrator/quality.py +356 -0
- super_dev/reviewers/__init__.py +17 -0
- super_dev/reviewers/code_review.py +471 -0
- super_dev/reviewers/quality_gate.py +964 -0
- super_dev/reviewers/redteam.py +881 -0
- super_dev/skills/__init__.py +7 -0
- super_dev/skills/manager.py +307 -0
- super_dev/specs/__init__.py +44 -0
- super_dev/specs/generator.py +264 -0
- super_dev/specs/manager.py +428 -0
- super_dev/specs/models.py +348 -0
- super_dev/specs/validator.py +415 -0
- super_dev/utils/__init__.py +11 -0
- super_dev/utils/logger.py +133 -0
- super_dev/web/api.py +1402 -0
- super_dev-2.0.0.dist-info/METADATA +252 -0
- super_dev-2.0.0.dist-info/RECORD +61 -0
- super_dev-2.0.0.dist-info/WHEEL +5 -0
- super_dev-2.0.0.dist-info/entry_points.txt +2 -0
- super_dev-2.0.0.dist-info/licenses/LICENSE +21 -0
- super_dev-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""
|
|
2
|
+
专家系统调度器 - 协调 10 位专家协作生成文档
|
|
3
|
+
|
|
4
|
+
开发:Excellent(11964948@qq.com)
|
|
5
|
+
功能:调度专家角色,生成高质量项目文档
|
|
6
|
+
作用:将工作路由到正确的专家处理器
|
|
7
|
+
创建时间:2025-12-30
|
|
8
|
+
最后修改:2026-01-29
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Literal, cast
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ExpertRole(Enum):
|
|
20
|
+
"""专家角色枚举"""
|
|
21
|
+
PM = "PM" # 产品经理
|
|
22
|
+
ARCHITECT = "ARCHITECT" # 架构师
|
|
23
|
+
UI = "UI" # UI 设计师
|
|
24
|
+
UX = "UX" # UX 设计师
|
|
25
|
+
SECURITY = "SECURITY" # 安全专家
|
|
26
|
+
CODE = "CODE" # 代码专家
|
|
27
|
+
DBA = "DBA" # 数据库专家
|
|
28
|
+
QA = "QA" # 质量保证专家
|
|
29
|
+
DEVOPS = "DEVOPS" # DevOps 工程师
|
|
30
|
+
RCA = "RCA" # 根因分析专家
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
EXPERT_DESCRIPTIONS: dict[ExpertRole, str] = {
|
|
34
|
+
ExpertRole.PM: "需求分析、PRD 编写、用户故事、业务规则",
|
|
35
|
+
ExpertRole.ARCHITECT: "系统设计、技术选型、架构文档、API 设计",
|
|
36
|
+
ExpertRole.UI: "视觉设计、设计规范、组件库、品牌一致性",
|
|
37
|
+
ExpertRole.UX: "交互设计、用户体验、信息架构、可用性测试",
|
|
38
|
+
ExpertRole.SECURITY: "安全审查、漏洞检测、威胁建模、合规",
|
|
39
|
+
ExpertRole.CODE: "代码实现、最佳实践、代码审查、性能优化",
|
|
40
|
+
ExpertRole.DBA: "数据库设计、SQL 优化、数据建模、迁移策略",
|
|
41
|
+
ExpertRole.QA: "质量保证、测试策略、自动化测试、质量门禁",
|
|
42
|
+
ExpertRole.DEVOPS: "部署、CI/CD、容器化、监控告警",
|
|
43
|
+
ExpertRole.RCA: "根因分析、故障复盘、风险识别、改进建议",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ExpertOutput:
|
|
49
|
+
"""专家输出"""
|
|
50
|
+
role: ExpertRole
|
|
51
|
+
document_type: str # prd | architecture | uiux | redteam | quality-gate | ...
|
|
52
|
+
content: str
|
|
53
|
+
quality_score: int = 85 # 0-100
|
|
54
|
+
metadata: dict = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class ExpertTeamResult:
|
|
59
|
+
"""专家团队协作结果"""
|
|
60
|
+
outputs: list[ExpertOutput] = field(default_factory=list)
|
|
61
|
+
total_score: float = 0.0
|
|
62
|
+
summary: str = ""
|
|
63
|
+
|
|
64
|
+
def get_output(self, doc_type: str) -> ExpertOutput | None:
|
|
65
|
+
for out in self.outputs:
|
|
66
|
+
if out.document_type == doc_type:
|
|
67
|
+
return out
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ExpertDispatcher:
|
|
72
|
+
"""
|
|
73
|
+
专家调度器
|
|
74
|
+
|
|
75
|
+
根据任务类型将工作路由到正确的专家处理器,
|
|
76
|
+
并协调多专家协作完成文档生成任务。
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, project_dir: Path):
|
|
80
|
+
self.project_dir = Path(project_dir).resolve()
|
|
81
|
+
|
|
82
|
+
# ------------------------------------------------------------------
|
|
83
|
+
# 公共接口
|
|
84
|
+
# ------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def dispatch_document_generation(
|
|
87
|
+
self,
|
|
88
|
+
name: str,
|
|
89
|
+
description: str,
|
|
90
|
+
platform: str = "web",
|
|
91
|
+
frontend: str = "react",
|
|
92
|
+
backend: str = "node",
|
|
93
|
+
domain: str = "",
|
|
94
|
+
**kwargs,
|
|
95
|
+
) -> ExpertTeamResult:
|
|
96
|
+
"""
|
|
97
|
+
调度多专家协作生成完整项目文档集
|
|
98
|
+
|
|
99
|
+
调用顺序:PM → ARCHITECT → UI/UX → SECURITY → DBA → QA → DEVOPS
|
|
100
|
+
"""
|
|
101
|
+
from ..creators.document_generator import DocumentGenerator
|
|
102
|
+
|
|
103
|
+
gen = DocumentGenerator(
|
|
104
|
+
name=name,
|
|
105
|
+
description=description,
|
|
106
|
+
platform=platform,
|
|
107
|
+
frontend=frontend,
|
|
108
|
+
backend=backend,
|
|
109
|
+
domain=domain,
|
|
110
|
+
**kwargs,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
result = ExpertTeamResult()
|
|
114
|
+
|
|
115
|
+
# 1. PM 专家:生成 PRD
|
|
116
|
+
prd_content = gen.generate_prd()
|
|
117
|
+
result.outputs.append(ExpertOutput(
|
|
118
|
+
role=ExpertRole.PM,
|
|
119
|
+
document_type="prd",
|
|
120
|
+
content=prd_content,
|
|
121
|
+
quality_score=self._score_document(prd_content, ["产品愿景", "功能需求", "验收标准"]),
|
|
122
|
+
metadata={"name": name, "platform": platform},
|
|
123
|
+
))
|
|
124
|
+
|
|
125
|
+
# 2. ARCHITECT 专家:生成架构文档
|
|
126
|
+
arch_content = gen.generate_architecture()
|
|
127
|
+
result.outputs.append(ExpertOutput(
|
|
128
|
+
role=ExpertRole.ARCHITECT,
|
|
129
|
+
document_type="architecture",
|
|
130
|
+
content=arch_content,
|
|
131
|
+
quality_score=self._score_document(arch_content, ["技术栈", "数据库", "API", "安全"]),
|
|
132
|
+
metadata={"frontend": frontend, "backend": backend},
|
|
133
|
+
))
|
|
134
|
+
|
|
135
|
+
# 3. UI/UX 专家:生成 UI/UX 文档
|
|
136
|
+
uiux_content = gen.generate_uiux()
|
|
137
|
+
result.outputs.append(ExpertOutput(
|
|
138
|
+
role=ExpertRole.UI,
|
|
139
|
+
document_type="uiux",
|
|
140
|
+
content=uiux_content,
|
|
141
|
+
quality_score=self._score_document(uiux_content, ["设计系统", "色彩", "组件"]),
|
|
142
|
+
metadata={"platform": platform},
|
|
143
|
+
))
|
|
144
|
+
|
|
145
|
+
# 4. 计算团队总分
|
|
146
|
+
scores = [o.quality_score for o in result.outputs]
|
|
147
|
+
result.total_score = sum(scores) / len(scores) if scores else 0.0
|
|
148
|
+
result.summary = (
|
|
149
|
+
f"专家团队协作完成:生成 {len(result.outputs)} 份文档,"
|
|
150
|
+
f"平均质量分 {result.total_score:.0f}/100"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
def dispatch_redteam_review(
|
|
156
|
+
self,
|
|
157
|
+
name: str,
|
|
158
|
+
tech_stack: dict,
|
|
159
|
+
) -> ExpertOutput:
|
|
160
|
+
"""SECURITY 专家:调度红队审查"""
|
|
161
|
+
from ..reviewers.redteam import RedTeamReviewer
|
|
162
|
+
|
|
163
|
+
reviewer = RedTeamReviewer(
|
|
164
|
+
project_dir=self.project_dir,
|
|
165
|
+
name=name,
|
|
166
|
+
tech_stack=tech_stack,
|
|
167
|
+
)
|
|
168
|
+
report = reviewer.review()
|
|
169
|
+
content = report.to_markdown()
|
|
170
|
+
|
|
171
|
+
return ExpertOutput(
|
|
172
|
+
role=ExpertRole.SECURITY,
|
|
173
|
+
document_type="redteam",
|
|
174
|
+
content=content,
|
|
175
|
+
quality_score=report.total_score,
|
|
176
|
+
metadata={
|
|
177
|
+
"passed": report.passed,
|
|
178
|
+
"pass_threshold": report.pass_threshold,
|
|
179
|
+
"blocking_reasons": report.blocking_reasons,
|
|
180
|
+
"critical_count": report.critical_count,
|
|
181
|
+
"high_count": report.high_count,
|
|
182
|
+
"security_issues": [self._serialize_security_issue(i) for i in report.security_issues],
|
|
183
|
+
"performance_issues": [self._serialize_performance_issue(i) for i in report.performance_issues],
|
|
184
|
+
"architecture_issues": [self._serialize_architecture_issue(i) for i in report.architecture_issues],
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def dispatch_quality_gate(
|
|
189
|
+
self,
|
|
190
|
+
name: str,
|
|
191
|
+
tech_stack: dict,
|
|
192
|
+
redteam_report=None,
|
|
193
|
+
threshold_override: int | None = None,
|
|
194
|
+
) -> ExpertOutput:
|
|
195
|
+
"""QA 专家:调度质量门禁检查"""
|
|
196
|
+
from ..reviewers.quality_gate import QualityGateChecker
|
|
197
|
+
|
|
198
|
+
checker = QualityGateChecker(
|
|
199
|
+
project_dir=self.project_dir,
|
|
200
|
+
name=name,
|
|
201
|
+
tech_stack=tech_stack,
|
|
202
|
+
threshold_override=threshold_override,
|
|
203
|
+
)
|
|
204
|
+
result = checker.check(redteam_report=redteam_report)
|
|
205
|
+
content = result.to_markdown()
|
|
206
|
+
|
|
207
|
+
return ExpertOutput(
|
|
208
|
+
role=ExpertRole.QA,
|
|
209
|
+
document_type="quality-gate",
|
|
210
|
+
content=content,
|
|
211
|
+
quality_score=result.total_score,
|
|
212
|
+
metadata={
|
|
213
|
+
"passed": result.passed,
|
|
214
|
+
"scenario": result.scenario,
|
|
215
|
+
"weighted_score": result.weighted_score,
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def dispatch_code_review(
|
|
220
|
+
self,
|
|
221
|
+
name: str,
|
|
222
|
+
tech_stack: dict,
|
|
223
|
+
) -> ExpertOutput:
|
|
224
|
+
"""CODE 专家:调度代码审查"""
|
|
225
|
+
from ..reviewers.code_review import CodeReviewGenerator
|
|
226
|
+
|
|
227
|
+
generator = CodeReviewGenerator(
|
|
228
|
+
project_dir=self.project_dir,
|
|
229
|
+
name=name,
|
|
230
|
+
tech_stack=tech_stack,
|
|
231
|
+
)
|
|
232
|
+
content = generator.generate()
|
|
233
|
+
|
|
234
|
+
return ExpertOutput(
|
|
235
|
+
role=ExpertRole.CODE,
|
|
236
|
+
document_type="code-review",
|
|
237
|
+
content=content,
|
|
238
|
+
quality_score=85,
|
|
239
|
+
metadata={"tech_stack": tech_stack},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def dispatch_ai_prompt(self, name: str) -> ExpertOutput:
|
|
243
|
+
"""CODE 专家:生成 AI 提示词"""
|
|
244
|
+
from ..creators.prompt_generator import AIPromptGenerator
|
|
245
|
+
|
|
246
|
+
generator = AIPromptGenerator(
|
|
247
|
+
project_dir=self.project_dir,
|
|
248
|
+
name=name,
|
|
249
|
+
)
|
|
250
|
+
content = generator.generate()
|
|
251
|
+
|
|
252
|
+
return ExpertOutput(
|
|
253
|
+
role=ExpertRole.CODE,
|
|
254
|
+
document_type="ai-prompt",
|
|
255
|
+
content=content,
|
|
256
|
+
quality_score=90,
|
|
257
|
+
metadata={"name": name},
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def dispatch_cicd(
|
|
261
|
+
self,
|
|
262
|
+
name: str,
|
|
263
|
+
tech_stack: dict,
|
|
264
|
+
cicd_platform: str = "github",
|
|
265
|
+
) -> ExpertOutput:
|
|
266
|
+
"""DEVOPS 专家:生成 CI/CD 配置"""
|
|
267
|
+
from ..deployers.cicd import CICDGenerator
|
|
268
|
+
|
|
269
|
+
generator = CICDGenerator(
|
|
270
|
+
project_dir=self.project_dir,
|
|
271
|
+
name=name,
|
|
272
|
+
tech_stack=tech_stack,
|
|
273
|
+
platform=self._normalize_cicd_platform(cicd_platform),
|
|
274
|
+
)
|
|
275
|
+
generated_files = generator.generate()
|
|
276
|
+
content = self._render_generated_files_markdown(
|
|
277
|
+
title=f"{name} - CI/CD 配置",
|
|
278
|
+
generated_files=generated_files,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return ExpertOutput(
|
|
282
|
+
role=ExpertRole.DEVOPS,
|
|
283
|
+
document_type="cicd",
|
|
284
|
+
content=content,
|
|
285
|
+
quality_score=88,
|
|
286
|
+
metadata={
|
|
287
|
+
"platform": cicd_platform,
|
|
288
|
+
"generated_files": list(generated_files.keys()),
|
|
289
|
+
"generated_file_contents": generated_files,
|
|
290
|
+
},
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def dispatch_migration(
|
|
294
|
+
self,
|
|
295
|
+
name: str,
|
|
296
|
+
tech_stack: dict,
|
|
297
|
+
orm: str = "prisma",
|
|
298
|
+
) -> ExpertOutput:
|
|
299
|
+
"""DBA 专家:生成数据库迁移脚本"""
|
|
300
|
+
from ..deployers.migration import MigrationGenerator
|
|
301
|
+
|
|
302
|
+
generator = MigrationGenerator(
|
|
303
|
+
project_dir=self.project_dir,
|
|
304
|
+
name=name,
|
|
305
|
+
tech_stack=tech_stack,
|
|
306
|
+
orm_type=self._normalize_orm_type(orm),
|
|
307
|
+
)
|
|
308
|
+
generated_files = generator.generate()
|
|
309
|
+
content = self._render_generated_files_markdown(
|
|
310
|
+
title=f"{name} - 数据库迁移脚本",
|
|
311
|
+
generated_files=generated_files,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return ExpertOutput(
|
|
315
|
+
role=ExpertRole.DBA,
|
|
316
|
+
document_type="migration",
|
|
317
|
+
content=content,
|
|
318
|
+
quality_score=87,
|
|
319
|
+
metadata={
|
|
320
|
+
"orm": orm,
|
|
321
|
+
"generated_files": list(generated_files.keys()),
|
|
322
|
+
"generated_file_contents": generated_files,
|
|
323
|
+
},
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def list_experts(self) -> list[dict]:
|
|
327
|
+
"""列出所有专家信息"""
|
|
328
|
+
return [
|
|
329
|
+
{
|
|
330
|
+
"role": role.value,
|
|
331
|
+
"description": desc,
|
|
332
|
+
}
|
|
333
|
+
for role, desc in EXPERT_DESCRIPTIONS.items()
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
# ------------------------------------------------------------------
|
|
337
|
+
# 内部辅助
|
|
338
|
+
# ------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
def _score_document(self, content: str, required_keywords: list[str]) -> int:
|
|
341
|
+
"""基于关键词检测评估文档质量分"""
|
|
342
|
+
if not content:
|
|
343
|
+
return 0
|
|
344
|
+
base = 70
|
|
345
|
+
per_keyword = 10
|
|
346
|
+
for kw in required_keywords:
|
|
347
|
+
if kw in content:
|
|
348
|
+
base += per_keyword
|
|
349
|
+
# 长度加分(越详细越好,上限 100)
|
|
350
|
+
length_bonus = min(10, len(content) // 2000)
|
|
351
|
+
return min(100, base + length_bonus)
|
|
352
|
+
|
|
353
|
+
def _serialize_security_issue(self, issue) -> dict:
|
|
354
|
+
return {
|
|
355
|
+
"severity": issue.severity,
|
|
356
|
+
"category": issue.category,
|
|
357
|
+
"description": issue.description,
|
|
358
|
+
"recommendation": issue.recommendation,
|
|
359
|
+
"cwe": issue.cwe,
|
|
360
|
+
"file_path": issue.file_path,
|
|
361
|
+
"line": issue.line,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
def _serialize_performance_issue(self, issue) -> dict:
|
|
365
|
+
return {
|
|
366
|
+
"severity": issue.severity,
|
|
367
|
+
"category": issue.category,
|
|
368
|
+
"description": issue.description,
|
|
369
|
+
"recommendation": issue.recommendation,
|
|
370
|
+
"impact": issue.impact,
|
|
371
|
+
"file_path": issue.file_path,
|
|
372
|
+
"line": issue.line,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
def _serialize_architecture_issue(self, issue) -> dict:
|
|
376
|
+
return {
|
|
377
|
+
"severity": issue.severity,
|
|
378
|
+
"category": issue.category,
|
|
379
|
+
"description": issue.description,
|
|
380
|
+
"recommendation": issue.recommendation,
|
|
381
|
+
"adr_needed": issue.adr_needed,
|
|
382
|
+
"file_path": issue.file_path,
|
|
383
|
+
"line": issue.line,
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
def _normalize_cicd_platform(
|
|
387
|
+
self, platform: str
|
|
388
|
+
) -> Literal["github", "gitlab", "jenkins", "azure", "bitbucket", "all"]:
|
|
389
|
+
normalized = (platform or "").strip().lower()
|
|
390
|
+
allowed = {"github", "gitlab", "jenkins", "azure", "bitbucket", "all"}
|
|
391
|
+
if normalized in allowed:
|
|
392
|
+
return cast(
|
|
393
|
+
Literal["github", "gitlab", "jenkins", "azure", "bitbucket", "all"],
|
|
394
|
+
normalized,
|
|
395
|
+
)
|
|
396
|
+
return "github"
|
|
397
|
+
|
|
398
|
+
def _normalize_orm_type(self, orm: str):
|
|
399
|
+
from ..deployers.migration import ORMType
|
|
400
|
+
|
|
401
|
+
mapping = {
|
|
402
|
+
"prisma": ORMType.PRISMA,
|
|
403
|
+
"typeorm": ORMType.TYPEORM,
|
|
404
|
+
"sequelize": ORMType.SEQUELIZE,
|
|
405
|
+
"sqlalchemy": ORMType.SQLALCHEMY,
|
|
406
|
+
"django": ORMType.DJANGO,
|
|
407
|
+
"mongoose": ORMType.MONGOOSE,
|
|
408
|
+
}
|
|
409
|
+
return mapping.get((orm or "").strip().lower())
|
|
410
|
+
|
|
411
|
+
def _render_generated_files_markdown(self, title: str, generated_files: dict[str, str]) -> str:
|
|
412
|
+
lines = [
|
|
413
|
+
f"# {title}",
|
|
414
|
+
"",
|
|
415
|
+
f"共生成 {len(generated_files)} 个文件。",
|
|
416
|
+
"",
|
|
417
|
+
"## 文件列表",
|
|
418
|
+
"",
|
|
419
|
+
]
|
|
420
|
+
for file_path in sorted(generated_files.keys()):
|
|
421
|
+
lines.append(f"- `{file_path}`")
|
|
422
|
+
lines.append("")
|
|
423
|
+
return "\n".join(lines)
|