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
9 changes: 9 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@
# (Pattern has no slash so it matches the file regardless of path — its directory
# "Solution Items" contains a space, which .gitattributes cannot express in a path.)
usedComponents.json 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
# fixtures. Mark binary to store and check out the exact bytes.
LineEndings_LF.txt binary
LineEndings_CRLF.txt binary
LineEndings_CR.txt binary
LineEndings_Mixed.txt binary
61 changes: 56 additions & 5 deletions .github/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ LogExpert is a Windows log file viewer and analyzer built with C# and Windows Fo
- NUnit for testing
- Plugin-based architecture

## Environment

This is a Windows .NET/WinForms repo. Python is often unavailable — prefer PowerShell, `tshark`, and built-in tooling. Check whether `gh` CLI (and any other external tool) is actually installed before building a plan around it. The active GitHub account is `brunerpat`.

### Line Endings

The working tree is CRLF on Windows (only `usedComponents.json` is pinned to LF via [.gitattributes](.gitattributes); there is no global normalization). Never use `sed`-based or other text-tool renames that strip line endings — they produce a whole-file diff of pure line-ending churn. After any bulk rename, verify no references were left unrenamed (a missed reference will break the build).

## Testing

Follow TDD for all bug fixes: write a failing test that reproduces the issue first, then fix, then verify the **full suite** passes — not just the new test in isolation (timing/filesystem tests can pass alone but fail alongside the suite).

## Documentation

Docs must be code-grounded and reconciled against the actual source and live config, then reader-tested before delivery. Don't document from assumption.

## Refactoring

For "behavior-preserving" refactors, explicitly confirm equivalence and flag any unintended logic changes before applying.

## Build Commands

### Using Nuke Build (Recommended)
Expand All @@ -31,6 +51,15 @@ LogExpert is a Windows log file viewer and analyzer built with C# and Windows Fo
./build.ps1 --target Clean Pack CreateSetup --configuration Release
```

#### How the Nuke build works

- `build.ps1` bootstraps and runs the Nuke build defined in [build/Build.cs](build/Build.cs). Targets and their dependencies are declared there in C#.
- **Default target is `Test`** (which depends on `Compile`), so a bare `./build.ps1` restores, compiles, and runs the tests.
- Select targets with `--target <Name>` (space-separated for multiple). Common targets: `Clean`, `Restore`, `Compile`, `Test`, `Pack`, `CreateSetup`, `GeneratePluginHashes`, `Publish`.
- List all available targets: `./build.ps1 --help`
- Preview the execution plan without running it: `./build.ps1 --plan`
- Pick build configuration with `--configuration Debug|Release` (defaults to `Debug` locally, `Release` on CI).

### Using .NET CLI Directly

```bash
Expand Down Expand Up @@ -144,7 +173,7 @@ LogExpert/
- Use localization resources from `LogExpert.Resources` project
- Windows Forms designer files: `*.designer.cs`

### Testing
### Test Setup & Conventions

- Unit tests use NUnit framework with Moq for mocking
- Test projects follow naming pattern `*.Tests`
Expand All @@ -156,7 +185,7 @@ LogExpert/

### Code Style
- Nullable reference types enabled (`<Nullable>enable</Nullable>`)
- Comprehensive `.editorconfig` with 4000+ rules
- `.editorconfig` is the source of truth for formatting and analyzer severities — match it rather than imposing your own style; don't fight or override its rules
- ImplicitUsings enabled
- Assembly signing enabled (Key.snk)

Expand All @@ -174,9 +203,9 @@ LogExpert/

### Git Workflow
- Default branch: `Development` (use for PRs)
- Commit format: Include "Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
- Commit format: **Do not** add a `Co-Authored-By` trailer (or any other agent/AI attribution) to commit messages
- GitHub Actions run on push to Development branch
- AppVeyor for CI builds and artifact creation
- AppVeyor is the second CI for builds and artifact creation. It is currently disabled and needs to be re-added alongside GitHub Actions

## Plugin Security System

Expand All @@ -198,6 +227,15 @@ LogExpert/
6. **Encoding detection**: BOM-less files default to encoding from EncodingOptions
7. **Plugin hashes**: Only verified in Release builds; Debug builds skip verification

## Dont Do that

Hard rules — never do these:

- **Don't add a `Co-Authored-By` trailer** (or any other agent/AI attribution) to commit messages.
- **Don't attempt Linux/macOS builds.** This is Windows-only (Windows Desktop SDK + Windows Forms).
- **Don't use `AutoScaleMode` or `AutoScaleDimensions` on individual controls** — only on forms (High DPI).
- **Don't override or fight `.editorconfig` rules** — it is the source of truth for formatting and analyzer severities.

## Key Dependencies

- **NLog**: Logging framework
Expand All @@ -212,10 +250,23 @@ LogExpert/
- Main README: [README.md](README.md)
- Plugin Development: [PLUGIN_DEVELOPMENT_GUIDE.md](src/docs/PLUGIN_DEVELOPMENT_GUIDE.md)
- Plugin Hash System: [PLUGIN_HASH_MANAGEMENT.md](src/docs/PLUGIN_HASH_MANAGEMENT.md)
- Performance Benchmarks: [BENCHMARK_SUMMARY.md](src/docs/performance/BENCHMARK_SUMMARY.md)
- GitHub Wiki: https://github.com/LogExperts/LogExpert/wiki
- Discord: https://discord.gg/SjxkuckRe9

## Agent skills

### Issue tracker

Issues are tracked in `LogExperts/LogExpert` GitHub Issues, read-only for skills via the `gh` CLI — analysis only; only humans post. See `docs/agents/issue-tracker.md`.

### Triage labels

Canonical triage roles map to repo labels (`needs-info` → `question`; the rest use default names), used to read/filter issues — skills don't apply labels. See `docs/agents/triage-labels.md`.

### Domain docs

Single-context: one `CONTEXT.md` + `docs/adr/` at the repo root. See `docs/agents/domain.md`.

# Update Rules File
To update this file, ensure that all sections are kept current with the latest architectural decisions, build processes, and development workflows. Follow these guidelines:
- Review and update build commands if there are changes in the build system.
Expand Down
155 changes: 155 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# LogExpert — Context & Ubiquitous Language

Glossary of domain terms used in this codebase. Terms here have a single, agreed
meaning; do not redefine them locally.

## Highlighting & triggers

- **Highlight Entry** (`HighlightEntry`) — One search-text rule plus the visual
styling and trigger flags applied to lines that match it. Belongs to exactly
one Highlight Group.
- **Highlight Group** (`HighlightGroup`) — A named, ordered list of Highlight
Entries. The Log Window evaluates lines against the currently selected group.
- **Trigger** — A side-effecting action attached to a Highlight Entry that fires
when a line matches. Current triggers: Set Bookmark, Stop Tail, Don't Dirty
LED, Plugin (keyword action), and Audio Alert.
- **Action Entry** (`ActionEntry`) — Plugin name + parameters bound to the
Plugin trigger of a Highlight Entry.
- **Tail trigger path** — The single code path in `LogWindow.CheckFilterAndHighlight`
that evaluates highlight entries against *newly appended* lines. Triggers
that have user-perceivable side effects (currently: Audio Alert) fire **only**
on this path; bulk/scanner paths intentionally skip them.

## Audio alerts

- **Audio Alert** — A sound played when a tail-only highlight match occurs.
Toggled per Highlight Entry via `AlertOnHit`.
- **Sound File** (`SoundFilePath`) — Per-entry absolute path to an audio file
(WAV/MP3/WMA/AIFF, anything NAudio can decode). Empty path means "default
system beep". A missing or unreadable file falls back silently to the system
beep and is logged at warn level.
- **Cooldown** (`CooldownSeconds`) — Per-entry minimum seconds between two
audio alerts. `0` disables throttling.
- **Active Cooldown** — The cooldown value of the *most recently played* alert.
It gates every subsequent attempt across the whole application until it
expires, regardless of which entry's cooldown was passed in for the new
attempt. See ADR 0001.
- **Audio Player** (`LogExpert.Audio.AudioPlayer`) — Static, process-wide
fire-and-forget player. Owns the single global last-played timestamp and
active-cooldown state.

## Sessions

- **Session** (`.lxj`) — Persisted workspace: a list of log file paths plus a
tab/window layout XML blob. Loading a session reopens those files in their
saved arrangement. Per-file state (bookmarks, filters, columnizer, …) is
*not* in the session — it loads independently from each file's **Session
File** (`.lxp`). A session is created or restored only by explicit user
action ("Save Project" / "Load Project" menu items, to be renamed to
Session terminology).
- **Session File** (`.lxp`) — JSON file saved beside (or in a central
directory next to) a log file, restoring per-file state on reload:
columnizer name, bookmarks, filter pipes, highlight group, multi-file
state, follow-tail, encoding. Created automatically on save; one per log
file. Composed by a Session via its `FileNames` list. The columnizer
field of a Session File is one of several sources that can be selected
by **Columnizer Selection Priority**; the other fields always load when
the Session File exists.

*Avoid*: "project" / "project file" / "workspace" (use **Session**),
"persistence file" / "per-file persistence" (use **Session File**),
bare "session file" when you mean the workspace (that's a **Session**).

## Control character display

- **Control Character Substitution** — Display-only replacement of selected
control characters with visible glyphs, performed inside `LogWindow.PaintCell`.
Never modifies raw column data, never affects search, filter, highlight
regex, or columnizer parsing. Off by default. See ADR 0003.
- **Substitution Style** — One of five rendering modes applied globally to
every enabled control character: Caret notation (`^G`), C escape (`\a` /
`\xNN`), Abbreviation (`BEL`), Unicode Control Pictures (`␇`), ISO 2047
(`⍾`). Default: Control Pictures.
- **Enabled Code Points** (`ControlCharSettings.EnabledCodepoints`) — The
subset of C0 + DEL the user has opted in to substituting. First-time
default is the "non-whitespace preset": all 33 characters except `HT`,
`LF`, `CR`.
- **Substitution Style Fallback** — Per-style rule for characters the style
has no defined glyph for. C escape falls back to `\xNN`; ISO 2047 falls
back to the Control Pictures glyph; the other three styles cover all 33
in-scope characters.
- **Copy Displayed Form** (`ControlCharSettings.CopyDisplayedForm`) —
Opt-in setting that makes clipboard copy (and other "export selection"
paths) use the substituted text instead of the raw bytes. Default: off,
i.e. clipboard always carries raw data.

*Avoid*: "control char rendering" (use **Substitution**), "escape" alone
(ambiguous between the **C escape** style and the general concept).

## Columnizer selection

- **Columnizer** (`ILogLineMemoryColumnizer`) — A plugin that parses a log
line into columns. Each loaded log window has exactly one active
columnizer at a time. The set of available columnizers is owned by
`PluginRegistry`.
- **Columnizer Mask Entry** (`ColumnizerMaskEntry`) — One user-configured
row on the Settings → Columnizers tab. Pairs a **Mask**, a **Mask Type**,
and a **Columnizer Name**. Stored in `Preferences.ColumnizerMaskList`.
- **Mask Type** — `Glob` or `Regex`. Glob uses `*` and `?` wildcards and
matches against the short file name; Regex uses .NET regular-expression
syntax. New rows default to `Glob`; rows that existed in `settings.json`
before this field was introduced deserialize as `Regex` for backward
compatibility. See ADR 0004.
- **Stale Mask Entry** — A Columnizer Mask Entry whose `ColumnizerName`
does not resolve to any currently-registered columnizer. Stale entries
are skipped at match time (the loop continues to the next entry), kept
in the settings file (the plugin may return), and flagged in the
Settings dialog with a leading warning icon.
- **Columnizer History** — Auto-maintained list (`Settings.ColumnizerHistoryList`,
capped at 40 entries) recording the columnizer last used per absolute
file path. Stale entries (columnizer no longer registered) are removed
on lookup; this list, unlike the Mask list, is not user-curated.
- **Columnizer Selection Priority** (`ColumnizerSelectionPriority` enum) —
The user-configured rule that orders the four sources of a "which
columnizer should this file open with?" decision: Session File,
Columnizer History, Columnizer Mask Entry, and AutoPick. Three modes,
mutually exclusive:
- `HistoryThenMask` *(default; today's behaviour)* — Session File →
History → Mask → AutoPick → built-in default.
- `MaskThenHistory` — Session File → Mask → History → AutoPick → default.
- `MaskOverridesPersistence` — Mask → Session File → History → AutoPick
→ default. The only mode in which a matching mask outranks an
existing Session File. Only the columnizer field of the Session File
is overridden; bookmarks, filters, etc. still restore.

See ADR 0005. Replaces the deprecated `Preferences.MaskPrio` bool.
(Note: the enum member name `MaskOverridesPersistence` retains the old
"Persistence" wording for backward compatibility of serialized settings;
the user-facing concept is **Session File**.)
- **AutoPick** (`Preferences.AutoPick`) — When on, runs
`ColumnizerPicker.FindBetterMemoryColumnizer` against the loaded file
content to auto-detect a columnizer. Fires only when the Columnizer
Selection Priority chain above produced no result — never overrides
an explicit Mask, History, or Session File hit.

*Avoid*: "file mask" alone (ambiguous between glob and regex — say
**Glob Mask** or **Regex Mask**), "mask priority" (use **Columnizer
Selection Priority**).

## Flagged ambiguities

- "**session file**" was used by old UI resource keys (e.g.
`LoadProject_UI_Message_Error_Title_FailedToUpdateSessionFile`) to mean
the workspace (`.lxj`). Resolved: from now on **Session File** = `.lxp`
(per-file state) and **Session** = `.lxj` (workspace). Existing resource
keys named `*SessionFile*` today refer to the workspace and need
renaming.
- "**project**" / "**project file**" historically referred to `.lxj`.
Resolved: use **Session**. The `ProjectFileHandler` /
`ProjectPersister` type names and `OnLoadProjectToolStripMenuItemClick`
handler are scheduled for renaming.
- "**Per-file Persistence**" was the prior term for `.lxp`. Resolved:
use **Session File**. The enum member
`ColumnizerSelectionPriority.MaskOverridesPersistence` keeps its old
name for serialization compatibility — internal name only, not a
domain term.
Loading