Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Bug report
description: Something in FieldKit did not work as expected.
labels: [bug]
body:
- type: markdown
attributes:
value: |
Every run writes a timestamped log. Attaching it makes a report far
easier to act on — use **Open Log** in the toolbar to find it.

- type: textarea
id: what-happened
attributes:
label: What happened
description: What you did, what you expected, and what you got instead.
validations:
required: true

- type: input
id: operation
attributes:
label: Which operation
description: The name shown in the operations list, if the problem was in one specific task.
placeholder: Clear Temporary Files
validations:
required: false

- type: input
id: version
attributes:
label: FieldKit version
description: Right-click FieldKit.exe, then Properties, then Details.
placeholder: 1.1.0
validations:
required: true

- type: input
id: windows
attributes:
label: Windows version
description: Run `winver`, or Settings, then System, then About.
placeholder: Windows 11 24H2 (26100.2314)
validations:
required: true

- type: textarea
id: log
attributes:
label: Log output
description: Paste the relevant part of the log, or attach the whole file.
render: text
validations:
required: false
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Security vulnerability
url: https://github.com/tsudo/fieldkit/security/policy
about: Report privately. Do not open a public issue for a security problem.
33 changes: 33 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Feature request
description: Suggest an operation or change for FieldKit.
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
[ROADMAP.md](https://github.com/tsudo/fieldkit/blob/main/ROADMAP.md)
covers what is already planned and what is deliberately out of scope.
Worth a look first.

- type: textarea
id: problem
attributes:
label: What problem does this solve
description: Describe the situation you run into, not the implementation you have in mind.
validations:
required: true

- type: textarea
id: proposal
attributes:
label: What you would like it to do
validations:
required: true

- type: textarea
id: alternatives
attributes:
label: How you handle it today
description: The manual steps, script, or other tool you currently use.
validations:
required: false
20 changes: 20 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## What this changes

<!-- One or two sentences. If it fixes an open issue, write "Fixes #123". -->

## Why

<!-- The problem being solved. Skip if the change is self-evident. -->

## How it was verified

<!-- What you ran, and on which Windows version. For a new or changed
maintenance operation, say what you observed the system actually do. -->

- [ ] `dotnet build FieldKit.sln --configuration Release` succeeds
- [ ] Ran the affected operation on Windows 10 or 11
- [ ] No new warnings

## Notes for the reviewer

<!-- Anything worth flagging: judgment calls, follow-up work, known limits. -->
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 5

# The workflows pin actions by major tag; without this ecosystem those pins
# get no update PRs and drift silently.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
47 changes: 44 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,53 @@ on:
pull_request:
branches: [main]

permissions:
contents: read

jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v4
- uses: actions/checkout@v7
- uses: actions/setup-dotnet@v6
with:
dotnet-version: "9.0.x"
# Latest 10.0 SDK. global.json sets the floor; the SDK in use
# determines which runtime version gets embedded in the
# self-contained exe, so tracking forward here is deliberate.
dotnet-version: "10.0.x"

- run: dotnet build FieldKit.sln --configuration Release

# Verify the configuration that actually ships. `dotnet build` does not
# exercise single-file packing, self-contained runtime bundling, or
# compression — all of which have their own failure modes.
- name: Verify release publish
run: dotnet publish FieldKit/FieldKit.csproj --configuration Release --output publish

- name: Confirm single-file artifact
shell: pwsh
run: |
$exe = "publish/FieldKit.exe"
if (-not (Test-Path $exe)) { throw "Expected single-file artifact at $exe" }

# A size floor alone does not prove single-file: dropping
# IncludeNativeLibrariesForSelfExtract still yields a ~59 MB exe but
# scatters native DLLs beside it, which an elevated binary would then
# resolve from its launch directory. Assert nothing else is emitted.
$stray = Get-ChildItem publish -File |
Where-Object { $_.Name -ne 'FieldKit.exe' -and $_.Extension -ne '.pdb' }
if ($stray) { throw "Not single-file - loose files present: $($stray.Name -join ', ')" }

$sizeMb = [math]::Round((Get-Item $exe).Length / 1MB, 1)
Write-Host "FieldKit.exe = $sizeMb MB"
if ($sizeMb -lt 40) { throw "Artifact is $sizeMb MB - self-contained runtime looks missing" }
if ($sizeMb -gt 100) { throw "Artifact is $sizeMb MB - compression looks disabled" }

# Retain it: without this the artifact that just passed verification is
# destroyed with the runner, so a failed assertion leaves nothing to
# inspect and the exe is never smoke-tested off the author's machine.
- uses: actions/upload-artifact@v7
with:
name: FieldKit-exe
path: publish/FieldKit.exe
if-no-files-found: error
59 changes: 59 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Weekly, so advisories that land after a quiet period still get caught.
- cron: "23 5 * * 1"

jobs:
analyze:
name: Analyze ${{ matrix.language }}
runs-on: windows-latest
permissions:
security-events: write
actions: read
contents: read

strategy:
fail-fast: false
matrix:
include:
# Manual build mode rather than buildless: FieldKit already has a
# known-good build, and compiling gives CodeQL full type resolution
# across the partial classes WPF generates from XAML.
- language: csharp
build-mode: manual
# The workflows in this repo are themselves analyzable.
- language: actions
build-mode: none

steps:
- uses: actions/checkout@v7

- uses: actions/setup-dotnet@v6
if: matrix.language == 'csharp'
with:
# Latest 10.0 SDK, matching build.yml. Resolving from global.json
# instead would freeze the embedded self-contained runtime at
# whatever that SDK bundles — and a self-contained app only gets
# runtime CVE fixes by being rebuilt.
dotnet-version: "10.0.x"

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}

- name: Build
if: matrix.language == 'csharp'
run: dotnet build FieldKit.sln --configuration Release

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bin/
obj/
publish/
*.user
*.suo
.vs/
71 changes: 71 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Changelog

All notable changes to FieldKit are recorded here.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.0] - 2026-08-15

Maintenance release. No new operations — this moves the runtime off a version
heading for end of support and fixes two defects found during review.

### Changed

- Runtime moved from .NET 9 to .NET 10 (LTS). .NET 9 reaches end of support on
2026-11-10; because FieldKit publishes as a self-contained single file, the
runtime is embedded in the exe, so shipping on .NET 9 past that date would
hand every downloader an unpatched runtime. .NET 10 is supported through
2028-11-14.
- `System.Management` and `System.ServiceProcess.ServiceController` updated from
9.0.0 to 10.0.11.
- `global.json` sets .NET 10 as the minimum SDK, so a build on an older SDK
fails immediately with a clear message. It is a floor rather than a pin by
design: the SDK in use determines which runtime version is embedded in the
self-contained exe, and a self-contained app only receives runtime fixes when
it is rebuilt, so tracking forward is the safer default.

### Fixed

- Removed a duplicate `Logger` instance. `MainWindow`'s parameterless
constructor created its own logger, and the real constructor chained through
it before replacing the field, leaving an undisposed `StreamWriter` behind.
The visible symptom was an empty `FieldKit-*.log` file appearing next to the
exe on every launch.
- Unhandled exceptions on the UI thread are now caught, written to the log, and
reported with the log path, instead of closing the app without explanation.
- A run that stops early now says so. Previously the status bar read "Run
complete" at 100% whether or not the run finished; an interrupted run reports
"Run aborted", marks the operations that did not finish, and shows what went
wrong instead of a completion summary.
- Closing the window mid-run no longer disposes the cancellation token while the
run loop is still using it. Every remaining operation used to fail with
`ObjectDisposedException`, which read in the log as many broken operations
rather than one lifecycle bug.

### Added

- CodeQL static analysis, running on every push and pull request to `main` plus
a weekly schedule.
- Continuous integration now verifies the published single-file artifact, not
just `dotnet build`. Single-file packing, self-contained runtime bundling, and
compression have their own failure modes that a plain build never exercises.
- Issue and pull request templates.
- This changelog.

## [1.0.0] - 2026-04-06

First release.

### Added

- 12 maintenance operations across cleanup, updates, and system health.
- Presets for focused runs: All, Cleanup, Updates, Health, Defaults.
- WPF interface with a Fluent dark theme.
- System Information panel with export.
- Timestamped log file for every run.
- Security hardening: absolute paths for system binaries, and symlink and
junction protection on temp cleanup.

[1.1.0]: https://github.com/tsudo/fieldkit/releases/tag/v1.1.0
[1.0.0]: https://github.com/tsudo/fieldkit/releases/tag/v1.0.0
28 changes: 28 additions & 0 deletions FieldKit/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Windows;
using System.Windows.Threading;
using FieldKit.Services;
using FieldKit.Tasks;

Expand Down Expand Up @@ -44,6 +45,12 @@ private void App_Startup(object sender, StartupEventArgs e)

var mainWindow = new MainWindow(tasks, _logger);
mainWindow.Show();

// Registered only once startup has succeeded. Anything that fails
// before this point belongs to the catch below, which shuts down
// deliberately — including failures inside the disclaimer dialog,
// whose whole job is obtaining consent before destructive work.
DispatcherUnhandledException += OnDispatcherUnhandledException;
}
catch (Exception ex)
{
Expand All @@ -53,6 +60,27 @@ private void App_Startup(object sender, StartupEventArgs e)
}
}

/// <summary>
/// Last-chance handler for exceptions raised on the UI thread after startup.
/// Maintenance tasks catch their own failures and the run loop reports its
/// own aborts, so anything reaching here is a UI-layer bug outside a run.
/// The app stays alive so the user keeps access to the log path rather than
/// losing it to a silent crash.
/// </summary>
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
var logger = _logger;
logger?.Log($"Unhandled UI exception: {e.Exception}", "ERROR");

var logNote = logger is null ? "" : $"\n\nLog: {logger.LogFilePath}";
MessageBox.Show(
$"FieldKit hit an unexpected error:\n\n{e.Exception.Message}{logNote}",
"FieldKit — Unexpected Error",
MessageBoxButton.OK, MessageBoxImage.Error);

e.Handled = true;
}

protected override void OnExit(ExitEventArgs e)
{
_logger?.Dispose();
Expand Down
8 changes: 4 additions & 4 deletions FieldKit/FieldKit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>fieldkit.ico</ApplicationIcon>
<AssemblyName>FieldKit</AssemblyName>
<RootNamespace>FieldKit</RootNamespace>
<Version>1.0.0</Version>
<Version>1.1.0</Version>
<Authors>Keith S. Crawford</Authors>
<Product>FieldKit</Product>
<Description>Portable Windows maintenance in a single exe. No installer, no bloat, no telemetry.</Description>
Expand All @@ -23,8 +23,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Management" Version="9.0.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="9.0.0" />
<PackageReference Include="System.Management" Version="10.0.11" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.11" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading
Loading