diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5cb5d96045cd..54b0cbfa788d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -21,10 +21,10 @@ Testing: - Examples: - `dotnet test test/dotnet.Tests/dotnet.Tests.csproj --filter "Name~ItShowsTheAppropriateMessageToTheUser"` - `dotnet exec artifacts/bin/redist/Debug/dotnet.Tests.dll -method "*ItShowsTheAppropriateMessageToTheUser*"` -- For incremental test runs of `dotnet.Tests` (avoids slow full `build.cmd`), see the skill at `.github/copilot/skills/incremental-test.md`. In short: build only the modified projects, copy their output DLLs into the redist SDK layout, then run the tests. +- For incremental test runs of `dotnet.Tests` (avoids slow full `build.cmd`), use the `incremental-test` skill. - To test CLI command changes: - Build the redist SDK: `./build.sh` from repo root - - Create a dogfood environment: `source eng/dogfood.sh` + - Create a dogfood environment: `source eng/dogfood.sh` - Test commands in the dogfood shell (e.g., `dnx --help`, `dotnet tool install --help`) - The dogfood script sets up PATH and environment to use the newly built SDK diff --git a/.github/skills/AGENTS.md b/.github/skills/AGENTS.md new file mode 100644 index 000000000000..0b022c9eba26 --- /dev/null +++ b/.github/skills/AGENTS.md @@ -0,0 +1,24 @@ +# Agent Skills + +When creating skills, follow: +- Agent skills specification: https://agentskills.io/specification.md +- Best practices: https://agentskills.io/skill-creation/best-practices.md + +## Structure + +``` +.github/skills/skill-name/ +├── SKILL.md # Required: metadata + instructions +├── scripts/ # Optional: executable code +├── references/ # Optional: documentation +├── assets/ # Optional: templates, resources +└── ... # Any additional files or directories +``` + +## Quick Checklist + +- [ ] Run `dotnet .github/skills/ValidateSkill.cs ` to validate format. +- [ ] `description` describes what the skill does and when to use it. Skill body does not include "When to use this skill". +- [ ] Skill does not explain things the agent already knows. Focus on what's specific to the task at hand. +- [ ] Deterministic processes use scripts (for example, to fetch and format data from an API). +- [ ] Scripts use PowerShell or .NET file-based apps, not bash. diff --git a/.github/skills/ValidateSkill.cs b/.github/skills/ValidateSkill.cs new file mode 100755 index 000000000000..12d1e0b51342 --- /dev/null +++ b/.github/skills/ValidateSkill.cs @@ -0,0 +1,103 @@ +#!/usr/bin/env dotnet +#:property ManagePackageVersionsCentrally=false +#:property PublishAot=false +#:package YamlDotNet@16.3.0 + +using YamlDotNet.Serialization; +using System.Text.RegularExpressions; + +if (args.Length == 0) +{ + Console.Error.WriteLine("Usage: dotnet ValidateSkill.cs "); + return 1; +} + +string skillDir = Path.GetFullPath(args[0]); +string skillName = Path.GetFileName(Path.TrimEndingDirectorySeparator(skillDir)); +string skillFile = Path.Combine(skillDir, "SKILL.md"); + +// SKILL.md must exist in the skill directory +if (!File.Exists(skillFile)) +{ + Console.Error.WriteLine($"SKILL.md not found in {skillDir}"); + return 1; +} + +string text = File.ReadAllText(skillFile); + +// SKILL.md must begin with YAML frontmatter delimited by --- +if (!text.StartsWith("---")) +{ + Console.Error.WriteLine("No YAML frontmatter found."); + return 1; +} + +Match frontmatterMatch = Regex.Match( + text, + @"\A---\r?\n(?.*?)(?:\r?\n)---(?:\r?\n|$)", + RegexOptions.Singleline); +if (!frontmatterMatch.Success) +{ + Console.Error.WriteLine("Unterminated YAML frontmatter."); + return 1; +} + +string yaml = frontmatterMatch.Groups["yaml"].Value.Trim(); + +IDeserializer deserializer = new DeserializerBuilder().Build(); +Dictionary frontmatter = deserializer.Deserialize>(yaml); + +// name is required +if (!frontmatter.TryGetValue("name", out object? nameValue) || nameValue is not string frontmatterName) +{ + Console.Error.WriteLine("Frontmatter missing 'name' field."); + return 1; +} + +// name must be 1-64 characters +if (frontmatterName.Length == 0 || frontmatterName.Length > 64) +{ + Console.Error.WriteLine($"Name is {frontmatterName.Length} chars (must be 1-64)."); + return 1; +} + +// name: lowercase alphanumeric and hyphens only, no leading/trailing/consecutive hyphens +if (!Regex.IsMatch(frontmatterName, @"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$") + || frontmatterName.Contains("--")) +{ + Console.Error.WriteLine($"Invalid name '{frontmatterName}'. Must be lowercase letters, numbers, and hyphens only. Must not start/end with a hyphen or contain consecutive hyphens."); + return 1; +} + +// name must match the parent directory name +if (!string.Equals(skillName, frontmatterName, StringComparison.Ordinal)) +{ + Console.Error.WriteLine($"Name mismatch: directory is '{skillName}' but SKILL.md name is '{frontmatterName}'."); + return 1; +} + +// description is required +if (!frontmatter.TryGetValue("description", out object? descValue) || descValue is not string description) +{ + Console.Error.WriteLine("Frontmatter missing 'description' field."); + return 1; +} + +// description must be 1-1024 characters +if (description.Length == 0 || description.Length > 1024) +{ + Console.Error.WriteLine($"Description is {description.Length} chars (must be 1-1024)."); + return 1; +} + +// Keep SKILL.md under 500 lines; move detailed content to references/ or scripts/ +// See "Progressive Disclosure" at https://agentskills.io/specification.md +int lineCount = text.Split('\n').Length; +if (lineCount > 500) +{ + Console.Error.WriteLine($"SKILL.md is {lineCount} lines (max 500). See \"Progressive Disclosure\" at https://agentskills.io/specification.md"); + return 1; +} + +Console.WriteLine($"Skill '{frontmatterName}' is valid."); +return 0; diff --git a/.github/copilot/skills/incremental-test.md b/.github/skills/incremental-test/SKILL.md similarity index 90% rename from .github/copilot/skills/incremental-test.md rename to .github/skills/incremental-test/SKILL.md index 7422b8a28d79..f4d927873152 100644 --- a/.github/copilot/skills/incremental-test.md +++ b/.github/skills/incremental-test/SKILL.md @@ -1,7 +1,12 @@ -# Incremental Test Runner for dotnet.Tests +--- +name: incremental-test +description: >- + Run dotnet.Tests incrementally without a full build.cmd rebuild. Use after + modifying source code in SDK projects to quickly build only changed projects, + deploy their outputs into the redist SDK layout, and run tests against them. +--- -This skill enables fast incremental test runs of `dotnet.Tests` without a full `build.cmd` rebuild. -Use it after making source code changes to quickly build only the modified projects and deploy their outputs into the redist SDK layout so tests can run against them. +# Incremental Test Runner for dotnet.Tests ## Prerequisites @@ -9,10 +14,6 @@ Use it after making source code changes to quickly build only the modified proje - The repo-local `.dotnet` SDK must match the version expected by the test projects. If the runtime or SDK version is out of date (e.g., test build fails with a missing framework error), run `.\restore.cmd` (or `./restore.sh` on macOS/Linux) to download the correct SDK into `.dotnet`. - This workflow uses Windows/PowerShell commands and paths. On macOS/Linux, substitute forward slashes and use `cp` instead of `Copy-Item`. -## When to use - -Use this skill when you need to run `dotnet.Tests` after modifying source code in one or more SDK projects. It avoids the slow full `build.cmd` by only rebuilding the changed projects and copying their output DLLs into the redist layout. - ## Workflow ### Step 1: Identify modified projects