kiro-spec-engine 1.2.3 → 1.3.0
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.
- package/CHANGELOG.md +74 -0
- package/README.md +172 -0
- package/bin/kiro-spec-engine.js +62 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/cross-tool-guide.md +554 -0
- package/docs/manual-workflows-guide.md +417 -0
- package/docs/steering-strategy-guide.md +196 -0
- package/lib/adoption/detection-engine.js +14 -4
- package/lib/commands/adopt.js +117 -3
- package/lib/commands/context.js +99 -0
- package/lib/commands/prompt.js +105 -0
- package/lib/commands/status.js +225 -0
- package/lib/commands/task.js +199 -0
- package/lib/commands/watch.js +569 -0
- package/lib/commands/workflows.js +240 -0
- package/lib/commands/workspace.js +189 -0
- package/lib/context/context-exporter.js +378 -0
- package/lib/context/prompt-generator.js +482 -0
- package/lib/steering/adoption-config.js +164 -0
- package/lib/steering/steering-manager.js +289 -0
- package/lib/task/task-claimer.js +430 -0
- package/lib/utils/tool-detector.js +383 -0
- package/lib/watch/action-executor.js +458 -0
- package/lib/watch/event-debouncer.js +323 -0
- package/lib/watch/execution-logger.js +550 -0
- package/lib/watch/file-watcher.js +499 -0
- package/lib/watch/presets.js +266 -0
- package/lib/watch/watch-manager.js +533 -0
- package/lib/workspace/workspace-manager.js +370 -0
- package/lib/workspace/workspace-sync.js +356 -0
- package/package.json +3 -1
- package/template/.kiro/tools/backup_manager.py +295 -0
- package/template/.kiro/tools/configuration_manager.py +218 -0
- package/template/.kiro/tools/document_evaluator.py +550 -0
- package/template/.kiro/tools/enhancement_logger.py +168 -0
- package/template/.kiro/tools/error_handler.py +335 -0
- package/template/.kiro/tools/improvement_identifier.py +444 -0
- package/template/.kiro/tools/modification_applicator.py +737 -0
- package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
- package/template/.kiro/tools/quality_scorer.py +305 -0
- package/template/.kiro/tools/report_generator.py +154 -0
- package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
- package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
- package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
- package/template/.kiro/tools/workflow_quality_gate.py +100 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quality Gate Enforcer - 质量门控组件
|
|
4
|
+
|
|
5
|
+
负责在 Spec 创建工作流中强制执行质量标准
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from ultrawork_enhancer_v3 import UltraworkEnhancerV3, EnhancementResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class GateResult:
|
|
15
|
+
"""质量门结果"""
|
|
16
|
+
passed: bool
|
|
17
|
+
score: float
|
|
18
|
+
threshold: float
|
|
19
|
+
enhancement_result: Optional[EnhancementResult] = None
|
|
20
|
+
message: str = ""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class QualityGateEnforcer:
|
|
24
|
+
"""
|
|
25
|
+
质量门控器 - 强制执行质量标准
|
|
26
|
+
|
|
27
|
+
集成到 Spec 创建工作流,确保文档达到质量阈值
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self,
|
|
31
|
+
requirements_threshold: float = 9.0,
|
|
32
|
+
design_threshold: float = 9.0,
|
|
33
|
+
tasks_threshold: float = 8.0):
|
|
34
|
+
"""
|
|
35
|
+
初始化质量门控器
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
requirements_threshold: Requirements 质量阈值
|
|
39
|
+
design_threshold: Design 质量阈值
|
|
40
|
+
tasks_threshold: Tasks 质量阈值
|
|
41
|
+
"""
|
|
42
|
+
self.requirements_threshold = requirements_threshold
|
|
43
|
+
self.design_threshold = design_threshold
|
|
44
|
+
self.tasks_threshold = tasks_threshold
|
|
45
|
+
|
|
46
|
+
# 创建增强器实例
|
|
47
|
+
self.enhancer = UltraworkEnhancerV3(
|
|
48
|
+
quality_threshold=requirements_threshold,
|
|
49
|
+
max_iterations=10,
|
|
50
|
+
plateau_iterations=3,
|
|
51
|
+
create_backups=True,
|
|
52
|
+
cleanup_backups_on_success=True
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def check_requirements_gate(self, requirements_path: str) -> GateResult:
|
|
56
|
+
"""
|
|
57
|
+
检查 Requirements 质量门
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
requirements_path: Requirements 文档路径
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
GateResult: 质量门结果
|
|
64
|
+
"""
|
|
65
|
+
print(f"\n{'='*60}")
|
|
66
|
+
print(f"Quality Gate: Requirements")
|
|
67
|
+
print(f"Threshold: {self.requirements_threshold}/10")
|
|
68
|
+
print(f"{'='*60}\n")
|
|
69
|
+
|
|
70
|
+
# 设置阈值
|
|
71
|
+
self.enhancer.set_quality_threshold(self.requirements_threshold)
|
|
72
|
+
|
|
73
|
+
# 执行增强
|
|
74
|
+
result = self.enhancer.enhance_requirements_quality(requirements_path)
|
|
75
|
+
|
|
76
|
+
# 检查是否通过
|
|
77
|
+
passed = result.final_score >= self.requirements_threshold
|
|
78
|
+
|
|
79
|
+
if passed:
|
|
80
|
+
message = f"✓ Requirements quality gate PASSED ({result.final_score:.2f}/{self.requirements_threshold})"
|
|
81
|
+
else:
|
|
82
|
+
message = f"✗ Requirements quality gate FAILED ({result.final_score:.2f}/{self.requirements_threshold})"
|
|
83
|
+
|
|
84
|
+
print(f"\n{message}\n")
|
|
85
|
+
|
|
86
|
+
return GateResult(
|
|
87
|
+
passed=passed,
|
|
88
|
+
score=result.final_score,
|
|
89
|
+
threshold=self.requirements_threshold,
|
|
90
|
+
enhancement_result=result,
|
|
91
|
+
message=message
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def check_design_gate(self, design_path: str, requirements_path: str) -> GateResult:
|
|
95
|
+
"""
|
|
96
|
+
检查 Design 质量门
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
design_path: Design 文档路径
|
|
100
|
+
requirements_path: Requirements 文档路径
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
GateResult: 质量门结果
|
|
104
|
+
"""
|
|
105
|
+
print(f"\n{'='*60}")
|
|
106
|
+
print(f"Quality Gate: Design")
|
|
107
|
+
print(f"Threshold: {self.design_threshold}/10")
|
|
108
|
+
print(f"{'='*60}\n")
|
|
109
|
+
|
|
110
|
+
# 设置阈值
|
|
111
|
+
self.enhancer.set_quality_threshold(self.design_threshold)
|
|
112
|
+
|
|
113
|
+
# 执行增强
|
|
114
|
+
result = self.enhancer.enhance_design_completeness(design_path, requirements_path)
|
|
115
|
+
|
|
116
|
+
# 检查是否通过
|
|
117
|
+
passed = result.final_score >= self.design_threshold
|
|
118
|
+
|
|
119
|
+
if passed:
|
|
120
|
+
message = f"✓ Design quality gate PASSED ({result.final_score:.2f}/{self.design_threshold})"
|
|
121
|
+
else:
|
|
122
|
+
message = f"✗ Design quality gate FAILED ({result.final_score:.2f}/{self.design_threshold})"
|
|
123
|
+
|
|
124
|
+
print(f"\n{message}\n")
|
|
125
|
+
|
|
126
|
+
return GateResult(
|
|
127
|
+
passed=passed,
|
|
128
|
+
score=result.final_score,
|
|
129
|
+
threshold=self.design_threshold,
|
|
130
|
+
enhancement_result=result,
|
|
131
|
+
message=message
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def check_tasks_gate(self, tasks_path: str) -> GateResult:
|
|
135
|
+
"""
|
|
136
|
+
检查 Tasks 质量门
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
tasks_path: Tasks 文档路径
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
GateResult: 质量门结果
|
|
143
|
+
"""
|
|
144
|
+
print(f"\n{'='*60}")
|
|
145
|
+
print(f"Quality Gate: Tasks")
|
|
146
|
+
print(f"Threshold: {self.tasks_threshold}/10")
|
|
147
|
+
print(f"{'='*60}\n")
|
|
148
|
+
|
|
149
|
+
# 执行验证
|
|
150
|
+
result = self.enhancer.validate_tasks_completeness(tasks_path)
|
|
151
|
+
|
|
152
|
+
# 检查是否通过
|
|
153
|
+
passed = result.final_score >= self.tasks_threshold
|
|
154
|
+
|
|
155
|
+
if passed:
|
|
156
|
+
message = f"✓ Tasks quality gate PASSED ({result.final_score:.2f}/{self.tasks_threshold})"
|
|
157
|
+
else:
|
|
158
|
+
message = f"✗ Tasks quality gate FAILED ({result.final_score:.2f}/{self.tasks_threshold})"
|
|
159
|
+
|
|
160
|
+
print(f"\n{message}\n")
|
|
161
|
+
|
|
162
|
+
return GateResult(
|
|
163
|
+
passed=passed,
|
|
164
|
+
score=result.final_score,
|
|
165
|
+
threshold=self.tasks_threshold,
|
|
166
|
+
enhancement_result=result,
|
|
167
|
+
message=message
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def main():
|
|
172
|
+
"""命令行入口"""
|
|
173
|
+
import sys
|
|
174
|
+
|
|
175
|
+
if len(sys.argv) < 3:
|
|
176
|
+
print("Usage: python quality_gate_enforcer.py <gate> <path> [requirements_path]")
|
|
177
|
+
print("Gates:")
|
|
178
|
+
print(" requirements <path> - Check requirements quality gate")
|
|
179
|
+
print(" design <path> <requirements> - Check design quality gate")
|
|
180
|
+
print(" tasks <path> - Check tasks quality gate")
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
gate = sys.argv[1]
|
|
184
|
+
path = sys.argv[2]
|
|
185
|
+
|
|
186
|
+
enforcer = QualityGateEnforcer()
|
|
187
|
+
|
|
188
|
+
if gate == 'requirements':
|
|
189
|
+
result = enforcer.check_requirements_gate(path)
|
|
190
|
+
elif gate == 'design':
|
|
191
|
+
if len(sys.argv) < 4:
|
|
192
|
+
print("Error: design gate requires requirements path")
|
|
193
|
+
sys.exit(1)
|
|
194
|
+
requirements_path = sys.argv[3]
|
|
195
|
+
result = enforcer.check_design_gate(path, requirements_path)
|
|
196
|
+
elif gate == 'tasks':
|
|
197
|
+
result = enforcer.check_tasks_gate(path)
|
|
198
|
+
else:
|
|
199
|
+
print(f"Error: Unknown gate '{gate}'")
|
|
200
|
+
sys.exit(1)
|
|
201
|
+
|
|
202
|
+
# 返回退出码:0 = 通过,1 = 失败
|
|
203
|
+
sys.exit(0 if result.passed else 1)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if __name__ == '__main__':
|
|
207
|
+
main()
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quality Scorer - 质量评分组件
|
|
4
|
+
|
|
5
|
+
负责计算文档质量分数,提供加权评分算法和详细评分分解
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from document_evaluator import DocumentEvaluator, QualityAssessment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class CriterionScore:
|
|
15
|
+
"""单项评分标准"""
|
|
16
|
+
name: str
|
|
17
|
+
score: float # 0-10
|
|
18
|
+
weight: float # 0-1
|
|
19
|
+
weighted_score: float # score * weight
|
|
20
|
+
max_score: float = 10.0
|
|
21
|
+
description: str = ""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ScoringResult:
|
|
26
|
+
"""评分结果"""
|
|
27
|
+
total_score: float # 0-10
|
|
28
|
+
criterion_scores: Dict[str, CriterionScore] = field(default_factory=dict)
|
|
29
|
+
language: str = 'en'
|
|
30
|
+
scoring_breakdown: str = ""
|
|
31
|
+
assessment: Optional[QualityAssessment] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class QualityScorer:
|
|
35
|
+
"""
|
|
36
|
+
质量评分器 - 计算文档质量分数
|
|
37
|
+
|
|
38
|
+
支持加权评分算法和详细评分分解
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# 默认权重配置
|
|
42
|
+
DEFAULT_REQUIREMENTS_WEIGHTS = {
|
|
43
|
+
'structure': 0.20, # 20%
|
|
44
|
+
'ears_format': 0.20, # 20%
|
|
45
|
+
'user_stories': 0.20, # 20%
|
|
46
|
+
'acceptance_criteria': 0.20, # 20%
|
|
47
|
+
'nfr_coverage': 0.10, # 10%
|
|
48
|
+
'constraints': 0.10 # 10%
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
DEFAULT_DESIGN_WEIGHTS = {
|
|
52
|
+
'structure': 0.25, # 25%
|
|
53
|
+
'traceability': 0.20, # 20%
|
|
54
|
+
'component_detail': 0.20, # 20%
|
|
55
|
+
'diagrams': 0.15, # 15%
|
|
56
|
+
'technology': 0.10, # 10%
|
|
57
|
+
'nfr_design': 0.05, # 5%
|
|
58
|
+
'interfaces': 0.05 # 5%
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def __init__(self):
|
|
62
|
+
self.evaluator = DocumentEvaluator()
|
|
63
|
+
self.requirements_weights = self.DEFAULT_REQUIREMENTS_WEIGHTS.copy()
|
|
64
|
+
self.design_weights = self.DEFAULT_DESIGN_WEIGHTS.copy()
|
|
65
|
+
|
|
66
|
+
def configure_weights(self, weights: Dict[str, float]):
|
|
67
|
+
"""
|
|
68
|
+
配置评分标准权重
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
weights: 权重字典,格式为 {'criterion_name': weight}
|
|
72
|
+
"""
|
|
73
|
+
# 验证权重总和为 1.0
|
|
74
|
+
total_weight = sum(weights.values())
|
|
75
|
+
if abs(total_weight - 1.0) > 0.01:
|
|
76
|
+
raise ValueError(f"Weights must sum to 1.0, got {total_weight}")
|
|
77
|
+
|
|
78
|
+
# 更新权重
|
|
79
|
+
for criterion, weight in weights.items():
|
|
80
|
+
if criterion in self.requirements_weights:
|
|
81
|
+
self.requirements_weights[criterion] = weight
|
|
82
|
+
elif criterion in self.design_weights:
|
|
83
|
+
self.design_weights[criterion] = weight
|
|
84
|
+
|
|
85
|
+
def score_requirements(self, content: str, language: Optional[str] = None) -> ScoringResult:
|
|
86
|
+
"""
|
|
87
|
+
计算 Requirements 文档质量分数
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
content: 文档内容
|
|
91
|
+
language: 语言 ('zh' 或 'en'),None 则自动检测
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
ScoringResult: 评分结果
|
|
95
|
+
"""
|
|
96
|
+
# 使用 DocumentEvaluator 进行评估
|
|
97
|
+
assessment = self.evaluator.assess_requirements_quality(content, language)
|
|
98
|
+
|
|
99
|
+
# 计算加权分数
|
|
100
|
+
criterion_scores = {}
|
|
101
|
+
total_score = 0.0
|
|
102
|
+
|
|
103
|
+
for criterion, weight in self.requirements_weights.items():
|
|
104
|
+
raw_score = assessment.criteria_scores.get(criterion, 0.0)
|
|
105
|
+
weighted_score = raw_score * weight * 10.0 # 转换为 0-10 分制
|
|
106
|
+
|
|
107
|
+
criterion_scores[criterion] = CriterionScore(
|
|
108
|
+
name=criterion,
|
|
109
|
+
score=raw_score * 10.0, # 原始分数(0-10)
|
|
110
|
+
weight=weight,
|
|
111
|
+
weighted_score=weighted_score,
|
|
112
|
+
description=self._get_criterion_description(criterion, assessment.language)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
total_score += weighted_score
|
|
116
|
+
|
|
117
|
+
# 生成评分分解说明
|
|
118
|
+
breakdown = self._generate_requirements_breakdown(
|
|
119
|
+
criterion_scores,
|
|
120
|
+
total_score,
|
|
121
|
+
assessment
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return ScoringResult(
|
|
125
|
+
total_score=min(total_score, 10.0),
|
|
126
|
+
criterion_scores=criterion_scores,
|
|
127
|
+
language=assessment.language,
|
|
128
|
+
scoring_breakdown=breakdown,
|
|
129
|
+
assessment=assessment
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def score_design(self, design_content: str, requirements_content: str, language: Optional[str] = None) -> ScoringResult:
|
|
133
|
+
"""
|
|
134
|
+
计算 Design 文档质量分数
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
design_content: 设计文档内容
|
|
138
|
+
requirements_content: 需求文档内容
|
|
139
|
+
language: 语言 ('zh' 或 'en'),None 则自动检测
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
ScoringResult: 评分结果
|
|
143
|
+
"""
|
|
144
|
+
# 使用 DocumentEvaluator 进行评估
|
|
145
|
+
assessment = self.evaluator.assess_design_quality(
|
|
146
|
+
design_content,
|
|
147
|
+
requirements_content,
|
|
148
|
+
language
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# 计算加权分数
|
|
152
|
+
criterion_scores = {}
|
|
153
|
+
total_score = 0.0
|
|
154
|
+
|
|
155
|
+
for criterion, weight in self.design_weights.items():
|
|
156
|
+
raw_score = assessment.criteria_scores.get(criterion, 0.0)
|
|
157
|
+
weighted_score = raw_score * weight * 10.0 # 转换为 0-10 分制
|
|
158
|
+
|
|
159
|
+
criterion_scores[criterion] = CriterionScore(
|
|
160
|
+
name=criterion,
|
|
161
|
+
score=raw_score * 10.0, # 原始分数(0-10)
|
|
162
|
+
weight=weight,
|
|
163
|
+
weighted_score=weighted_score,
|
|
164
|
+
description=self._get_criterion_description(criterion, assessment.language)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
total_score += weighted_score
|
|
168
|
+
|
|
169
|
+
# 生成评分分解说明
|
|
170
|
+
breakdown = self._generate_design_breakdown(
|
|
171
|
+
criterion_scores,
|
|
172
|
+
total_score,
|
|
173
|
+
assessment
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return ScoringResult(
|
|
177
|
+
total_score=min(total_score, 10.0),
|
|
178
|
+
criterion_scores=criterion_scores,
|
|
179
|
+
language=assessment.language,
|
|
180
|
+
scoring_breakdown=breakdown,
|
|
181
|
+
assessment=assessment
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def _get_criterion_description(self, criterion: str, language: str) -> str:
|
|
185
|
+
"""获取评分标准描述"""
|
|
186
|
+
descriptions_zh = {
|
|
187
|
+
'structure': '文档结构完整性',
|
|
188
|
+
'ears_format': 'EARS 格式验收标准',
|
|
189
|
+
'user_stories': '用户故事质量',
|
|
190
|
+
'acceptance_criteria': '验收标准完整性',
|
|
191
|
+
'nfr_coverage': '非功能需求覆盖',
|
|
192
|
+
'constraints': '约束条件说明',
|
|
193
|
+
'traceability': '需求追溯性',
|
|
194
|
+
'component_detail': '组件详细度',
|
|
195
|
+
'diagrams': '架构图和设计图',
|
|
196
|
+
'technology': '技术选型说明',
|
|
197
|
+
'nfr_design': '非功能需求设计',
|
|
198
|
+
'interfaces': '接口定义完整性'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
descriptions_en = {
|
|
202
|
+
'structure': 'Document Structure Completeness',
|
|
203
|
+
'ears_format': 'EARS Format Acceptance Criteria',
|
|
204
|
+
'user_stories': 'User Story Quality',
|
|
205
|
+
'acceptance_criteria': 'Acceptance Criteria Completeness',
|
|
206
|
+
'nfr_coverage': 'Non-functional Requirements Coverage',
|
|
207
|
+
'constraints': 'Constraints Description',
|
|
208
|
+
'traceability': 'Requirements Traceability',
|
|
209
|
+
'component_detail': 'Component Detail Level',
|
|
210
|
+
'diagrams': 'Architecture and Design Diagrams',
|
|
211
|
+
'technology': 'Technology Stack Explanation',
|
|
212
|
+
'nfr_design': 'Non-functional Requirements Design',
|
|
213
|
+
'interfaces': 'Interface Definition Completeness'
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if language == 'zh':
|
|
217
|
+
return descriptions_zh.get(criterion, criterion)
|
|
218
|
+
else:
|
|
219
|
+
return descriptions_en.get(criterion, criterion)
|
|
220
|
+
|
|
221
|
+
def _generate_requirements_breakdown(self, criterion_scores: Dict[str, CriterionScore], total_score: float, assessment: QualityAssessment) -> str:
|
|
222
|
+
"""生成 Requirements 评分分解说明"""
|
|
223
|
+
lang = assessment.language
|
|
224
|
+
|
|
225
|
+
if lang == 'zh':
|
|
226
|
+
breakdown = f"## 评分分解\n\n"
|
|
227
|
+
breakdown += f"**总分**: {total_score:.2f}/10\n\n"
|
|
228
|
+
breakdown += f"### 各项评分\n\n"
|
|
229
|
+
|
|
230
|
+
for criterion, score_obj in criterion_scores.items():
|
|
231
|
+
breakdown += f"- **{score_obj.description}** ({score_obj.weight*100:.0f}%): "
|
|
232
|
+
breakdown += f"{score_obj.score:.2f}/10 → 加权分 {score_obj.weighted_score:.2f}\n"
|
|
233
|
+
|
|
234
|
+
if assessment.missing_sections:
|
|
235
|
+
breakdown += f"\n### 缺失章节\n\n"
|
|
236
|
+
for section in assessment.missing_sections:
|
|
237
|
+
breakdown += f"- {section}\n"
|
|
238
|
+
|
|
239
|
+
if assessment.issues:
|
|
240
|
+
breakdown += f"\n### 改进建议\n\n"
|
|
241
|
+
for issue in assessment.issues[:5]: # 最多显示 5 个
|
|
242
|
+
breakdown += f"- {issue}\n"
|
|
243
|
+
else:
|
|
244
|
+
breakdown = f"## Scoring Breakdown\n\n"
|
|
245
|
+
breakdown += f"**Total Score**: {total_score:.2f}/10\n\n"
|
|
246
|
+
breakdown += f"### Criterion Scores\n\n"
|
|
247
|
+
|
|
248
|
+
for criterion, score_obj in criterion_scores.items():
|
|
249
|
+
breakdown += f"- **{score_obj.description}** ({score_obj.weight*100:.0f}%): "
|
|
250
|
+
breakdown += f"{score_obj.score:.2f}/10 → Weighted {score_obj.weighted_score:.2f}\n"
|
|
251
|
+
|
|
252
|
+
if assessment.missing_sections:
|
|
253
|
+
breakdown += f"\n### Missing Sections\n\n"
|
|
254
|
+
for section in assessment.missing_sections:
|
|
255
|
+
breakdown += f"- {section}\n"
|
|
256
|
+
|
|
257
|
+
if assessment.issues:
|
|
258
|
+
breakdown += f"\n### Improvement Suggestions\n\n"
|
|
259
|
+
for issue in assessment.issues[:5]: # Show max 5
|
|
260
|
+
breakdown += f"- {issue}\n"
|
|
261
|
+
|
|
262
|
+
return breakdown
|
|
263
|
+
|
|
264
|
+
def _generate_design_breakdown(self, criterion_scores: Dict[str, CriterionScore], total_score: float, assessment: QualityAssessment) -> str:
|
|
265
|
+
"""生成 Design 评分分解说明"""
|
|
266
|
+
lang = assessment.language
|
|
267
|
+
|
|
268
|
+
if lang == 'zh':
|
|
269
|
+
breakdown = f"## 评分分解\n\n"
|
|
270
|
+
breakdown += f"**总分**: {total_score:.2f}/10\n\n"
|
|
271
|
+
breakdown += f"### 各项评分\n\n"
|
|
272
|
+
|
|
273
|
+
for criterion, score_obj in criterion_scores.items():
|
|
274
|
+
breakdown += f"- **{score_obj.description}** ({score_obj.weight*100:.0f}%): "
|
|
275
|
+
breakdown += f"{score_obj.score:.2f}/10 → 加权分 {score_obj.weighted_score:.2f}\n"
|
|
276
|
+
|
|
277
|
+
if assessment.missing_sections:
|
|
278
|
+
breakdown += f"\n### 缺失章节\n\n"
|
|
279
|
+
for section in assessment.missing_sections:
|
|
280
|
+
breakdown += f"- {section}\n"
|
|
281
|
+
|
|
282
|
+
if assessment.issues:
|
|
283
|
+
breakdown += f"\n### 改进建议\n\n"
|
|
284
|
+
for issue in assessment.issues[:5]:
|
|
285
|
+
breakdown += f"- {issue}\n"
|
|
286
|
+
else:
|
|
287
|
+
breakdown = f"## Scoring Breakdown\n\n"
|
|
288
|
+
breakdown += f"**Total Score**: {total_score:.2f}/10\n\n"
|
|
289
|
+
breakdown += f"### Criterion Scores\n\n"
|
|
290
|
+
|
|
291
|
+
for criterion, score_obj in criterion_scores.items():
|
|
292
|
+
breakdown += f"- **{score_obj.description}** ({score_obj.weight*100:.0f}%): "
|
|
293
|
+
breakdown += f"{score_obj.score:.2f}/10 → Weighted {score_obj.weighted_score:.2f}\n"
|
|
294
|
+
|
|
295
|
+
if assessment.missing_sections:
|
|
296
|
+
breakdown += f"\n### Missing Sections\n\n"
|
|
297
|
+
for section in assessment.missing_sections:
|
|
298
|
+
breakdown += f"- {section}\n"
|
|
299
|
+
|
|
300
|
+
if assessment.issues:
|
|
301
|
+
breakdown += f"\n### Improvement Suggestions\n\n"
|
|
302
|
+
for issue in assessment.issues[:5]:
|
|
303
|
+
breakdown += f"- {issue}\n"
|
|
304
|
+
|
|
305
|
+
return breakdown
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Report Generator - 报告生成器
|
|
4
|
+
|
|
5
|
+
生成增强过程的详细报告
|
|
6
|
+
注意:这是一个轻量级实现,基本报告功能已集成在 EnhancementResult 中
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import List, Dict, Optional
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class EnhancementReport:
|
|
16
|
+
"""增强报告数据结构"""
|
|
17
|
+
document_type: str
|
|
18
|
+
document_path: str
|
|
19
|
+
initial_score: float
|
|
20
|
+
final_score: float
|
|
21
|
+
improvement: float
|
|
22
|
+
iterations: int
|
|
23
|
+
stopping_reason: str
|
|
24
|
+
improvements_applied: List[Dict]
|
|
25
|
+
timestamp: str
|
|
26
|
+
gate_passed: Optional[bool] = None
|
|
27
|
+
gate_threshold: Optional[float] = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ReportGenerator:
|
|
31
|
+
"""
|
|
32
|
+
报告生成器
|
|
33
|
+
|
|
34
|
+
生成 Markdown 格式的增强报告和质量摘要
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def generate_enhancement_report(self, report_data: EnhancementReport) -> str:
|
|
38
|
+
"""
|
|
39
|
+
生成增强报告
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
report_data: 报告数据
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Markdown 格式的报告
|
|
46
|
+
"""
|
|
47
|
+
lines = []
|
|
48
|
+
|
|
49
|
+
# 标题
|
|
50
|
+
lines.append(f"# Enhancement Report: {report_data.document_type.upper()}")
|
|
51
|
+
lines.append(f"\n**Generated**: {report_data.timestamp}")
|
|
52
|
+
lines.append(f"**Document**: `{report_data.document_path}`\n")
|
|
53
|
+
lines.append("---\n")
|
|
54
|
+
|
|
55
|
+
# 摘要
|
|
56
|
+
lines.append("## Summary\n")
|
|
57
|
+
lines.append(f"- **Initial Score**: {report_data.initial_score:.2f}/10")
|
|
58
|
+
lines.append(f"- **Final Score**: {report_data.final_score:.2f}/10")
|
|
59
|
+
lines.append(f"- **Improvement**: +{report_data.improvement:.2f}")
|
|
60
|
+
lines.append(f"- **Iterations**: {report_data.iterations}")
|
|
61
|
+
lines.append(f"- **Stopping Reason**: {report_data.stopping_reason}\n")
|
|
62
|
+
|
|
63
|
+
# 质量门结果
|
|
64
|
+
if report_data.gate_passed is not None:
|
|
65
|
+
lines.append("## Quality Gate\n")
|
|
66
|
+
status = "✓ PASSED" if report_data.gate_passed else "✗ FAILED"
|
|
67
|
+
lines.append(f"- **Status**: {status}")
|
|
68
|
+
lines.append(f"- **Threshold**: {report_data.gate_threshold}/10")
|
|
69
|
+
lines.append(f"- **Final Score**: {report_data.final_score:.2f}/10\n")
|
|
70
|
+
|
|
71
|
+
# 改进详情
|
|
72
|
+
if report_data.improvements_applied:
|
|
73
|
+
lines.append("## Improvements Applied\n")
|
|
74
|
+
for i, imp in enumerate(report_data.improvements_applied, 1):
|
|
75
|
+
lines.append(f"{i}. **{imp.get('type', 'Unknown')}**")
|
|
76
|
+
lines.append(f" - Section: {imp.get('section', 'N/A')}")
|
|
77
|
+
lines.append(f" - Priority: {imp.get('priority', 'N/A')}")
|
|
78
|
+
if imp.get('description'):
|
|
79
|
+
lines.append(f" - Description: {imp['description']}")
|
|
80
|
+
lines.append("")
|
|
81
|
+
|
|
82
|
+
lines.append("---\n")
|
|
83
|
+
lines.append(f"*Report generated by Ultrawork Enhancement System*\n")
|
|
84
|
+
|
|
85
|
+
return '\n'.join(lines)
|
|
86
|
+
|
|
87
|
+
def generate_quality_summary(self, reports: List[EnhancementReport]) -> str:
|
|
88
|
+
"""
|
|
89
|
+
生成质量摘要(多个文档)
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
reports: 报告列表
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Markdown 格式的摘要
|
|
96
|
+
"""
|
|
97
|
+
lines = []
|
|
98
|
+
|
|
99
|
+
# 标题
|
|
100
|
+
lines.append("# Quality Summary\n")
|
|
101
|
+
lines.append(f"**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
102
|
+
lines.append(f"**Documents**: {len(reports)}\n")
|
|
103
|
+
lines.append("---\n")
|
|
104
|
+
|
|
105
|
+
# 总体统计
|
|
106
|
+
total_improvement = sum(r.improvement for r in reports)
|
|
107
|
+
avg_iterations = sum(r.iterations for r in reports) / len(reports) if reports else 0
|
|
108
|
+
passed_gates = sum(1 for r in reports if r.gate_passed)
|
|
109
|
+
|
|
110
|
+
lines.append("## Overall Statistics\n")
|
|
111
|
+
lines.append(f"- **Total Improvement**: +{total_improvement:.2f}")
|
|
112
|
+
lines.append(f"- **Average Iterations**: {avg_iterations:.1f}")
|
|
113
|
+
if any(r.gate_passed is not None for r in reports):
|
|
114
|
+
lines.append(f"- **Quality Gates Passed**: {passed_gates}/{len(reports)}\n")
|
|
115
|
+
else:
|
|
116
|
+
lines.append("")
|
|
117
|
+
|
|
118
|
+
# 各文档详情
|
|
119
|
+
lines.append("## Document Details\n")
|
|
120
|
+
for report in reports:
|
|
121
|
+
lines.append(f"### {report.document_type.upper()}\n")
|
|
122
|
+
lines.append(f"- **Path**: `{report.document_path}`")
|
|
123
|
+
lines.append(f"- **Score**: {report.initial_score:.2f} → {report.final_score:.2f} (+{report.improvement:.2f})")
|
|
124
|
+
lines.append(f"- **Iterations**: {report.iterations}")
|
|
125
|
+
|
|
126
|
+
if report.gate_passed is not None:
|
|
127
|
+
status = "✓ PASSED" if report.gate_passed else "✗ FAILED"
|
|
128
|
+
lines.append(f"- **Quality Gate**: {status}")
|
|
129
|
+
|
|
130
|
+
lines.append("")
|
|
131
|
+
|
|
132
|
+
lines.append("---\n")
|
|
133
|
+
lines.append(f"*Summary generated by Ultrawork Enhancement System*\n")
|
|
134
|
+
|
|
135
|
+
return '\n'.join(lines)
|
|
136
|
+
|
|
137
|
+
def save_report(self, report: str, output_path: str):
|
|
138
|
+
"""
|
|
139
|
+
保存报告到文件
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
report: 报告内容
|
|
143
|
+
output_path: 输出路径
|
|
144
|
+
"""
|
|
145
|
+
from pathlib import Path
|
|
146
|
+
|
|
147
|
+
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
|
148
|
+
|
|
149
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
150
|
+
f.write(report)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# 注意:基本报告功能已集成在 ultrawork_enhancer_v3.py 的 EnhancementResult 中
|
|
154
|
+
# 本类提供更详细的 Markdown 格式报告生成,可选使用
|
|
File without changes
|