diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml
new file mode 100644
index 00000000..d6eb3cfe
--- /dev/null
+++ b/.github/codeql/codeql-config.yml
@@ -0,0 +1,16 @@
+name: QuickShell CodeQL configuration
+
+queries:
+ - uses: security-and-quality
+
+query-filters:
+ # Quality notes (not vulns). Quick Shell is a Windows shell host that must call
+ # Win32/COM for folder/file dialogs, clipboard, and dialog timeout recovery after
+ # dropping WinForms for MSIX trimming.
+ #
+ # cs/call-to-unmanaged-code: call sites of extern methods
+ # cs/unmanaged-code: DllImport/LibraryImport declarations themselves
+ - exclude:
+ id: cs/call-to-unmanaged-code
+ - exclude:
+ id: cs/unmanaged-code
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index b6450d58..4d260f40 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -11,6 +11,7 @@ on:
- "**/*.sln"
- "QuickShell.Raycast/**"
- ".github/workflows/codeql.yml"
+ - ".github/codeql/**"
pull_request:
paths:
- "**/*.cs"
@@ -18,6 +19,7 @@ on:
- "**/*.sln"
- "QuickShell.Raycast/**"
- ".github/workflows/codeql.yml"
+ - ".github/codeql/**"
schedule:
- cron: "17 3 * * 0"
workflow_dispatch:
@@ -51,6 +53,7 @@ jobs:
- '**/*.csproj'
- '**/*.sln'
- '.github/workflows/codeql.yml'
+ - '.github/codeql/**'
raycast:
- 'QuickShell.Raycast/**'
- '.github/workflows/codeql.yml'
@@ -86,7 +89,7 @@ jobs:
uses: github/codeql-action/init@v4
with:
languages: csharp
- queries: security-and-quality
+ config-file: ./.github/codeql/codeql-config.yml
- name: Restore solution
run: dotnet restore QuickShell.sln --verbosity minimal
diff --git a/.github/workflows/release-extension.yml b/.github/workflows/release-extension.yml
index b7142069..4b0648bb 100644
--- a/.github/workflows/release-extension.yml
+++ b/.github/workflows/release-extension.yml
@@ -106,7 +106,7 @@ jobs:
${{ github.event.inputs.release_notes }}
- Package matrix: [docs/release-packages.md](https://github.com/tonythethompson/QuickShell/blob/main/docs/release-packages.md)
+ Package matrix: [docs/release-packages.md](https://github.com/tonythethompson/QuickShell/blob/master/docs/release-packages.md)
## Install
diff --git a/.github/workflows/release-run-plugin.yml b/.github/workflows/release-run-plugin.yml
index d65a11a1..1cda0571 100644
--- a/.github/workflows/release-run-plugin.yml
+++ b/.github/workflows/release-run-plugin.yml
@@ -92,7 +92,7 @@ jobs:
Restart PowerToys after copying files.
- Activate with the **qs** action keyword, or type **Quick Shell** / **quickshell** in global Run.
- - See [docs/powertoys-run-plugin.md](https://github.com/tonythethompson/QuickShell/blob/main/docs/powertoys-run-plugin.md).
+ - See [docs/powertoys-run-plugin.md](https://github.com/tonythethompson/QuickShell/blob/master/docs/powertoys-run-plugin.md).
files: |
QuickShell.Run/bin/x64/Release/QuickShell.Run-x64.zip
QuickShell.Run/bin/ARM64/Release/QuickShell.Run-ARM64.zip
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 44b762c5..87a20217 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,6 +14,7 @@
+
diff --git a/QuickShell.Core.Tests/WorkspaceSecurityPolicyTests.cs b/QuickShell.Core.Tests/WorkspaceSecurityPolicyTests.cs
index 8a9d7598..0e361175 100644
--- a/QuickShell.Core.Tests/WorkspaceSecurityPolicyTests.cs
+++ b/QuickShell.Core.Tests/WorkspaceSecurityPolicyTests.cs
@@ -270,6 +270,78 @@ public void Open_directory_rejects_non_local_path_namespaces(string directory)
Assert.Equal(WorkspaceIssueCode.DirectoryOpenNotAllowed, result.PrimaryIssueCode);
}
+ [Fact]
+ public void ComputeDigest_matches_legacy_anonymous_Serialize_bytes()
+ {
+ // Pins trim-safe DTO serialization to the pre-refactor JsonSerializer.Serialize(anonymous)
+ // shape (compact, nulls included). Drift here silently invalidates WorkspaceReviewToken digests.
+ var workspace = new TerminalShortcut
+ {
+ Id = "digest-workspace",
+ Name = "Digest",
+ Directory = @"C:\repos\demo",
+ Command = null,
+ Terminal = "wt",
+ WtProfile = null,
+ RunAsAdmin = false,
+ Launches =
+ [
+ new WorkspaceEntry
+ {
+ Id = "launch-1",
+ Label = "Launch",
+ Terminal = "default",
+ WtProfile = null,
+ Command = "echo hi",
+ RunAsAdmin = false,
+ IsEnabled = true,
+ Order = 0,
+ TaskType = "none",
+ },
+ ],
+ DevServerUrl = null,
+ OpenDevServerOnLaunch = false,
+ RepoUrl = null,
+ CompanionApps =
+ [
+ new CompanionAppEntry
+ {
+ Id = "companion-1",
+ Path = @"C:\Tools\editor.exe",
+ Arguments = null,
+ OpenOnLaunch = true,
+ Order = 0,
+ },
+ ],
+ OpenCompanionAppOnLaunch = true,
+ CompanionAppPath = @"C:\Tools\editor.exe",
+ CompanionAppArguments = null,
+ };
+
+ var legacyPayload = System.Text.Json.JsonSerializer.Serialize(new
+ {
+ workspace.Id,
+ workspace.Name,
+ workspace.Directory,
+ workspace.Command,
+ workspace.Terminal,
+ workspace.WtProfile,
+ workspace.RunAsAdmin,
+ workspace.Launches,
+ workspace.DevServerUrl,
+ workspace.OpenDevServerOnLaunch,
+ workspace.RepoUrl,
+ workspace.CompanionApps,
+ workspace.OpenCompanionAppOnLaunch,
+ workspace.CompanionAppPath,
+ workspace.CompanionAppArguments,
+ });
+ var expected = Convert.ToHexString(
+ System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(legacyPayload)));
+
+ Assert.Equal(expected, WorkspaceSecurityPolicy.ComputeDigest(workspace));
+ }
+
[Fact]
public void Trust_review_token_is_invalidated_by_revision_change()
{
diff --git a/QuickShell.Core/Classification/ProjectAnalysisService.cs b/QuickShell.Core/Classification/ProjectAnalysisService.cs
index 9482c0ee..5673e8a4 100644
--- a/QuickShell.Core/Classification/ProjectAnalysisService.cs
+++ b/QuickShell.Core/Classification/ProjectAnalysisService.cs
@@ -1,4 +1,5 @@
using QuickShell.Abstractions.Classification;
+using QuickShell.Models;
using QuickShell.Services;
namespace QuickShell.Classification;
@@ -78,21 +79,26 @@ public string GetTaskTypeChoiceTooltip(string? directory, string? taskType, Task
return $"Suggests: {first.Command} · also {alternates}";
}
+ ///
+ /// Builds a JSON payload containing the available task type choices.
+ ///
+ /// The project directory used to determine available choices.
+ /// Context used to select task type suggestions.
+ /// Whether to include the choice for adding a new command row.
+ /// A JSON representation of the task type choices.
public string BuildTaskTypeChoicesJson(
string? directory = null,
TaskTypePickContext? pickContext = null,
bool includePlaceholder = true)
{
pickContext ??= TaskTypePickContext.Empty;
- var choices = new List