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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
# "Solution Items" contains a space, which .gitattributes cannot express in a path.)
usedComponents.json text eol=lf

# Git executes hooks with its shell, including on Windows.
.githooks/* text eol=lf

# Line-ending test fixtures (src/LogExpert.Tests/TestData). Their whole point is
# byte-exact terminators (bare \r, mixed \n/\r\n/\r), so git must NOT normalize
# EOLs on them — otherwise core.autocrlf would rewrite \r\n<->\n and corrupt the
Expand Down
24 changes: 24 additions & 0 deletions .githooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Commit message cleanup

Enable the hook once per clone, from the repository root:

```sh
git config --local core.hooksPath .githooks
```

On Linux or macOS, also run `chmod +x .githooks/commit-msg` if needed.
Git for Windows includes the shell and utilities used by this hook.

Before a commit is created, `commit-msg` removes:

- All `Co-authored-by:` lines, regardless of capitalization or author.
- Entire lines containing recognized AI conversation/session URLs for ChatGPT,
Codex, Claude, Cursor, Gemini, Microsoft Copilot, or GitHub Copilot.

The URL patterns live in `commit-msg`; add patterns there for other providers
or URL formats. Ordinary links, including GitHub issues and pull requests, stay.
If a session URL shares a line with other text, that entire line is removed.

This applies to future local commits, including amendments; it does not rewrite
existing history. `git commit --no-verify` skips this hook. Hooks are not installed
automatically when someone clones the repository.
30 changes: 30 additions & 0 deletions .githooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh
# Git passes the commit-message filename as the first argument.
set -eu

# PowerShell/GUI clients may not put Git for Windows utilities on PATH.
PATH="$PATH:/usr/bin:/bin"
export PATH

message=$1
temporary=$(mktemp "${message}.clean.XXXXXX")
trap 'rm -f "$temporary"' EXIT
trap 'exit 1' HUP INT TERM

# Match specific conversation/session URL paths, keeping ordinary project links.
# Remove the entire matching line, including any Markdown label or trailer key.
awk '
{
line = tolower($0)
if (line ~ /^[[:space:]]*co-authored-by[[:space:]]*:/) next
if (line ~ /https?:\/\/(www\.)?(chatgpt\.com|chat\.openai\.com)\/(c|share|codex\/tasks)\//) next
if (line ~ /https?:\/\/(www\.)?claude\.ai\/(chat|share|code)\//) next
if (line ~ /https?:\/\/(www\.)?cursor\.com\/(agents|background-agent)\//) next
if (line ~ /https?:\/\/(www\.)?gemini\.google\.com\/(app|share)\//) next
if (line ~ /https?:\/\/copilot\.microsoft\.com\/(chats|shares)\//) next
if (line ~ /https?:\/\/github\.com\/[^/[:space:]]+\/[^/[:space:]]+\/(sessions|copilot\/tasks)\//) next
print
}
' "$message" > "$temporary"

cat "$temporary" > "$message"
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -719,3 +719,10 @@ http-client.private.env.json
######## END JET BRAINS git ignore https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
/AgentFiles/*
/.claude/*

# Local scratch notes (tickets, checklists, working docs)
.scratch/

# graphify knowledge-graph outputs and extraction cache. Unanchored so it also
# catches src/graphify-out/, where the AST/semantic cache lands.
graphify-out/
18 changes: 18 additions & 0 deletions tools/winget-sandbox/LogExpert.wsb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Configuration>
<Networking>Enable</Networking>
<MappedFolders>
<MappedFolder>
<HostFolder>G:\Github\LogExpert\winget</HostFolder>
<SandboxFolder>C:\Manifest</SandboxFolder>
<ReadOnly>true</ReadOnly>
</MappedFolder>
<MappedFolder>
<HostFolder>G:\Github\LogExpert\tools\winget-sandbox</HostFolder>
<SandboxFolder>C:\SandboxTools</SandboxFolder>
<ReadOnly>true</ReadOnly>
</MappedFolder>
</MappedFolders>
<LogonCommand>
<Command>powershell.exe -NoProfile -ExecutionPolicy Bypass -NoExit -File C:\SandboxTools\Test-Manifest.ps1</Command>
</LogonCommand>
</Configuration>
45 changes: 45 additions & 0 deletions tools/winget-sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Test LogExpert's WinGet manifests

Double-click `LogExpert.wsb` on the host. The paths in that file assume this checkout is
`G:\Github\LogExpert`; update its two `HostFolder` entries if you move the checkout.
Windows Sandbox must be enabled and able to access the internet.

The guest maps only the manifests and test tools, both read-only. It installs WinGet
using [Microsoft's Sandbox bootstrap instructions](https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget-on-windows-sandbox),
enables local manifests inside the guest, validates the YAML, and silently installs
LogExpert with its declared .NET dependency. It checks the registered version and
x64 Desktop Runtime, then opens a sample log for your manual check.

1. Confirm the application opens, both sample lines appear, filtering for `ERROR`
works, and the columnizer menu and Help/About work without exceptions.
2. Close LogExpert, then click **Yes** in the **LogExpert application check** dialog.
Click **No** if any application check failed. The script tests
silent uninstall and checks that the executable and uninstall entry disappear.
3. Copy `Desktop\LogExpert-test-results` from the guest to the host before closing
Sandbox. Files inside the guest are discarded when Sandbox closes.

If installation succeeded but a later check failed, keep that Sandbox open and
open `C:\SandboxTools` in the guest's File Explorer and double-click
`Resume-Test.cmd`. This opens PowerShell and resumes the checks. Alternatively:

```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\SandboxTools\Test-Manifest.ps1 -Resume
```

Resume skips WinGet setup and package installation, then checks the installed
application and tests removal. Its results go in a timestamped `resume-*` subfolder
of `Desktop\LogExpert-test-results`, preserving the original installation logs.
If the Sandbox has already been closed, open `LogExpert.wsb` for a fresh test.

If startup fails, click **No** in the check dialog and keep the guest open to inspect the exception and
logs. Successful manifest validation or installation does not establish that the
application works. To uninstall manually inside the guest:

```powershell
winget uninstall --product-code '{9C6E17B8-912C-45F9-9E7F-49CEAD8D6D7A}_is1' --silent --disable-interactivity
```

This is a clean installation test, not an upgrade test. Once the first version is
accepted into `microsoft/winget-pkgs`, also test upgrading from it before relying on
the release automation in PR #614. Submit only the three YAML files from `winget`
under `manifests/l/LogExperts/LogExpert/1.40.3/` in the community repository.
2 changes: 2 additions & 0 deletions tools/winget-sandbox/Resume-Test.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -NoExit -File "%~dp0Test-Manifest.ps1" -Resume
131 changes: 131 additions & 0 deletions tools/winget-sandbox/Test-Manifest.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Run only through LogExpert.wsb. All installations and logs stay in the guest.
param([switch]$Resume)

$ErrorActionPreference = 'Stop'
if ($env:USERNAME -ne 'WDAGUtilityAccount' -or -not (Test-Path C:\Manifest)) {
throw 'Run this script inside Windows Sandbox using LogExpert.wsb.'
}

$results = Join-Path $env:USERPROFILE 'Desktop\LogExpert-test-results'
if ($Resume) {
$results = Join-Path $results ('resume-' + (Get-Date -Format 'yyyyMMdd-HHmmss'))
}
New-Item -ItemType Directory -Path $results -Force | Out-Null
Start-Transcript -Path (Join-Path $results 'transcript.txt') -Force

function Invoke-WinGet {
Write-Host "Running: winget $args"
# Piping native output makes it visible to PowerShell's transcript as well as
# saving a separate command log (direct console output can be absent there).
& $script:winget @args | Tee-Object -FilePath (Join-Path $results 'winget-output.txt') -Append | Out-Host
$commandExitCode = $LASTEXITCODE
Write-Host ('WinGet exit code: {0} (0x{1:X8})' -f $commandExitCode, ($commandExitCode -band 0xffffffffL))
if ($commandExitCode -ne 0) {
throw "WinGet failed (exit code $commandExitCode): $args"
}
}

function Get-LogExpertRegistration {
$key = '{9C6E17B8-912C-45F9-9E7F-49CEAD8D6D7A}_is1'
foreach ($root in @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
)) {
Get-ItemProperty -LiteralPath "$root\$key" -ErrorAction SilentlyContinue
}
}

try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ProgressPreference = 'SilentlyContinue'
if (-not $Resume) {
Write-Host 'Installing WinGet in Windows Sandbox...'
Install-PackageProvider -Name NuGet -Force | Out-Null
Install-Module -Name Microsoft.WinGet.Client -Repository PSGallery -Force -Scope AllUsers
Import-Module Microsoft.WinGet.Client
Repair-WinGetPackageManager -AllUsers
}

$script:winget = Join-Path $env:LOCALAPPDATA 'Microsoft\WindowsApps\winget.exe'
if (-not (Test-Path $script:winget)) {
$script:winget = Join-Path (Get-AppxPackage Microsoft.DesktopAppInstaller).InstallLocation 'winget.exe'
}
Invoke-WinGet --version
Invoke-WinGet --info
if (-not $Resume) {
Invoke-WinGet settings --enable LocalManifestFiles
Invoke-WinGet validate --manifest C:\Manifest
Invoke-WinGet show --id Microsoft.DotNet.DesktopRuntime.10 --exact --source winget --accept-source-agreements --disable-interactivity
# Let the manifest install its dependency; do not preinstall .NET.
Invoke-WinGet install --manifest C:\Manifest --dependency-source winget --silent --accept-package-agreements --accept-source-agreements --disable-interactivity --verbose-logs
}

$registration = @(Get-LogExpertRegistration)
if ($registration.Count -ne 1) {
throw "Expected one machine-wide LogExpert uninstall entry, found $($registration.Count)."
}
$registration | Select-Object DisplayName, DisplayVersion, Publisher, InstallLocation, PSChildName |
Format-List | Out-String | Tee-Object -FilePath (Join-Path $results 'registration.txt') | Write-Host
$expectedVersion = (Select-String -Path C:\Manifest\LogExperts.LogExpert.yaml -Pattern '^PackageVersion:\s*(.+)$').Matches.Groups[1].Value.Trim()
if ($registration[0].DisplayVersion -ne $expectedVersion) {
throw "Installed version does not match manifest version $expectedVersion."
}
# Inno registers a versioned display name. Query installed applications without
# requiring an exact unversioned name or membership in the published catalog.
# The registry checks above verify the precise product code and version.
Invoke-WinGet list --name LogExpert --accept-source-agreements --disable-interactivity
$dotnet = Join-Path $env:ProgramFiles 'dotnet\dotnet.exe'
$runtimes = & $dotnet --list-runtimes
$runtimes | Tee-Object -FilePath (Join-Path $results 'runtimes.txt') | Write-Host
if ($LASTEXITCODE -ne 0 -or -not ($runtimes -match '^Microsoft.WindowsDesktop.App 10\.')) {
throw 'The x64 .NET 10 Desktop Runtime is missing.'
}
$exe = Join-Path $registration[0].InstallLocation 'LogExpert.exe'
$sample = Join-Path $results 'sample.log'
@('2026-09-06 INFO Sandbox installation completed', '2026-09-06 ERROR Sample error for filtering') |
Set-Content -Path $sample
Start-Process -FilePath $exe -ArgumentList ('"{0}"' -f $sample)
Write-Host 'Check that LogExpert opens the sample log, displays both lines, and can filter for ERROR.'
Write-Host 'Also check the columnizer menu and Help/About. An installer success alone is not an application pass.'
# Sandbox logon may not provide usable console input. A dialog waits for an
# explicit result independently of PowerShell's console visibility/stdin.
$dialogs = New-Object -ComObject WScript.Shell
do {
$message = "Check the sample log, filtering for ERROR, the columnizer menu, and Help/About.`r`n`r`nClose LogExpert when finished, then click Yes if all checks passed, or No if something failed."
# Yes/No, question icon, default No, bring to foreground; no timeout.
$answer = $dialogs.Popup($message, 0, 'LogExpert application check', 4 + 32 + 256 + 65536)
Write-Host "Manual application check dialog result: $answer (6 = Yes)"
if ($answer -ne 6) { throw 'Manual application smoke test was not confirmed as PASS.' }
$stillRunning = @(Get-Process LogExpert -ErrorAction SilentlyContinue).Count -gt 0
if ($stillRunning) {
[void]$dialogs.Popup('LogExpert is still running. Close it before confirming the check.', 0, 'Close LogExpert', 48 + 65536)
}
} while ($stillRunning)
Invoke-WinGet uninstall --product-code '{9C6E17B8-912C-45F9-9E7F-49CEAD8D6D7A}_is1' --silent --disable-interactivity
if (@(Get-LogExpertRegistration).Count -ne 0 -or (Test-Path $exe)) {
throw 'Uninstall left the application executable or uninstall registration behind.'
}
if ($Resume) {
Write-Host 'PASS: resumed registration, runtime, manual application check, and uninstall. See the original run for installation results.' -ForegroundColor Green
}
else {
Write-Host 'PASS: install, dependency, registration, manual application check, and uninstall.' -ForegroundColor Green
}
}
catch {
Write-Host "FAIL: $($_.Exception.Message)" -ForegroundColor Red
$_ | Out-String | Set-Content (Join-Path $results 'failure.txt')
}
finally {
$logLocations = @{
'WinGet-logs' = Join-Path $env:LOCALAPPDATA 'Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir'
'WinGet-temp-logs' = Join-Path $env:TEMP 'WinGet'
}
foreach ($entry in $logLocations.GetEnumerator()) {
if (Test-Path $entry.Value) {
Copy-Item -Path $entry.Value -Destination (Join-Path $results $entry.Key) -Recurse -Force
}
}
Write-Host "Results: $results. Copy them to the host before closing Windows Sandbox."
Stop-Transcript
}
4 changes: 3 additions & 1 deletion winget/LogExperts.LogExpert.installer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
PackageIdentifier: LogExperts.LogExpert
PackageVersion: 1.40.3
InstallerType: inno
Scope: machine
ProductCode: '{9C6E17B8-912C-45F9-9E7F-49CEAD8D6D7A}_is1'
Dependencies:
PackageDependencies:
- PackageIdentifier: Microsoft.DotNet.DesktopRuntime.10
Expand All @@ -13,4 +15,4 @@ Installers:
InstallerSha256: f32bde3703da1db0c1a963d37ee0eeab029bb18cbe3b8730ea12eda0eaafe2fa
ManifestType: installer
ManifestVersion: 1.10.0
ReleaseDate: 2026-06-12
ReleaseDate: 2026-06-15
Loading