# 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 -- <args>
```

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/<Name>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`).
