mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
42e29303b4
* Add coverage-analysis skill to dotnet-test plugin Adds an automated, project-wide code coverage and CRAP score analysis skill for .NET projects. Complements the existing crap-score skill (which is manual/targeted for single methods) with an end-to-end pipeline: - Auto-detects solution structure and test projects - Detects coverage provider (Microsoft.Testing.Extensions.CodeCoverage or Coverlet) per test project and selects the correct dotnet test arguments - Runs coverage collection, generates reports via ReportGenerator - Calculates CRAP scores per method via bundled PowerShell scripts - Surfaces risk hotspots ranked by danger (complex + uncovered code) - Produces actionable recommendations with projected score improvements Boundary with crap-score: coverage-analysis is project-wide and automated; crap-score is targeted and LLM-directed. The frontmatter descriptions cross-reference each other so the skill router disambiguates correctly. Includes eval.yaml with 3 scenarios (existing data analysis, from-scratch coverage collection, coverage plateau diagnosis) and shared buildable fixtures. Fixes #457 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update plugins/dotnet-test/skills/coverage-analysis/SKILL.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address PR review: coverage output path and scoped tool install - Add --coverage-output $rawDir to all MS CodeCoverage dotnet test commands (unified, .NET 9, .NET 10+, and mixed-project modes) so Cobertura XML lands in the expected directory for downstream search - Replace dotnet tool install --global with --tool-path scoped to the coverage output directory to avoid mutating the user environment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Cobertura XML fixture line numbers to match StudentService.cs FindById starts at line 101 (was 88), Search starts at line 109 (was 96). Updated both partial-coverage and plateau fixtures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Wrap Get-ChildItem pipelines in @() for single-result array safety PowerShell returns a scalar FileInfo (not array) when a pipeline yields exactly one result, making .Count return null. Wrapping in @() ensures the result is always an array so .Count works reliably for the common single-test-project case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove CODEOWNERS change - not needed for this PR The existing /plugins/dotnet-test/ and /tests/dotnet-test/ wildcards already route reviews to @dotnet/dotnet-testing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: .csproj fallback, cross-platform paths, .sln header - Add .csproj fallback discovery in Step 1 when no .sln found (emit ENTRY_TYPE:Project) to match documented behavior - Fix ProjectReference backslash to forward slash in test fixture .csproj - Remove leading blank line from ContosoUniversity.sln fixture Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add plateau/stuck trigger language to frontmatter description The skill was NOT ACTIVATED for the 'Coverage plateau diagnosis' eval scenario because the frontmatter description (used by the skill router) lacked plateau-related keywords. The prompt 'coverage is stuck' and 'what's blocking me' didn't match the existing description text. Add 'stuck', 'plateaued', and 'blocking improvement' to the frontmatter Use-when sentence so the skill router activates for plateau diagnosis prompts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make CRAP scores mandatory, add existing-data shortcut path The plateau eval scenario scored 1/5 on CRAP scores across all modes because the LLM shortcut the workflow when existing Cobertura XML was provided — it read the XML directly and gave a quick diagnostic answer without computing CRAP scores or following the output template. - Add existing-data guidance at top of Workflow section: skip test execution but still run CRAP score computation (Steps 5-6) - Add mandatory CRAP rule to guidelines.md: Risk Hotspots table is required in every output regardless of analysis mode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update plugins/dotnet-test/skills/coverage-analysis/SKILL.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
137 lines
4.3 KiB
C#
137 lines
4.3 KiB
C#
namespace ContosoUniversity.Services;
|
|
|
|
public class StudentService
|
|
{
|
|
private readonly List<Student> _students = new();
|
|
|
|
/// <summary>
|
|
/// Enroll a student. Validates name, age, and duplicate checks.
|
|
/// Complexity: moderate (multiple branches).
|
|
/// </summary>
|
|
public EnrollmentResult Enroll(string firstName, string lastName, int age)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(firstName) || string.IsNullOrWhiteSpace(lastName))
|
|
return new EnrollmentResult(false, "Name is required.");
|
|
|
|
if (age < 16 || age > 100)
|
|
return new EnrollmentResult(false, "Age must be between 16 and 100.");
|
|
|
|
var existing = _students.FirstOrDefault(s =>
|
|
s.FirstName.Equals(firstName, StringComparison.OrdinalIgnoreCase) &&
|
|
s.LastName.Equals(lastName, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (existing != null)
|
|
return new EnrollmentResult(false, $"Student {firstName} {lastName} is already enrolled.");
|
|
|
|
var student = new Student
|
|
{
|
|
Id = _students.Count + 1,
|
|
FirstName = firstName,
|
|
LastName = lastName,
|
|
Age = age,
|
|
EnrolledDate = DateTime.UtcNow
|
|
};
|
|
|
|
_students.Add(student);
|
|
return new EnrollmentResult(true, "Enrolled successfully.", student.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate GPA from a list of grades. Handles edge cases.
|
|
/// Complexity: high (loops, conditionals, null checks).
|
|
/// </summary>
|
|
public GpaResult CalculateGpa(int studentId, List<Grade>? grades)
|
|
{
|
|
var student = _students.FirstOrDefault(s => s.Id == studentId);
|
|
if (student == null)
|
|
return new GpaResult(false, 0, "Student not found.");
|
|
|
|
if (grades == null || grades.Count == 0)
|
|
return new GpaResult(true, 0, "No grades to calculate.");
|
|
|
|
double totalPoints = 0;
|
|
int totalCredits = 0;
|
|
var warnings = new List<string>();
|
|
|
|
foreach (var grade in grades)
|
|
{
|
|
if (grade.Credits <= 0)
|
|
{
|
|
warnings.Add($"Skipped invalid credit value for {grade.CourseName}.");
|
|
continue;
|
|
}
|
|
|
|
double points = grade.LetterGrade?.ToUpperInvariant() switch
|
|
{
|
|
"A" => 4.0,
|
|
"B" => 3.0,
|
|
"C" => 2.0,
|
|
"D" => 1.0,
|
|
"F" => 0.0,
|
|
_ => -1.0
|
|
};
|
|
|
|
if (points < 0)
|
|
{
|
|
warnings.Add($"Unknown grade '{grade.LetterGrade}' for {grade.CourseName}.");
|
|
continue;
|
|
}
|
|
|
|
totalPoints += points * grade.Credits;
|
|
totalCredits += grade.Credits;
|
|
}
|
|
|
|
if (totalCredits == 0)
|
|
return new GpaResult(true, 0, "No valid grades after filtering.");
|
|
|
|
double gpa = Math.Round(totalPoints / totalCredits, 2);
|
|
|
|
string message = warnings.Count > 0
|
|
? $"GPA calculated with {warnings.Count} warning(s): {string.Join("; ", warnings)}"
|
|
: "GPA calculated successfully.";
|
|
|
|
return new GpaResult(true, gpa, message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simple lookup by ID. Complexity: low (1 branch).
|
|
/// </summary>
|
|
public Student? FindById(int id)
|
|
{
|
|
return _students.FirstOrDefault(s => s.Id == id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Search students by name. Complexity: low.
|
|
/// </summary>
|
|
public List<Student> Search(string query)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(query))
|
|
return new List<Student>();
|
|
|
|
return _students
|
|
.Where(s => s.FirstName.Contains(query, StringComparison.OrdinalIgnoreCase) ||
|
|
s.LastName.Contains(query, StringComparison.OrdinalIgnoreCase))
|
|
.ToList();
|
|
}
|
|
}
|
|
|
|
public record EnrollmentResult(bool Success, string Message, int? StudentId = null);
|
|
public record GpaResult(bool Success, double Gpa, string Message);
|
|
|
|
public class Student
|
|
{
|
|
public int Id { get; set; }
|
|
public string FirstName { get; set; } = string.Empty;
|
|
public string LastName { get; set; } = string.Empty;
|
|
public int Age { get; set; }
|
|
public DateTime EnrolledDate { get; set; }
|
|
}
|
|
|
|
public class Grade
|
|
{
|
|
public string CourseName { get; set; } = string.Empty;
|
|
public string? LetterGrade { get; set; }
|
|
public int Credits { get; set; }
|
|
}
|