174 lines
5.7 KiB
YAML
174 lines
5.7 KiB
YAML
name: Requirement Traceability Check
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
- main
|
|
- develop
|
|
pull_request:
|
|
branches:
|
|
- master
|
|
- main
|
|
- develop
|
|
|
|
jobs:
|
|
traceability:
|
|
name: Validate Requirement Traces
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v1
|
|
with:
|
|
bun-version: latest
|
|
|
|
- name: Install dependencies
|
|
run: bun install
|
|
|
|
- name: Extract requirement traces
|
|
run: bun run traces:json > traces.json
|
|
|
|
- name: Validate trace format
|
|
run: |
|
|
if ! jq empty traces.json 2>/dev/null; then
|
|
echo "❌ Invalid traces.json format"
|
|
exit 1
|
|
fi
|
|
echo "✅ Traces JSON is valid"
|
|
|
|
- name: Check requirement coverage
|
|
run: |
|
|
set -e
|
|
|
|
# Extract coverage stats
|
|
TOTAL_TRACES=$(jq '.totalTraces' traces.json)
|
|
UR_COUNT=$(jq '.byType.UR | length' traces.json)
|
|
IR_COUNT=$(jq '.byType.IR | length' traces.json)
|
|
DR_COUNT=$(jq '.byType.DR | length' traces.json)
|
|
JA_COUNT=$(jq '.byType.JA | length' traces.json)
|
|
|
|
echo "## 📊 Requirement Traceability Report"
|
|
echo ""
|
|
echo "**Total TRACES Found:** $TOTAL_TRACES"
|
|
echo ""
|
|
echo "### Requirements Covered:"
|
|
echo "- User Requirements (UR): $UR_COUNT / 39 ($(( UR_COUNT * 100 / 39 ))%)"
|
|
echo "- Integration Requirements (IR): $IR_COUNT / 24 ($(( IR_COUNT * 100 / 24 ))%)"
|
|
echo "- Development Requirements (DR): $DR_COUNT / 48 ($(( DR_COUNT * 100 / 48 ))%)"
|
|
echo "- Jellyfin API Requirements (JA): $JA_COUNT / 3 ($(( JA_COUNT * 100 / 3 ))%)"
|
|
echo ""
|
|
|
|
# Set minimum coverage threshold (50%)
|
|
TOTAL_REQS=114
|
|
MIN_COVERAGE=$((TOTAL_REQS / 2))
|
|
COVERED=$((UR_COUNT + IR_COUNT + DR_COUNT + JA_COUNT))
|
|
COVERAGE_PERCENT=$((COVERED * 100 / TOTAL_REQS))
|
|
|
|
echo "**Overall Coverage:** $COVERED / $TOTAL_REQS ($COVERAGE_PERCENT%)"
|
|
echo ""
|
|
|
|
if [ "$COVERED" -lt "$MIN_COVERAGE" ]; then
|
|
echo "❌ Coverage below minimum threshold ($COVERAGE_PERCENT% < 50%)"
|
|
exit 1
|
|
else
|
|
echo "✅ Coverage meets minimum threshold ($COVERAGE_PERCENT% >= 50%)"
|
|
fi
|
|
|
|
- name: Check for new untraced code
|
|
run: |
|
|
set -e
|
|
|
|
# Find files modified in this PR/push
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '\.(ts|tsx|svelte|rs)$' || true)
|
|
else
|
|
CHANGED_FILES=$(git diff --name-only HEAD~1 | grep -E '\.(ts|tsx|svelte|rs)$' || true)
|
|
fi
|
|
|
|
if [ -z "$CHANGED_FILES" ]; then
|
|
echo "✅ No source files changed"
|
|
exit 0
|
|
fi
|
|
|
|
echo "### Files Changed:"
|
|
echo "$CHANGED_FILES" | sed 's/^/- /'
|
|
echo ""
|
|
|
|
# Check if changed files have TRACES
|
|
UNTRACED_FILES=""
|
|
while IFS= read -r file; do
|
|
if [ -f "$file" ]; then
|
|
# Skip test files and generated code
|
|
if [[ "$file" == *".test."* ]] || [[ "$file" == *"node_modules"* ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Check if file has TRACES comments
|
|
if ! grep -q "TRACES:" "$file" 2>/dev/null; then
|
|
UNTRACED_FILES+="$file"$'\n'
|
|
fi
|
|
fi
|
|
done <<< "$CHANGED_FILES"
|
|
|
|
if [ -n "$UNTRACED_FILES" ]; then
|
|
echo "⚠️ New files without TRACES:"
|
|
echo "$UNTRACED_FILES" | sed 's/^/ - /'
|
|
echo ""
|
|
echo "💡 Add TRACES comments to link code to requirements:"
|
|
echo " // TRACES: UR-001, UR-002 | DR-003"
|
|
else
|
|
echo "✅ All changed files have TRACES comments"
|
|
fi
|
|
|
|
- name: Generate traceability report
|
|
if: always()
|
|
run: bun run traces:markdown
|
|
|
|
- name: Upload traceability report
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: traceability-report
|
|
path: docs/TRACEABILITY.md
|
|
retention-days: 30
|
|
|
|
- name: Comment PR with coverage report
|
|
if: github.event_name == 'pull_request'
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const fs = require('fs');
|
|
const traces = JSON.parse(fs.readFileSync('traces.json', 'utf8'));
|
|
|
|
const urCount = traces.byType.UR.length;
|
|
const irCount = traces.byType.IR.length;
|
|
const drCount = traces.byType.DR.length;
|
|
const jaCount = traces.byType.JA.length;
|
|
const total = urCount + irCount + drCount + jaCount;
|
|
const coverage = Math.round((total / 114) * 100);
|
|
|
|
const comment = `## 📊 Requirement Traceability Report
|
|
|
|
**Coverage:** ${coverage}% (${total}/114 requirements traced)
|
|
|
|
### By Type:
|
|
- **User Requirements (UR):** ${urCount}/39 (${Math.round(urCount/39*100)}%)
|
|
- **Integration Requirements (IR):** ${irCount}/24 (${Math.round(irCount/24*100)}%)
|
|
- **Development Requirements (DR):** ${drCount}/48 (${Math.round(drCount/48*100)}%)
|
|
- **Jellyfin API (JA):** ${jaCount}/3 (${Math.round(jaCount/3*100)}%)
|
|
|
|
**Total Traces:** ${traces.totalTraces}
|
|
|
|
[View full report](artifacts) | [Format Guide](https://github.com/yourusername/jellytau/blob/master/scripts/README.md#extract-tracests)`;
|
|
|
|
github.rest.issues.createComment({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
body: comment
|
|
});
|