From 99c1a2b023e09432af3bd532e029295bfd5b1e49 Mon Sep 17 00:00:00 2001 From: David Shnayder Date: Sun, 22 Mar 2026 18:45:51 +0200 Subject: [PATCH 1/3] fix(publishing): default windows packages to framework dependent to maintain proper behavior --- .github/workflows/publish-release.yml | 4 - src/Seek.Cli/Seek.Cli.csproj | 4 +- tests/Seek.Cli.Tests/ToolPackagingTests.cs | 93 ++++++++++++++++++++++ 3 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 tests/Seek.Cli.Tests/ToolPackagingTests.cs diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index ec0324d..658ad38 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -120,10 +120,6 @@ jobs: fail-fast: false matrix: include: - - os: windows-latest - rid: win-x64 - - os: windows-latest - rid: win-arm64 - os: ubuntu-latest rid: linux-x64 - os: ubuntu-24.04-arm diff --git a/src/Seek.Cli/Seek.Cli.csproj b/src/Seek.Cli/Seek.Cli.csproj index 2f57b7e..ba23ba6 100644 --- a/src/Seek.Cli/Seek.Cli.csproj +++ b/src/Seek.Cli/Seek.Cli.csproj @@ -39,8 +39,10 @@ Seek true ../.././nupkg + - win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64;any + linux-x64;linux-arm64;osx-x64;osx-arm64;any diff --git a/tests/Seek.Cli.Tests/ToolPackagingTests.cs b/tests/Seek.Cli.Tests/ToolPackagingTests.cs new file mode 100644 index 0000000..61bb795 --- /dev/null +++ b/tests/Seek.Cli.Tests/ToolPackagingTests.cs @@ -0,0 +1,93 @@ +using System.Xml.Linq; + +namespace Seek.Cli.Tests; + +public sealed partial class ToolPackagingTests { + [Test] + public async Task CliProject_ToolPackageRuntimeIdentifiers_ExcludeWindowsRidPackages(CancellationToken cancellationToken) { + var projectContents = await File.ReadAllTextAsync( + Path.Combine(FindRepositoryRoot(), "src", "Seek.Cli", "Seek.Cli.csproj"), + cancellationToken); + + var runtimeIdentifiers = ParseToolPackageRuntimeIdentifiers(projectContents); + + await Assert.That(runtimeIdentifiers).Contains("linux-x64"); + await Assert.That(runtimeIdentifiers).Contains("linux-arm64"); + await Assert.That(runtimeIdentifiers).Contains("osx-x64"); + await Assert.That(runtimeIdentifiers).Contains("osx-arm64"); + await Assert.That(runtimeIdentifiers).Contains("any"); + await Assert.That(runtimeIdentifiers).DoesNotContain("win-x64"); + await Assert.That(runtimeIdentifiers).DoesNotContain("win-arm64"); + } + + [Test] + public async Task PublishReleaseWorkflow_NugetRidMatrix_SkipsWindowsRidPackagesButKeepsWindowsBinaryReleases(CancellationToken cancellationToken) { + var workflowContents = await File.ReadAllTextAsync( + Path.Combine(FindRepositoryRoot(), ".github", "workflows", "publish-release.yml"), + cancellationToken); + + var nugetRidSection = ExtractSection(workflowContents, "nuget-rid", "nuget-publish"); + var binariesSection = ExtractSection(workflowContents, "binaries", string.Empty); + + await Assert.That(nugetRidSection).Contains("linux-x64"); + await Assert.That(nugetRidSection).Contains("linux-arm64"); + await Assert.That(nugetRidSection).Contains("osx-x64"); + await Assert.That(nugetRidSection).Contains("osx-arm64"); + await Assert.That(nugetRidSection).DoesNotContain("win-x64"); + await Assert.That(nugetRidSection).DoesNotContain("win-arm64"); + + await Assert.That(binariesSection).Contains("win-x86"); + await Assert.That(binariesSection).Contains("win-x64"); + await Assert.That(binariesSection).Contains("win-arm64"); + } + + private static string FindRepositoryRoot() { + for (var current = new DirectoryInfo(AppContext.BaseDirectory); current is not null; current = current.Parent) { + var projectPath = Path.Combine(current.FullName, "src", "Seek.Cli", "Seek.Cli.csproj"); + var workflowPath = Path.Combine(current.FullName, ".github", "workflows", "publish-release.yml"); + if (File.Exists(projectPath) && File.Exists(workflowPath)) { + return current.FullName; + } + } + + throw new DirectoryNotFoundException("Could not locate the repository root from the test output directory."); + } + + private static string[] ParseToolPackageRuntimeIdentifiers(string projectContents) { + var project = XDocument.Parse(projectContents); + var runtimeIdentifiers = project.Root? + .Elements() + .Where(element => element.Name.LocalName == "PropertyGroup") + .Elements() + .FirstOrDefault(element => element.Name.LocalName == "ToolPackageRuntimeIdentifiers") + ?.Value; + + if (string.IsNullOrWhiteSpace(runtimeIdentifiers)) { + throw new InvalidOperationException("Could not find ToolPackageRuntimeIdentifiers in Seek.Cli.csproj."); + } + + return runtimeIdentifiers + .Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + } + + private static string ExtractSection(string contents, string sectionName, string nextSectionName) { + var sectionHeader = $"{sectionName}:"; + var startIndex = contents.IndexOf(sectionHeader, StringComparison.Ordinal); + if (startIndex < 0) { + throw new InvalidOperationException($"Could not find the {sectionName} section in publish-release.yml."); + } + + var sectionStart = startIndex + sectionHeader.Length; + if (nextSectionName.Length == 0) { + return contents[sectionStart..]; + } + + var nextSectionHeader = $"{nextSectionName}:"; + var endIndex = contents.IndexOf(nextSectionHeader, sectionStart, StringComparison.Ordinal); + if (endIndex < 0) { + throw new InvalidOperationException($"Could not find the {nextSectionName} section in publish-release.yml."); + } + + return contents[sectionStart..endIndex]; + } +} From 5778b7d25a30db701e0ffb37637e0deea8153ba7 Mon Sep 17 00:00:00 2001 From: David Shnayder Date: Sun, 22 Mar 2026 19:34:45 +0200 Subject: [PATCH 2/3] feat: add groundwork for winget release (future versions) --- .github/workflows/publish-release.yml | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 658ad38..c1e31f4 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -13,6 +13,7 @@ concurrency: env: CLI_PROJECT: src/Seek.Cli/Seek.Cli.csproj NUGET_SOURCE: https://api.nuget.org/v3/index.json + WINGET_INSTALLERS_REGEX: '^seek-win-(x64|arm64)\.exe$' jobs: metadata: @@ -319,6 +320,12 @@ jobs: run: | Compress-Archive -Path "publish\Seek.exe" -DestinationPath "seek-${{ matrix.rid }}.zip" + - name: Stage raw Windows binary + if: runner.os == 'Windows' && (matrix.rid == 'win-x64' || matrix.rid == 'win-arm64') + shell: pwsh + run: | + Copy-Item "publish\Seek.exe" "seek-${{ matrix.rid }}.exe" + - name: Generate artifact attestation uses: actions/attest-build-provenance@v3 with: @@ -338,6 +345,14 @@ jobs: path: seek-${{ matrix.rid }}.zip if-no-files-found: error + - name: Upload raw Windows binary artifact + if: runner.os == 'Windows' && (matrix.rid == 'win-x64' || matrix.rid == 'win-arm64') + uses: actions/upload-artifact@v6 + with: + name: seek-${{ matrix.rid }}.exe + path: seek-${{ matrix.rid }}.exe + if-no-files-found: error + - name: Upload Sigstore bundle artifact uses: actions/upload-artifact@v6 with: @@ -412,3 +427,26 @@ jobs: for file in release-artifacts/*; do gh release upload "${{ needs.metadata.outputs.version }}" "$file" --clobber done + + winget-release: + needs: + - metadata + - github-release + # winget-releaser updates an existing winget package entry. + # Bootstrap the first Seek submission manually, then enable this job by + # configuring WINGET_PACKAGE_IDENTIFIER and WINGET_TOKEN. + if: ${{ vars.WINGET_PACKAGE_IDENTIFIER != '' }} + runs-on: ubuntu-latest + env: + WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }} + + steps: + - name: Publish GitHub release to WinGet + if: ${{ env.WINGET_TOKEN != '' }} + uses: vedantmgoyal9/winget-releaser@v2 + with: + identifier: ${{ vars.WINGET_PACKAGE_IDENTIFIER }} + installers-regex: ${{ env.WINGET_INSTALLERS_REGEX }} + release-tag: ${{ needs.metadata.outputs.version }} + fork-user: ${{ vars.WINGET_FORK_USER }} + token: ${{ env.WINGET_TOKEN }} From 97a9bc22457c82ac2654487ea4723d6be9431977 Mon Sep 17 00:00:00 2001 From: David Shnayder Date: Sun, 22 Mar 2026 19:43:36 +0200 Subject: [PATCH 3/3] docs: update version and instructions --- .agents/skills/seek-file-search/SKILL.md | 21 ++++++++++++++++++++- AGENTS.md | 14 ++++++++++---- CHANGELOG.md | 4 ++++ src/Seek.Cli/Program.cs | 2 +- src/Seek.Cli/Seek.Cli.csproj | 7 ++----- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.agents/skills/seek-file-search/SKILL.md b/.agents/skills/seek-file-search/SKILL.md index fe292c3..c4396fc 100644 --- a/.agents/skills/seek-file-search/SKILL.md +++ b/.agents/skills/seek-file-search/SKILL.md @@ -16,8 +16,12 @@ Use `seek` for filesystem path search. `seek report` - Search from a specific root: `seek report --root /path/to/root` +- Emit absolute paths instead of paths relative to `--root`: + `seek report --root /path/to/root --absolute` - Regex path search: `seek '.*\\.cs$' --regex` +- Match all eligible files: + `seek '' --files` - Only files: `seek report --files` - Only directories: @@ -28,6 +32,10 @@ Use `seek` for filesystem path search. `seek cache --plain` - NUL-delimited output for safe pipelines: `seek cache --null | xargs -0 rm -rf` +- Preview matching deletes: + `seek delete cache --directories` +- Apply matching deletes: + `seek delete cache --directories --apply` ## Workflow @@ -36,6 +44,7 @@ Use `seek` for filesystem path search. - Content search: use `rg`. 2. Pick the smallest useful option set. +- Root-relative output is the default. Add `--absolute` when the consumer needs standalone paths. - File-only matches: `--files` - Directory-only matches: `--directories` - Regex patterns: `--regex` @@ -47,10 +56,18 @@ Use `seek` for filesystem path search. - Plain line-delimited output: `--plain` - Safe machine output for filenames with spaces/newlines: `--null` and a NUL-aware consumer such as `xargs -0` +4. For destructive work, preview before apply. +- Preview with `seek delete ...` +- Delete only with `seek delete ... --apply` + ## Guardrails - `seek` searches full paths, not file contents. -- `seek --null` is for machine consumption. Pair it with `xargs -0` or another NUL-aware reader. +- Search output is relative to `--root` by default. +- `seek --absolute` emits absolute paths. +- `seek --null` is for machine consumption. It emits plain absolute paths terminated by `\0`. Pair it with `xargs -0` or another NUL-aware reader. +- `seek ''` matches all eligible entries under the selected root. +- `seek delete` previews absolute candidate paths and only deletes when `--apply` is present. - Do not pipe `seek --null` directly into commands like `rm`; `rm` expects argv, not stdin records. - For destructive operations, prefer inspecting results first, or use safe batching: `seek cache --directories --null | xargs -0 -n 100 rm -rf` @@ -65,3 +82,5 @@ Use `seek` for filesystem path search. `seek '\\.log$' --regex --files` - `ls -R | grep report` `seek report` +- `find . -type d -name '*cache*' -print0 | xargs -0 rm -rf` + `seek delete cache --directories --apply` diff --git a/AGENTS.md b/AGENTS.md index 6e5015a..766ec5e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,12 +12,13 @@ ## Task behavior - If a requested change needs unknown external source material, ask for it. -- If public command behavior, install flow, or package semantics change, ask whether to update `README.md` and `CHANGELOG.md`. +- If public command behavior, install flow, or package semantics change, ask whether to update `README.md`, `CHANGELOG.md`, and `.agents/skills/seek-file-search`. - When discussing external dependencies, verify behavior against the exact version in use. -- When preparing a release or changing release-relevant behavior, keep `CHANGELOG.md` and `src/Seek.Cli/Seek.Cli.csproj` `` in sync. +- When preparing a release or changing release-relevant behavior, keep `CHANGELOG.md`, `src/Seek.Cli/Seek.Cli.csproj` ``, and `.agents/skills/seek-file-search` in sync when the shipped skill documents affected behavior. - Write `CHANGELOG.md` in a human-readable release format aimed at GitHub release readers, not as a raw internal change inventory. +- `CHANGELOG.md` should only include public-facing changes. Do not include internal repo, process, test, or maintenance-only changes unless they materially affect users. - Keep `` simpler and shorter than `CHANGELOG.md`, focused on NuGet users and package-facing changes. -- If one of `CHANGELOG.md` or `` changes for a release, review the other in the same pass so they do not drift. +- If one of `CHANGELOG.md`, ``, or `.agents/skills/seek-file-search` changes for a release or CLI behavior update, review the others in the same pass so they do not drift. ## Current architecture @@ -34,12 +35,14 @@ - The CLI exposes a default search command wired in `Program.cs` via `app.Add("", Commands.SearchAsync)`. - The CLI also exposes a named destructive subcommand wired via `app.Add("delete", Commands.DeleteAsync)`. +- The CLI also exposes a named utility subcommand wired via `app.Add("check-for-updates", Commands.CheckForUpdatesAsync)`. - The default command positional argument is the search query. - Current arguments and defaults from `Commands.SearchAsync`: - `query`: required positional argument. - `regex`: defaults to `false`. - `caseSensitive`: defaults to `false`. - `plain`: defaults to `false`. + - `absolute`: defaults to `false`. - `null`: defaults to `false`. - `hidden`: defaults to `false`. - `system`: defaults to `false`. @@ -66,11 +69,14 @@ - `-d` => `--directories` - `-c` => `--highlight-color` - If `plain` is `true`, the CLI writes the full path directly and does not emit PrettyConsole color/escape sequences for match sections. -- If `null` is `true`, the CLI emits plain NUL-terminated paths and bypasses highlight rendering. +- If `absolute` is `false`, search output is relative to `root`. +- If `null` is `true`, the CLI emits plain absolute NUL-terminated paths and bypasses highlight rendering. - If both `files` and `directories` are `false`, both result kinds are emitted. If both are `true`, the current behavior is also to emit both result kinds. +- An empty search query matches all eligible entries under `root`. - `seek delete` previews candidates by default, prints absolute candidate paths, and only deletes when `apply` is `true`. - `seek delete` collapses descendants under matched directories before preview or apply. - `seek delete` deletes sequentially, prints one `SUCCESS` or `FAIL` line per candidate in apply mode, and returns exit code `1` if any deletion fails. +- `seek check-for-updates` compares `ConsoleApp.Version` against the latest published NuGet package version and prints either update instructions or an up-to-date message. - Worker count is not user-configurable today. `FileSystemSearch` computes it as `Math.Max(1, Environment.ProcessorCount - 1)`. - Results are rendered to standard output through `PrettyConsole` when highlighting is enabled. - Global exception handling preserves validation and argument parse failures, prints cancellations as a non-error, and prints unexpected exception messages in red while setting exit code `1`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bed992..798e132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.1.1 + +- Updated the shipped `seek-file-search` agent skill to reflect the 1.1.0 CLI behavior, including root-relative output by default, `--absolute`, `--null`, empty-query match-all searches, and `seek delete`. + ## 1.1.0 - Search results are relative to `--root` by default. Use `--absolute` for standalone paths or `--null` for NUL-terminated absolute paths in scripts. diff --git a/src/Seek.Cli/Program.cs b/src/Seek.Cli/Program.cs index 89f8e8c..8767357 100644 --- a/src/Seek.Cli/Program.cs +++ b/src/Seek.Cli/Program.cs @@ -1,7 +1,7 @@ using ConsoleAppFramework; using Seek.Cli; -ConsoleApp.Version = "1.1.0"; +ConsoleApp.Version = "1.1.1"; var app = ConsoleApp.Create(); app.UseFilter(); diff --git a/src/Seek.Cli/Seek.Cli.csproj b/src/Seek.Cli/Seek.Cli.csproj index ba23ba6..62a03f1 100644 --- a/src/Seek.Cli/Seek.Cli.csproj +++ b/src/Seek.Cli/Seek.Cli.csproj @@ -22,7 +22,7 @@ 0 true false - 1.1.0 + 1.1.1 true https://github.com/dusrdev/Seek git @@ -48,10 +48,7 @@ - - Root-relative search output by default, with --absolute and --null for path-safe scripting. - - Added seek delete with preview-first behavior and --apply for actual deletion. - - Empty-query searches now match all eligible entries instead of hanging. - - Native AOT NuGet tool packages for common runtimes, plus a Seek.any fallback package. + - No CLI or package behavior changes in this patch release.