From b196215d6714363b64a16bd80b86d4af19751b17 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 05:42:17 +0000 Subject: [PATCH 1/2] Initial plan From 248cb5fed79d2b9f40fe5a83504e5b9d62345487 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 05:50:05 +0000 Subject: [PATCH 2/2] Add instruction recommendation feature with session menu, repo menu, and /instructions slash command Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com> --- .../InstructionRecommendationHelperTests.cs | 128 ++++++++++++++++++ PolyPilot.Tests/PolyPilot.Tests.csproj | 1 + .../Components/Layout/SessionListItem.razor | 5 + .../Components/Layout/SessionSidebar.razor | 6 +- PolyPilot/Components/Pages/Dashboard.razor | 79 +++++++++++ .../Models/InstructionRecommendationHelper.cs | 77 +++++++++++ PolyPilot/Services/CopilotService.cs | 10 ++ 7 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 PolyPilot.Tests/InstructionRecommendationHelperTests.cs create mode 100644 PolyPilot/Models/InstructionRecommendationHelper.cs diff --git a/PolyPilot.Tests/InstructionRecommendationHelperTests.cs b/PolyPilot.Tests/InstructionRecommendationHelperTests.cs new file mode 100644 index 00000000..a5cf9525 --- /dev/null +++ b/PolyPilot.Tests/InstructionRecommendationHelperTests.cs @@ -0,0 +1,128 @@ +using PolyPilot.Models; + +namespace PolyPilot.Tests; + +public class InstructionRecommendationHelperTests +{ + [Fact] + public void BuildPrompt_NoArguments_ContainsBasicSections() + { + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt(null); + + Assert.Contains("Copilot Instructions", prompt); + Assert.Contains("Skills", prompt); + Assert.Contains("Agents", prompt); + Assert.Contains("copilot-instructions.md", prompt); + } + + [Fact] + public void BuildPrompt_WithWorkingDirectory_IncludesDirectory() + { + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt("/home/user/myproject"); + + Assert.Contains("/home/user/myproject", prompt); + } + + [Fact] + public void BuildPrompt_WithRepoName_IncludesRepoName() + { + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt( + "/home/user/myproject", repoName: "MyOrg/MyRepo"); + + Assert.Contains("MyOrg/MyRepo", prompt); + } + + [Fact] + public void BuildPrompt_WithExistingSkills_ListsThem() + { + var skills = new List<(string Name, string Description)> + { + ("build", "Build the project"), + ("test", "Run tests") + }; + + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt( + "/project", existingSkills: skills); + + Assert.Contains("Currently configured skills", prompt); + Assert.Contains("**build**", prompt); + Assert.Contains("Build the project", prompt); + Assert.Contains("**test**", prompt); + Assert.Contains("Run tests", prompt); + } + + [Fact] + public void BuildPrompt_WithExistingAgents_ListsThem() + { + var agents = new List<(string Name, string Description)> + { + ("reviewer", "Code review agent"), + ("docs", "") + }; + + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt( + "/project", existingAgents: agents); + + Assert.Contains("Currently configured agents", prompt); + Assert.Contains("**reviewer**", prompt); + Assert.Contains("Code review agent", prompt); + Assert.Contains("**docs**", prompt); + } + + [Fact] + public void BuildPrompt_NoSkillsOrAgents_DoesNotListSections() + { + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt("/project"); + + Assert.DoesNotContain("Currently configured skills", prompt); + Assert.DoesNotContain("Currently configured agents", prompt); + } + + [Fact] + public void BuildPrompt_EmptySkillsAndAgents_DoesNotListSections() + { + var skills = new List<(string Name, string Description)>(); + var agents = new List<(string Name, string Description)>(); + + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt( + "/project", existingSkills: skills, existingAgents: agents); + + Assert.DoesNotContain("Currently configured skills", prompt); + Assert.DoesNotContain("Currently configured agents", prompt); + } + + [Fact] + public void BuildPrompt_SkillWithEmptyDescription_OmitsDescription() + { + var skills = new List<(string Name, string Description)> + { + ("deploy", "") + }; + + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt( + "/project", existingSkills: skills); + + Assert.Contains("**deploy**", prompt); + // Should not have ": " after the name when description is empty + Assert.DoesNotContain("**deploy**: ", prompt); + } + + [Fact] + public void BuildPrompt_AllParameters_IncludesEverything() + { + var skills = new List<(string Name, string Description)> { ("build", "Build it") }; + var agents = new List<(string Name, string Description)> { ("review", "Review code") }; + + var prompt = InstructionRecommendationHelper.BuildRecommendationPrompt( + "/home/user/repo", + existingSkills: skills, + existingAgents: agents, + repoName: "org/repo"); + + Assert.Contains("org/repo", prompt); + Assert.Contains("/home/user/repo", prompt); + Assert.Contains("Currently configured skills", prompt); + Assert.Contains("Currently configured agents", prompt); + Assert.Contains("file path", prompt); + } +} diff --git a/PolyPilot.Tests/PolyPilot.Tests.csproj b/PolyPilot.Tests/PolyPilot.Tests.csproj index b8285fb8..ae0cd8d3 100644 --- a/PolyPilot.Tests/PolyPilot.Tests.csproj +++ b/PolyPilot.Tests/PolyPilot.Tests.csproj @@ -29,6 +29,7 @@ + diff --git a/PolyPilot/Components/Layout/SessionListItem.razor b/PolyPilot/Components/Layout/SessionListItem.razor index fa453704..630ccaf3 100644 --- a/PolyPilot/Components/Layout/SessionListItem.razor +++ b/PolyPilot/Components/Layout/SessionListItem.razor @@ -114,6 +114,10 @@ } + + @@ -141,6 +145,7 @@ [Parameter] public EventCallback OnCommitRename { get; set; } [Parameter] public EventCallback OnToggleMenu { get; set; } [Parameter] public EventCallback OnCloseMenu { get; set; } + [Parameter] public EventCallback OnRecommendInstructions { get; set; } private async Task HandleRenameKeyDown(KeyboardEventArgs e) { diff --git a/PolyPilot/Components/Layout/SessionSidebar.razor b/PolyPilot/Components/Layout/SessionSidebar.razor index 4e75a5ce..4ea8bc72 100644 --- a/PolyPilot/Components/Layout/SessionSidebar.razor +++ b/PolyPilot/Components/Layout/SessionSidebar.razor @@ -244,6 +244,9 @@ else +