# AGENTS.md Guidance for coding agents working in the `mxaccesscli` folder. ## Project Snapshot `mxaccesscli` (assembly name `mxa`) is a `.NET Framework 4.8 / x86` CliFx CLI that wraps `ArchestrA.MxAccess.LMXProxyServerClass` to read, write, and subscribe to AVEVA System Platform tags. Output as human-readable lines or as a stable `{ query, ok, results }` JSON envelope under `--llm-json`. For end-to-end usage and examples, read [`docs/usage.md`](docs/usage.md). For the underlying MxAccess COM API surface (assembly is shipped without public documentation), read [`docs/api-notes.md`](docs/api-notes.md). ## Key Documentation All paths relative to this `mxaccesscli/` folder. - [`README.md`](README.md) — tool entry point, hard constraints, build instructions. - [`docs/usage.md`](docs/usage.md) — every command, every option, examples, JSON envelope shape. - [`docs/api-notes.md`](docs/api-notes.md) — reverse-engineered MxAccess API reference (types, events, threading model, status semantics). ## Repository Layout ```text mxaccesscli/ MxAccess.Cli.slnx lib/ArchestrA.MxAccess.dll (copied from System Platform's Framework\Bin) src/MxAccess.Cli/ MxAccess.Cli.csproj Program.cs [STAThread] entry; wires CliFx commands IsExternalInit.cs net48 polyfill for C# 9 init accessors Commands/ ReadCommand.cs `read` — first OnDataChange per tag, timed WriteCommand.cs `write` — write + OnWriteComplete, timed SubscribeCommand.cs `subscribe` — stream OnDataChange for N seconds InfoCommand.cs `info` — assembly identity + value-type catalog Mx/ MxSession.cs Register/Unregister + event pump + queue MxItem.cs AddItem / Advise / UnAdvise / RemoveItem pairing MxUpdate.cs typed wrapper over OnDataChange / OnWriteComplete ValueCoercion.cs --type bool|int|double|... → boxed object for Write Output/ Envelope.cs { query, ok, results } JSON shape ``` ## Build And Test Run from this `mxaccesscli` folder. ```powershell dotnet build src/MxAccess.Cli/MxAccess.Cli.csproj -p:Platform=x86 -c Release dotnet run --project src/MxAccess.Cli/MxAccess.Cli.csproj -- ``` There is no test project. If you add one, mirror `graccesscli`'s pattern: `tests/MxAccess.Cli.Tests/`, `net48`, `x86`, xunit + Shouldly. Pure unit-testing of `MxSession` is hard because `LMXProxyServerClass` is sealed and COM-bound; use a thin `IMxSession` interface if you need test seams. ## Implementation Rules - **Stay on `net48` / `x86` / `[STAThread]`.** MxAccess is a 32-bit COM proxy and events fire on the apartment that called `Register()`. Crossing apartments will silently lose callbacks. - **Always pair Register / Unregister, AddItem / RemoveItem, Advise / UnAdvise.** `MxSession.Dispose` and `MxItem.Dispose` already do this — leverage `using` blocks. - **Reads are not synchronous in MxAccess.** Implement "read" as a brief Advise → first OnDataChange → tear down. Document the timeout semantic in user-facing surfaces. - **Surface `MxStatusCategory` on every error.** A non-Ok / non-Pending category in any element of `MXSTATUS_PROXY[]` means failure. Don't collapse the array — emit it whole in the JSON envelope so an agent can tell *why* something failed. - **`Write` uses an `int userId`.** Use 0 for unauthenticated operations; for secured attributes call `AuthenticateUser()` first (the CLI does not yet expose this — add it through `WriteCommand` if needed and document it). - **Don't reuse `LMXProxyServerClass` instances across runs.** Each `MxSession` constructs and disposes one. The proxy is cheap enough that one-shot CLI invocations are fine; for many writes back-to-back, build a session-mode subcommand modeled on `graccesscli`. - **CliFx command pattern.** Commands implement `ICommand`, decorate with `[Command]`, parameters with `[CommandParameter]`, options with `[CommandOption]`. C# 9 `init` accessors via `IsExternalInit.cs` polyfill; no `required`. - **JSON envelope contract.** Every `--llm-json` command emits `{ query: {...}, ok: bool, results: [...] }`. `query` echoes the resolved invocation (including coerced value types). `results` is always an array, even for single-tag commands. Adding fields to a `result` is non-breaking; renaming/removing is. ## Output Contracts | Mode | Trigger | Shape | | --- | --- | --- | | Human (read/write) | default | One line per tag: `[OK ] = (q=, t=)` or `[ERR] : ` | | Human (subscribe) | default | One line per event: `[] [OK ] = (q=)` | | LLM-JSON (read/write) | `--llm-json` | `{ query, ok, results: [ { tag, ok, value?, quality?, timestamp?, statuses, error? } ] }` | | LLM-JSON (subscribe) | `--llm-json` | **JSON Lines** — one JSON object per OnDataChange event, no envelope wrapper. Allows piped consumption. | The subscribe-stream JSON Lines format is intentionally different from the read/write envelope — streams need to be consumable line-by-line. ## Adding A New Command 1. Add `Commands/Command.cs` implementing `ICommand`. 2. Construct an `MxSession` in a `using` block; build `MxItem` instances; dispose them before `MxSession`. 3. Use `MxSession.WaitForUpdate(predicate, timeout, out update)` for one-shot waits, or `DrainUpdates()` + `WaitOnce()` in a loop for streams. 4. Add a row in [`README.md`](README.md) and a section in [`docs/usage.md`](docs/usage.md). Do not modify `../CLAUDE.md` — the root index points at the README and that is sufficient. 5. Keep envelope shape `{ query, ok, results }` consistent across read/write commands.