生成Allure报告
danielvm-git2026-10-28查看 1558复制 1458
从 bigpowers YAML 元数据生成 Allure 就绪的报告。读取 execution-status.yaml、release-plan.yaml、epic capsules、task YAMLs、cycle-times.yaml 和 bug registry,生成 allure-results/junit-results.xml、categories.json 和 executor.json。当准备进度仪表盘、与 Allure TestOps 集成或生成 CI 报告时使用。
📘如何使用这个技能
- 1复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
- 2打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
- 3粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
- 4直接提出你的需求·AI 会按技能定义的工作流、格式和约束来处理,无需再解释背景
💡 提示:技能生效后,每次对话只需一句需求,AI 会按 SKILL.md 的流程执行。
SKILL.md9419 chars
---
name: generate-allure-report
model: sonnet
description: "Generate Allure-ready reports from bigpowers YAML metadata. Reads execution-status.yaml, release-plan.yaml, epic capsules, task YAMLs, cycle-times.yaml, and bug registry to produce allure-results/junit-results.xml, categories.json, and executor.json. Use when preparing progress dashboards, integrating with Allure TestOps, or generating CI reports."
---
# Generate Allure Report
Generate Allure TestOps-compatible reports from bigpowers project metadata. Produces JUnit XML for story-level test results, custom categories for filtering, and executor metadata — all in the `allure-results/` directory.
## Quick Start
```bash
bash scripts/generate-allure-report.sh
```
## What It Produces
Three files in `allure-results/`:
| File | Description |
|------|-------------|
| `junit-results.xml` | One `<testcase>` per story with `<properties>` for risk, security, WSJF, tier, wave, and status. Incomplete stories get a `<failure>` element. |
| `categories.json` | Custom Allure categories for filtering by epic, risk level (P0), and security reviews. |
| `executor.json` | Build metadata — name, type, version from release-plan.yaml, build order. |
## Data Sources
See [REFERENCE.md](../../../skills/generate-allure-report/REFERENCE.md)
## Verify
```bash
test -f allure-results/junit-results.xml && test -f allure-results/categories.json && test -f allure-results/executor.json
```
## Handoff
- next_skill: null (terminal skill — no downstream workflow step)
---
# generate-allure-report — Reference
## Data Sources
The script reads five YAML sources from the project:
| Source | Path | Fields Used |
|--------|------|-------------|
| Execution status | `specs/execution-status.yaml` | `epics`, `stories`, `development_status` |
| Release plan | `specs/release-plan.yaml` | `release.version`, `release.status`, `bugs` summary |
| Epic capsules | `specs/epics/**/epic.yaml` + `-tasks.yaml` | Epic metadata, task pass/fail counts |
| Cycle times | `specs/metrics/cycle-times.yaml` | Story-level `cycle_minutes`, `bcp_per_hour`, `source` |
| Bug registry | `specs/bugs/registry.yaml` | Bug counts by status and severity |
## Script Body
`scripts/generate-allure-report.sh`:
```bash
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || ROOT="$(dirname "${BASH_SOURCE[0]}")/.."
mkdir -p "$ROOT/allure-results"
$PYTHON - "$ROOT" <<'PY'
import json
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
root = Path(sys.argv[1])
out = root / "allure-results"
# 1. Read execution-status.yaml
exec_status_file = root / "specs" / "execution-status.yaml"
release_plan_file = root / "specs" / "release-plan.yaml"
cycle_times_file = root / "specs" / "metrics" / "cycle-times.yaml"
bugs_registry_file = root / "specs" / "bugs" / "registry.yaml"
sys.path.insert(0, str(root / "scripts" / "lib"))
from simple_yaml import parse_simple_yaml
exec_status = parse_simple_yaml(exec_status_file.read_text()) if exec_status_file.exists() else {}
release_plan = parse_simple_yaml(release_plan_file.read_text()) if release_plan_file.exists() else {}
cycle_times = parse_simple_yaml(cycle_times_file.read_text()) if cycle_times_file.exists() else {"stories": []}
bugs_registry = parse_simple_yaml(bugs_registry_file.read_text()) if bugs_registry_file.exists() else {"bugs": []}
# Build cycle-times lookup
ct_lookup = {}
for ct in cycle_times.get("stories", []):
if isinstance(ct, dict):
ct_lookup[ct.get("id", "")] = ct
# 2. Build JUnit XML
stories = exec_status.get("stories", {})
# Counts for testsuite attributes
total_stories = len(stories)
incomplete = sum(1 for s in stories.values() if isinstance(s, dict) and s.get("status") != "done")
testsuite = ET.Element("testsuite", {
"name": "bigpowers-epic-progress",
"tests": str(total_stories),
"failures": str(incomplete),
"errors": "0",
"skipped": "0",
})
for story_id in sorted(stories.keys()):
story = stories[story_id]
if not isinstance(story, dict):
continue
epic_id = story.get("epic", "unknown")
title = story.get("title", story_id)
bcps = story.get("bcps", 0)
status = story.get("status", "backlog")
risk_max = story.get("risk_max", "none")
security_max = story.get("security_max", "none")
# Enrich with cycle-times data
ct_data = ct_lookup.get(story_id, {})
cycle_minutes = ct_data.get("cycle_minutes", 0)
bcp_per_hour = ct_data.get("bcp_per_hour", 0)
# Time: cycle_minutes * 60 for seconds in Allure display
time_seconds = cycle_minutes * 60.0 if cycle_minutes else 0.0
testcase = ET.SubElement(testsuite, "testcase", {
"classname": epic_id,
"name": f"{story_id}: {title}",
"time": str(round(time_seconds, 3)),
})
props = ET.SubElement(testcase, "properties")
ET.SubElement(props, "property", {"name": "risk", "value": risk_max})
ET.SubElement(props, "property", {"name": "security", "value": security_max})
ET.SubElement(props, "property", {"name": "bcps", "value": str(bcps)})
ET.SubElement(props, "property", {"name": "status", "value": status})
ET.SubElement(props, "property", {"name": "bcp_per_hour", "value": str(bcp_per_hour)})
ET.SubElement(props, "property", {"name": "lead_time_minutes", "value": str(cycle_minutes)})
if status != "done":
ET.SubElement(testcase, "failure", {
"message": f"Story {story_id} is {status} [risk={risk_max}, security={security_max}]",
"type": "StoryIncomplete"
})
tree = ET.ElementTree(testsuite)
ET.indent(tree, space=" ")
tree.write(str(out / "junit-results.xml"), encoding="utf-8", xml_declaration=True)
# 3. Build categories.json
epics = exec_status.get("epics", {})
categories = []
for epic_id in sorted(epics.keys()):
epic = epics[epic_id]
if isinstance(epic, dict) and epic.get("status") != "done":
categories.append({
"name": f"Epic: {epic.get('title', epic_id)}",
"matchedStatuses": ["failed"],
"messageRegex": f".*{epic_id}:.*"
})
categories.append({
"name": "P0 Risk",
"matchedStatuses": ["failed"],
"messageRegex": ".*risk.*P0.*"
})
categories.append({
"name": "Security Review",
"matchedStatuses": ["failed"],
"messageRegex": ".*security.*(?:medium|high).*"
})
# Add bug-based categories
bug_list = bugs_registry.get("bugs", [])
bug_count = len(bug_list) if isinstance(bug_list, list) else 0
open_bugs = sum(1 for b in bug_list if isinstance(b, dict) and b.get("status") not in ("fixed", "closed", None))
if open_bugs > 0:
categories.append({
"name": "Open Bugs",
"matchedStatuses": ["failed"],
"messageRegex": ".*Bug.*"
})
(out / "categories.json").write_text(json.dumps(categories, indent=2))
# 4. Build executor.json
rl = release_plan.get("release", {}) if isinstance(release_plan.get("release"), dict) else {}
executor = {
"name": "bigpowers",
"type": "bigpowers",
"buildName": rl.get("version", "unknown") if isinstance(rl, dict) else "unknown",
"buildOrder": len(exec_status.get("development_status", {})),
}
(out / "executor.json").write_text(json.dumps(executor, indent=2))
# Summary
epic_count = len([e for e in epics.values() if isinstance(e, dict) and e.get("status") == "done"])
total_epics = len(epics)
print(f"generate-allure-report: {total_stories} stories, {epic_count}/{total_epics} epics done, {bug_count} bugs")
print(f" -> {out}/junit-results.xml")
print(f" -> {out}/categories.json")
print(f" -> {out}/executor.json")
PY
```
## JUnit XML Schema
```xml
<?xml version="1.0" encoding="utf-8"?>
<testsuite name="bigpowers-epic-progress" tests="N" failures="F" errors="0" skipped="0">
<testcase classname="e01" name="e01s01: Security slopcheck tags" time="0.75">
<properties>
<property name="risk" value="none"/>
<property name="security" value="none"/>
<property name="bcps" value="1"/>
<property name="status" value="done"/>
<property name="bcp_per_hour" value="1.3"/>
<property name="lead_time_minutes" value="45"/>
</properties>
</testcase>
<testcase classname="e01" name="e01s99: Some incomplete story" time="0.0">
<properties>...</properties>
<failure message="Story e01s99 is backlog [risk=P0, security=high]" type="StoryIncomplete"/>
</testcase>
</testsuite>
```
## Categories JSON Schema
```json
[
{
"name": "Epic: Quality Core - Skill Hardening",
"matchedStatuses": ["failed"],
"messageRegex": ".*e45:.*"
},
{
"name": "P0 Risk",
"matchedStatuses": ["failed"],
"messageRegex": ".*risk.*P0.*"
},
{
"name": "Security Review",
"matchedStatuses": ["failed"],
"messageRegex": ".*security.*(?:medium|high).*"
}
]
```
## Executor JSON Schema
```json
{
"name": "bigpowers",
"type": "bigpowers",
"buildName": "2.76.2",
"buildOrder": 400
}
```
## Example Usage
```bash
# Generate reports
bash scripts/generate-allure-report.sh
# Verify output
test -f allure-results/junit-results.xml && echo "JUnit OK"
test -f allure-results/categories.json && echo "Categories OK"
test -f allure-results/executor.json && echo "Executor OK"
# Serve with Allure
allure serve allure-results/
# Or open the Allure TestOps UI
allure open allure-results/
```