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

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 ] <tag> = <value> (q=<quality>, t=<ts>)` or `[ERR] <tag>: <reason>` |
| Human (subscribe) | default | One line per event: `[<HH:mm:ss.fff>] [OK ] <tag> = <value> (q=<quality>)` |
| 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/<Name>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.
