# AGENTS.md Guidance for coding agents working in the `aalogcli` folder. ## Project Snapshot This folder contains `aalogcli` (assembly name `aalog`), a `.NET Framework 4.8 / x86` CliFx-based CLI for reading AVEVA / Wonderware System Platform binary log files (`*.aaLGX`). It wraps the third-party reader library [`aaOpenSource/aaLog`](https://github.com/aaOpenSource/aaLog) and exposes it as `last`, `tail`, `range`, `unread`, and `fields` commands tailored for LLM-driven debugging (stable JSON envelope, bounded payloads, post-fetch filters). For end-to-end command reference and examples, read [`docs/usage.md`](docs/usage.md). For the JSON shape of an emitted record, read [`docs/fields.md`](docs/fields.md). For the upstream reader's API (used by [`LogReaderFactory.cs`](src/AaLog.Cli/LogReaderFactory.cs) and the commands), see the [aaLog README](https://github.com/aaOpenSource/aaLog). ## Key Documentation All paths are relative to this `aalogcli/` folder. - [`README.md`](README.md) — tool entry point, hard constraints, build instructions. - [`docs/usage.md`](docs/usage.md) — every command, every option, with worked examples for the LLM-JSON envelope. - [`docs/fields.md`](docs/fields.md) — `LogRecordDto` field reference; the canonical shape of records in `--llm-json` output. ## Repository Layout ```text aalogcli/ AaLog.Cli.slnx lib/aaLogReader.dll (provisioned out-of-band — see README) src/AaLog.Cli/ AaLog.Cli.csproj Program.cs LogReaderFactory.cs Commands/ CommonOptions.cs (ReadCommandBase — shared options) LastCommand.cs (`last` — last N records) TailCommand.cs (`tail` — last N minutes) RangeCommand.cs (`range` — explicit start/end) UnreadCommand.cs (`unread`— incremental, cache-backed) FieldsCommand.cs (`fields`— field reference printout) Output/ LogRecordDto.cs (LLM-friendly subset of aaLogReader.LogRecord) OutputWriter.cs (human single-line + llm-json envelope) Filtering/ RecordFilter.cs (substring / regex over Component, Level, Message) IsExternalInit.cs (C# 9 `init` polyfill for net48) ``` ## Build And Test Run commands from this `aalogcli` folder unless noted otherwise. ```powershell dotnet build src/AaLog.Cli/AaLog.Cli.csproj -p:Platform=x86 -c Release dotnet run --project src/AaLog.Cli/AaLog.Cli.csproj -- ``` There is no test project at present. If you add one, mirror `graccesscli`'s convention: `tests/AaLog.Cli.Tests/AaLog.Cli.Tests.csproj`, `net48` / `x86`, xunit + Shouldly. ## Implementation Rules - Keep the CLI targeting `net48` and `x86`. The upstream `aaLogReader` is net40 — net48 loads it cleanly, .NET 10 / x64 will not. - Do **not** add a `[STAThread]` apartment requirement. Unlike `graccesscli`, `aaLogReader` is plain managed file I/O and runs fine on any thread. - Construct readers through [`LogReaderFactory.Open`](src/AaLog.Cli/LogReaderFactory.cs) so every command honors `--log-dir` the same way. - Always wrap the reader in `using` — it implements `IDisposable` and holds a `FileStream`. - Treat the upstream library as authoritative for log decoding. Do not reimplement aaLGX parsing in this CLI. - Filtering is **client-side**, after the library returns records. Do not push filter strings into `OptionsStruct.LogRecordPostFilters` from the CLI layer — surface stays flatter that way. - Every read command must inherit [`ReadCommandBase`](src/AaLog.Cli/Commands/CommonOptions.cs) so `--log-dir` / `--component` / `--level` / `--message` / `--regex` / `--llm-json` stay consistent. - `--llm-json` envelope shape is `{ query: {...}, count: N, records: [LogRecordDto, ...] }`. Preserve the envelope contract — agents may parse it positionally. - `LogRecordDto` is the JSON contract. Adding a field is a non-breaking change; renaming or removing one is breaking — bump the docs in the same commit and call it out. - C# language version is 9.0. `init` is supported through `IsExternalInit.cs`; do not use the `required` keyword. - Use CliFx command patterns: `[Command]`, `[CommandOption]`, classes implement `ICommand`. ## Output Contracts | Mode | Trigger | Shape | | --- | --- | --- | | Human | default | One line per record: `[localTs] [level] component (process#pid/tid) \| message` | | LLM-JSON | `--llm-json` | `{ "query": {...}, "count": N, "records": [LogRecordDto, ...] }` | The `query` object echoes the invocation parameters so an agent reading the JSON can confirm which window it actually got. `log_dir` in the query is the **resolved** directory (post-`--log-dir` override), so it doubles as a sanity check. ## Adding A New Command 1. Add `Commands/Command.cs` inheriting `ReadCommandBase` and implementing `ICommand`. 2. If it introduces a new query verb (e.g. `since-message-number`), wrap the corresponding `aaLogReader` method in `LogReaderFactory` or inline; document the verb in [`docs/usage.md`](docs/usage.md). 3. Add a row in [`README.md`](README.md) and [`docs/usage.md`](docs/usage.md). Do not modify `../CLAUDE.md` — the root index points at this README and that is sufficient. 4. Keep `--llm-json` envelope semantics identical across commands (`query`, `count`, `records`).