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.
Files changed (61) hide show
  1. super_dev/__init__.py +11 -0
  2. super_dev/analyzer/__init__.py +34 -0
  3. super_dev/analyzer/analyzer.py +440 -0
  4. super_dev/analyzer/detectors.py +511 -0
  5. super_dev/analyzer/models.py +285 -0
  6. super_dev/cli.py +3257 -0
  7. super_dev/config/__init__.py +11 -0
  8. super_dev/config/frontend.py +557 -0
  9. super_dev/config/manager.py +281 -0
  10. super_dev/creators/__init__.py +26 -0
  11. super_dev/creators/creator.py +134 -0
  12. super_dev/creators/document_generator.py +2473 -0
  13. super_dev/creators/frontend_builder.py +371 -0
  14. super_dev/creators/implementation_builder.py +789 -0
  15. super_dev/creators/prompt_generator.py +289 -0
  16. super_dev/creators/requirement_parser.py +354 -0
  17. super_dev/creators/spec_builder.py +195 -0
  18. super_dev/deployers/__init__.py +20 -0
  19. super_dev/deployers/cicd.py +1269 -0
  20. super_dev/deployers/delivery.py +229 -0
  21. super_dev/deployers/migration.py +1032 -0
  22. super_dev/design/__init__.py +74 -0
  23. super_dev/design/aesthetics.py +530 -0
  24. super_dev/design/charts.py +396 -0
  25. super_dev/design/codegen.py +379 -0
  26. super_dev/design/engine.py +528 -0
  27. super_dev/design/generator.py +395 -0
  28. super_dev/design/landing.py +422 -0
  29. super_dev/design/tech_stack.py +524 -0
  30. super_dev/design/tokens.py +269 -0
  31. super_dev/design/ux_guide.py +391 -0
  32. super_dev/exceptions.py +119 -0
  33. super_dev/experts/__init__.py +19 -0
  34. super_dev/experts/service.py +161 -0
  35. super_dev/integrations/__init__.py +7 -0
  36. super_dev/integrations/manager.py +264 -0
  37. super_dev/orchestrator/__init__.py +12 -0
  38. super_dev/orchestrator/engine.py +958 -0
  39. super_dev/orchestrator/experts.py +423 -0
  40. super_dev/orchestrator/knowledge.py +352 -0
  41. super_dev/orchestrator/quality.py +356 -0
  42. super_dev/reviewers/__init__.py +17 -0
  43. super_dev/reviewers/code_review.py +471 -0
  44. super_dev/reviewers/quality_gate.py +964 -0
  45. super_dev/reviewers/redteam.py +881 -0
  46. super_dev/skills/__init__.py +7 -0
  47. super_dev/skills/manager.py +307 -0
  48. super_dev/specs/__init__.py +44 -0
  49. super_dev/specs/generator.py +264 -0
  50. super_dev/specs/manager.py +428 -0
  51. super_dev/specs/models.py +348 -0
  52. super_dev/specs/validator.py +415 -0
  53. super_dev/utils/__init__.py +11 -0
  54. super_dev/utils/logger.py +133 -0
  55. super_dev/web/api.py +1402 -0
  56. super_dev-2.0.0.dist-info/METADATA +252 -0
  57. super_dev-2.0.0.dist-info/RECORD +61 -0
  58. super_dev-2.0.0.dist-info/WHEEL +5 -0
  59. super_dev-2.0.0.dist-info/entry_points.txt +2 -0
  60. super_dev-2.0.0.dist-info/licenses/LICENSE +21 -0
  61. super_dev-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,428 @@
1
+ """
2
+ Spec-Driven Development 管理器
3
+
4
+ 开发:Excellent(11964948@qq.com)
5
+ 功能:管理规范和变更的文件读写
6
+ 作用:处理 specs/ 和 changes/ 目录的文件操作
7
+ 创建时间:2025-12-30
8
+ """
9
+
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+
13
+ import yaml # type: ignore[import-untyped]
14
+
15
+ from .models import (
16
+ Change,
17
+ ChangeStatus,
18
+ DeltaType,
19
+ Proposal,
20
+ Requirement,
21
+ Scenario,
22
+ Spec,
23
+ SpecDelta,
24
+ Task,
25
+ TaskStatus,
26
+ )
27
+
28
+
29
+ class SpecManager:
30
+ """规范管理器 - 管理当前规范 (specs/)"""
31
+
32
+ def __init__(self, project_dir: Path | str):
33
+ """初始化规范管理器"""
34
+ self.project_dir = Path(project_dir).resolve()
35
+ self.specs_dir = self.project_dir / ".super-dev" / "specs"
36
+
37
+ def init(self):
38
+ """初始化规范目录"""
39
+ self.specs_dir.mkdir(parents=True, exist_ok=True)
40
+ (self.specs_dir / ".gitkeep").touch()
41
+
42
+ def get_spec_path(self, spec_name: str) -> Path:
43
+ """获取规范文件路径"""
44
+ return self.specs_dir / spec_name / "spec.md"
45
+
46
+ def load_spec(self, spec_name: str) -> Spec | None:
47
+ """加载规范"""
48
+ spec_path = self.get_spec_path(spec_name)
49
+ if not spec_path.exists():
50
+ return None
51
+
52
+ content = spec_path.read_text(encoding="utf-8")
53
+ return self._parse_spec(spec_name, content)
54
+
55
+ def save_spec(self, spec: Spec):
56
+ """保存规范"""
57
+ spec_path = self.get_spec_path(spec.name)
58
+ spec_path.parent.mkdir(parents=True, exist_ok=True)
59
+ spec.updated_at = datetime.now()
60
+ spec_path.write_text(spec.to_markdown(), encoding="utf-8")
61
+
62
+ def list_specs(self) -> list[str]:
63
+ """列出所有规范"""
64
+ if not self.specs_dir.exists():
65
+ return []
66
+ return [
67
+ d.name for d in self.specs_dir.iterdir()
68
+ if d.is_dir() and not d.name.startswith(".")
69
+ ]
70
+
71
+ def delete_spec(self, spec_name: str):
72
+ """删除规范"""
73
+ spec_path = self.get_spec_path(spec_name)
74
+ if spec_path.exists():
75
+ spec_path.parent.parent.rmdir()
76
+
77
+ def _parse_spec(self, spec_name: str, content: str) -> Spec:
78
+ """从 Markdown 解析规范"""
79
+ lines = content.split("\n")
80
+
81
+ spec = Spec(name=spec_name)
82
+ current_section = None
83
+ current_req = None
84
+
85
+ for line in lines:
86
+ # 解析标题
87
+ if line.startswith("# "):
88
+ spec.title = line[2:].strip()
89
+ elif line.startswith("## Purpose"):
90
+ current_section = "purpose"
91
+ elif line.startswith("## Requirements"):
92
+ current_section = "requirements"
93
+ elif line.startswith("### Requirement:"):
94
+ if current_req:
95
+ spec.requirements.append(current_req)
96
+ current_req = Requirement(name=line[16:].strip())
97
+ current_section = "requirement"
98
+ elif line.startswith("#### Scenario:"):
99
+ if current_req:
100
+ scenario = Scenario(when=line[13:].strip())
101
+ current_req.scenarios.append(scenario)
102
+ elif current_section == "purpose" and line.strip():
103
+ spec.purpose += line.strip() + "\n"
104
+ elif current_section == "requirement" and current_req:
105
+ if line.strip().startswith(("SHALL", "MUST", "SHOULD", "MAY")):
106
+ parts = line.strip().split(" ", 1)
107
+ current_req.keyword = parts[0]
108
+ current_req.description = parts[1] if len(parts) > 1 else ""
109
+ elif line.startswith("- GIVEN"):
110
+ if current_req.scenarios:
111
+ current_req.scenarios[-1].given = line[7:].strip()
112
+ elif line.startswith("- WHEN"):
113
+ if current_req.scenarios:
114
+ current_req.scenarios[-1].when = line[6:].strip()
115
+ elif line.startswith("- THEN"):
116
+ if current_req.scenarios:
117
+ current_req.scenarios[-1].then = line[6:].strip()
118
+
119
+ if current_req:
120
+ spec.requirements.append(current_req)
121
+
122
+ spec.purpose = spec.purpose.strip()
123
+ return spec
124
+
125
+
126
+ class ChangeManager:
127
+ """变更管理器 - 管理变更提案 (changes/)"""
128
+
129
+ def __init__(self, project_dir: Path | str):
130
+ """初始化变更管理器"""
131
+ self.project_dir = Path(project_dir).resolve()
132
+ self.changes_dir = self.project_dir / ".super-dev" / "changes"
133
+
134
+ def init(self):
135
+ """初始化变更目录"""
136
+ self.changes_dir.mkdir(parents=True, exist_ok=True)
137
+ (self.changes_dir / ".gitkeep").touch()
138
+
139
+ def get_change_path(self, change_id: str) -> Path:
140
+ """获取变更目录路径"""
141
+ return self.changes_dir / change_id
142
+
143
+ def load_change(self, change_id: str) -> Change | None:
144
+ """加载变更"""
145
+ change_path = self.get_change_path(change_id)
146
+ if not change_path.exists():
147
+ return None
148
+
149
+ change = Change(id=change_id, title=change_id.replace("-", " ").title())
150
+
151
+ # 加载提案
152
+ proposal_path = change_path / "proposal.md"
153
+ if proposal_path.exists():
154
+ change.proposal = self._parse_proposal(proposal_path.read_text(encoding="utf-8"))
155
+
156
+ # 加载任务
157
+ tasks_path = change_path / "tasks.md"
158
+ if tasks_path.exists():
159
+ change.tasks = self._parse_tasks(tasks_path.read_text(encoding="utf-8"))
160
+
161
+ # 加载设计笔记
162
+ design_path = change_path / "design.md"
163
+ if design_path.exists():
164
+ change.design_notes = design_path.read_text(encoding="utf-8")
165
+
166
+ # 加载规范增量
167
+ specs_deltas_dir = change_path / "specs"
168
+ if specs_deltas_dir.exists():
169
+ change.spec_deltas = self._parse_spec_deltas(specs_deltas_dir)
170
+
171
+ return change
172
+
173
+ def save_change(self, change: Change):
174
+ """保存变更"""
175
+ change_path = self.get_change_path(change.id)
176
+ change_path.mkdir(parents=True, exist_ok=True)
177
+ change.updated_at = datetime.now()
178
+
179
+ # 保存提案
180
+ if change.proposal:
181
+ proposal_path = change_path / "proposal.md"
182
+ proposal_path.write_text(change.proposal.to_markdown(), encoding="utf-8")
183
+
184
+ # 保存任务
185
+ tasks_path = change_path / "tasks.md"
186
+ tasks_path.write_text(self._format_tasks(change.tasks), encoding="utf-8")
187
+
188
+ # 保存设计笔记
189
+ if change.design_notes:
190
+ design_path = change_path / "design.md"
191
+ design_path.write_text(change.design_notes, encoding="utf-8")
192
+
193
+ # 保存规范增量
194
+ if change.spec_deltas:
195
+ for delta in change.spec_deltas:
196
+ delta_path = change_path / "specs" / delta.spec_name / "spec.md"
197
+ delta_path.parent.mkdir(parents=True, exist_ok=True)
198
+ delta_path.write_text(delta.to_markdown(), encoding="utf-8")
199
+
200
+ # 保存元数据
201
+ meta_path = change_path / "change.yaml"
202
+ with open(meta_path, "w", encoding="utf-8") as f:
203
+ yaml.dump({
204
+ "id": change.id,
205
+ "title": change.title,
206
+ "status": change.status.value,
207
+ "created_at": change.created_at.isoformat(),
208
+ "updated_at": change.updated_at.isoformat()
209
+ }, f, allow_unicode=True)
210
+
211
+ def list_changes(self, status: ChangeStatus | None = None) -> list[Change]:
212
+ """列出所有变更"""
213
+ if not self.changes_dir.exists():
214
+ return []
215
+
216
+ changes = []
217
+ for d in self.changes_dir.iterdir():
218
+ if d.is_dir() and not d.name.startswith("."):
219
+ change = self.load_change(d.name)
220
+ if change and (status is None or change.status == status):
221
+ changes.append(change)
222
+
223
+ return sorted(changes, key=lambda c: c.created_at, reverse=True)
224
+
225
+ def delete_change(self, change_id: str):
226
+ """删除变更"""
227
+ change_path = self.get_change_path(change_id)
228
+ if change_path.exists():
229
+ import shutil
230
+ shutil.rmtree(change_path)
231
+
232
+ def archive_change(self, change_id: str, spec_manager: SpecManager):
233
+ """归档变更 - 将规范增量合并到主规范"""
234
+ change = self.load_change(change_id)
235
+ if not change:
236
+ raise FileNotFoundError(f"变更不存在: {change_id}")
237
+
238
+ # 合并规范增量
239
+ for delta in change.spec_deltas:
240
+ spec = spec_manager.load_spec(delta.spec_name)
241
+ if not spec:
242
+ spec = Spec(name=delta.spec_name, title=delta.spec_name.replace("-", " ").title())
243
+
244
+ if delta.delta_type == DeltaType.ADDED:
245
+ spec.requirements.extend(delta.requirements)
246
+ elif delta.delta_type == DeltaType.MODIFIED:
247
+ # 替换同名需求
248
+ for new_req in delta.requirements:
249
+ for i, existing_req in enumerate(spec.requirements):
250
+ if existing_req.name == new_req.name:
251
+ spec.requirements[i] = new_req
252
+ break
253
+ else:
254
+ spec.requirements.append(new_req)
255
+ elif delta.delta_type == DeltaType.REMOVED:
256
+ # 移除指定需求
257
+ spec.requirements = [
258
+ r for r in spec.requirements
259
+ if r.name not in {req.name for req in delta.requirements}
260
+ ]
261
+
262
+ spec_manager.save_spec(spec)
263
+
264
+ # 移动到归档
265
+ archive_dir = self.changes_dir.parent / "archive" / change_id
266
+ import shutil
267
+ shutil.move(str(self.get_change_path(change_id)), str(archive_dir))
268
+
269
+ change.status = ChangeStatus.ARCHIVED
270
+ return change
271
+
272
+ def _parse_proposal(self, content: str) -> Proposal:
273
+ """解析提案"""
274
+ lines = content.split("\n")
275
+ proposal = Proposal(title="", description="")
276
+
277
+ current_section = None
278
+ for line in lines:
279
+ if line.startswith("## "):
280
+ current_section = line[3:].lower()
281
+ elif current_section == "description" and line.strip():
282
+ proposal.description += line.strip() + "\n"
283
+ elif current_section == "motivation" and line.strip():
284
+ proposal.motivation += line.strip() + "\n"
285
+ elif current_section == "impact" and line.strip():
286
+ proposal.impact += line.strip() + "\n"
287
+ elif line.startswith("# ") and not proposal.title:
288
+ proposal.title = line[2:].strip()
289
+
290
+ proposal.description = proposal.description.strip()
291
+ proposal.motivation = proposal.motivation.strip()
292
+ proposal.impact = proposal.impact.strip()
293
+
294
+ return proposal
295
+
296
+ def _parse_tasks(self, content: str) -> list[Task]:
297
+ """解析任务"""
298
+ tasks = []
299
+ for line in content.split("\n"):
300
+ if line.strip().startswith("- [") or line.strip().startswith("- [~") or line.strip().startswith("- [x]"):
301
+ # 解析任务行: - [ ] **1.1: Task Title**
302
+ status_str = line[3:5]
303
+ status = {
304
+ "[ ]": TaskStatus.PENDING,
305
+ "[~": TaskStatus.IN_PROGRESS,
306
+ "[x]": TaskStatus.COMPLETED,
307
+ "[_]": TaskStatus.SKIPPED,
308
+ }.get(status_str, TaskStatus.PENDING)
309
+
310
+ rest = line[5:].strip()
311
+
312
+ # 提取 ID 和标题
313
+ if rest.startswith("**"):
314
+ parts = rest.split(":", 1)
315
+ if len(parts) == 2:
316
+ task_id = parts[0][2:].strip()
317
+ title = parts[1].split("*", 1)[0].strip()
318
+ tasks.append(Task(id=task_id, title=title, status=status))
319
+
320
+ return tasks
321
+
322
+ def _format_tasks(self, tasks: list[Task]) -> str:
323
+ """格式化任务为 Markdown"""
324
+ lines = ["# Tasks", ""]
325
+ groups: dict[str, list[Task]] = {}
326
+ for task in tasks:
327
+ group = task.id.split(".")[0]
328
+ if group not in groups:
329
+ groups[group] = []
330
+ groups[group].append(task)
331
+
332
+ for group in sorted(groups.keys()):
333
+ group_name = {
334
+ "1": "1. Setup",
335
+ "2": "2. Backend",
336
+ "3": "3. Frontend",
337
+ "4": "4. Testing",
338
+ "5": "5. Documentation"
339
+ }.get(group, f"{group}. Other")
340
+ lines.append(f"## {group_name}")
341
+ lines.append("")
342
+ for task in groups[group]:
343
+ lines.append(task.to_markdown())
344
+ lines.append("")
345
+
346
+ return "\n".join(lines)
347
+
348
+ def _parse_spec_deltas(self, specs_dir: Path) -> list[SpecDelta]:
349
+ """解析规范增量"""
350
+ deltas = []
351
+ for spec_dir in specs_dir.iterdir():
352
+ if spec_dir.is_dir():
353
+ spec_path = spec_dir / "spec.md"
354
+ if spec_path.exists():
355
+ content = spec_path.read_text(encoding="utf-8")
356
+ # 解析增量类型
357
+ delta_type = DeltaType.ADDED
358
+ for line in content.split("\n"):
359
+ if line.startswith("## ADDED"):
360
+ delta_type = DeltaType.ADDED
361
+ break
362
+ elif line.startswith("## MODIFIED"):
363
+ delta_type = DeltaType.MODIFIED
364
+ break
365
+ elif line.startswith("## REMOVED"):
366
+ delta_type = DeltaType.REMOVED
367
+ break
368
+
369
+ requirements: list[Requirement] = []
370
+ current_req: Requirement | None = None
371
+ current_scenario: Scenario | None = None
372
+
373
+ for raw_line in content.split("\n"):
374
+ line = raw_line.strip()
375
+ if line.startswith("### Requirement:"):
376
+ if current_req:
377
+ requirements.append(current_req)
378
+ req_name = line[len("### Requirement:"):].strip()
379
+ current_req = Requirement(name=req_name)
380
+ current_scenario = None
381
+ continue
382
+
383
+ if current_req is None:
384
+ continue
385
+
386
+ if line.startswith(("SHALL ", "MUST ", "SHOULD ", "MAY ")):
387
+ parts = line.split(" ", 1)
388
+ current_req.keyword = parts[0]
389
+ current_req.description = parts[1] if len(parts) > 1 else ""
390
+ continue
391
+
392
+ if line.startswith("#### Scenario"):
393
+ current_scenario = Scenario()
394
+ current_req.scenarios.append(current_scenario)
395
+ continue
396
+
397
+ if line.startswith("- GIVEN"):
398
+ if current_scenario is None:
399
+ current_scenario = Scenario()
400
+ current_req.scenarios.append(current_scenario)
401
+ current_scenario.given = line[len("- GIVEN"):].strip()
402
+ continue
403
+
404
+ if line.startswith("- WHEN"):
405
+ if current_scenario is None:
406
+ current_scenario = Scenario()
407
+ current_req.scenarios.append(current_scenario)
408
+ current_scenario.when = line[len("- WHEN"):].strip()
409
+ continue
410
+
411
+ if line.startswith("- THEN"):
412
+ if current_scenario is None:
413
+ current_scenario = Scenario()
414
+ current_req.scenarios.append(current_scenario)
415
+ current_scenario.then = line[len("- THEN"):].strip()
416
+ continue
417
+
418
+ if current_req:
419
+ requirements.append(current_req)
420
+
421
+ deltas.append(SpecDelta(
422
+ spec_name=spec_dir.name,
423
+ delta_type=delta_type,
424
+ description="",
425
+ requirements=requirements
426
+ ))
427
+
428
+ return deltas