Skip to content
Closed
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
15 changes: 15 additions & 0 deletions .agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Portable agent skills

Project skills live here in the open
[Agent Skills](https://agentskills.io/) format. Each subdirectory contains a
`SKILL.md` with YAML frontmatter (`name`, `description`) and instructions.

```
.agents/skills/<skill-name>/SKILL.md
```

Do not add parallel copies under tool-specific paths (`.cursor/skills/`,
`.claude/skills/`, etc.); keep a single tree under `.agents/skills/`.

Always-on project guidance for all agents: [`AGENTS.md`](../AGENTS.md) at the
repository root. Changelog encoding: skill `edg-changes-example`.
88 changes: 88 additions & 0 deletions .agents/skills/acknowledg-bench/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
name: acknowledg-bench
description: >-
Reads Cachegrind .cgout Ir totals, compares benchmark baselines to runs,
and supports AcknowlEDG bench review / update-baseline flows. Use when
editing edg-bench-run-delta, edgacknowledg.client.bench, .cgout summary
parsing, or benchmark UI ops fields.
---

# AcknowlEDG / Cachegrind benchmarks

## Metric

**Ir** (instruction reads) is the first `summary:` integer in a `.cgout`.
CLIs and the UI call it **OPs**. Lower run Ir vs baseline = faster.

```text
events: Ir I1mr ILmr Dr ...
...
summary: 3696685 6148 4719 ...
```

With cache-sim enabled there are multiple counts; **always use the first**
(Ir). Do not sum fields or take a later column.

Shared regex (keep both call sites in sync):

```python
_REGEX_CGOUT_SUMMARY = re.compile(r'^summary: ([0-9]+)(?: [0-9]+)*\n$')
```

## Readers

| Location | Function |
| --- | --- |
| [`dev_tools/bin/edg-bench-run-delta`](../../../dev_tools/bin/edg-bench-run-delta) | `read_program_totals` |
| [`dev_tools/pylibs/edgacknowledg/client/bench/__init__.py`](../../../dev_tools/pylibs/edgacknowledg/client/bench/__init__.py) | same |

Readers scan the last ~10 lines for `summary:`. Missing/malformed → skip that
benchmark. Do not invent a third independent parser.

Percent change:

```text
((baseline - run) / max(1, baseline)) * 100
```

## Layout

Bench home: `$EDG_BENCH_HOME` or `<repo>/benchmarks`.

| Path | Role |
| --- | --- |
| `benchmarks/benchmarks/**/*.bnch.cpp` | Sources |
| `benchmarks/runs/<YYYY.MM.DD-HH.MM.SS>/…/*.bnch.cgout` | Runs |
| `benchmarks/baselines/project/<tag>/` | Project baselines |
| `benchmarks/baselines/user/<name>/` | User baselines (`@name` tag) |
| `benchmarks/.acknowledg-cache/` | `cg_annotate` disk cache |

Relative `.cgout` paths must exist under both run and baseline dirs to compare.

## Review / UI principles

Wire payloads should carry raw `baseline_ops` / `run_ops` (and baseline tag
when needed). Derive in the client:

- op delta, percent change, faster/slower badges
- mean / median / population stdev over the list
- sort order

Do **not** ship redundant counts, samples arrays, or precomputed change
comments when the UI already has the ops list.

Bench annotate detail uses `cg_annotate --no-annotate --diff` (delta output,
not a line-oriented source diff).

## Update baseline

Bench editor copies the selected run `.cgout` into the session’s baseline tag
directory and refreshes statuses (`BenchEditHandler` /
`edg-bench-review`).

## Protocol

Tags `bench.src.*` / `bench.edit.*`: see
[PROTOCOL.md](../../../dev_tools/services/acknowledg/PROTOCOL.md).
Client architecture: skill **acknowledg-client**.
Python under `dev_tools/`: skill **edg-python-style**.
114 changes: 114 additions & 0 deletions .agents/skills/acknowledg-client/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
name: acknowledg-client
description: >-
Works on the AcknowlEDG Python client: BatchClient vs DirectClient,
ReviewClient Protocol, resilient websocket reconnect, and test/bench
provider handlers. Use when editing edgacknowledg.client, edgy-review,
edg-bench-review, edg-acknowledg-cli, or section subscribe/reconnect logic.
---

# AcknowlEDG client

Canonical package: [`dev_tools/pylibs/edgacknowledg/`](../../../dev_tools/pylibs/edgacknowledg/).
`dev_tools/services/acknowledg/server/edgacknowledg` is a **symlink** — edit
pylibs only.

Wire protocol (tags, routes, auth, occupancy):
[PROTOCOL.md](../../../dev_tools/services/acknowledg/PROTOCOL.md). Keep
`PROTOCOL_REVISION` in sync with
`dev_tools/services/acknowledg/client/src/sockets.js`.

## Public API

From `edgacknowledg.client` (`__all__`):

- `BatchClient` / `DirectClient` — async context managers; constructor takes
endpoint settings and optional `status_listener`
- `BatchClientSession` / `DirectClientSession` — yielded by client `__aenter__`;
`create_review` / `remove_review`, `close()`, `stopped()`, `start()`, `stop()`
(client `__aexit__` awaits `stopped()` then `stop()`)
- `ConnectionStatusListener` — `connected` / `attempting_reconnect` /
`disconnected`
- `ReviewClient` (structural **Protocol**): `review_id`, `name`, `timestamp`,
`sections()`, `add_section` / `remove_section`
- URL/env helpers: `add_endpoint_arguments`, `form_section_http_url`,
`default_*` host/port helpers
- Shared: `BackgroundWorkerPool`, errors

Usage:

```python
async with BatchClient(
api_host=..., api_port=..., status_listener=listener
) as session:
review = await session.create_review(...)
# Block ends when session.stop() is called, or on cancellation.
```

Private (leading `_`): `_ClientReview`, `_BatchClientReview`,
`_DirectClientReview`, channel helpers. Callers should type against
`ReviewClient`, not private classes.

## Batch vs Direct

| | Batch | Direct |
| --- | --- | --- |
| Socket | One resilient `/batch` | Per-section `/source|edit/<uuid>/<kind>/<slug>` |
| Mux | `batch.subscribe` / `unsubscribe` / `event` | Dedicated WS per section |
| CLI | `edg-acknowledg-cli` | `edgy-review`, `edg-bench-review` |
| Reconnect | Drop local subs, `_restore_subscriptions()` | Each section's `ResilientWebsocket` loop |

Direct URL mapping uses handler `subscription_type` (e.g. `test.src` →
`/source/.../test/<slug>`, `bench.edit` → `/edit/.../bench/<slug>`).

## Reconnect

[`client/websocket.py`](../../../dev_tools/pylibs/edgacknowledg/client/websocket.py):
`ResilientWebsocket`, exponential backoff + jitter on connect failure;
jitter-only delay after a drop. Pattern:

```python
async for websocket in ResilientWebsocket(...):
try:
...
except ConnectionClosed:
continue
```

Auth header: `X-AcknowlEDG-API-Key` (never put the key in query strings).
`RoleOccupied`: discard that handler / stop reconnecting that section; keep
other sections running.

## Batch subscription maps

`_BatchClientReview` tracks:

- `_subscriptions: Dict[int, uuid.UUID]` — `id(handler)` → server subscription id
- `_subscription_tasks: Dict[uuid.UUID, asyncio.Task]` — subscription id → task

Unsubscribe needs the server id; the handler-key map provides it after
reconnect bookkeeping.

## Providers

| Package | Handlers |
| --- | --- |
| `client/test/__init__.py` | `TestSourceHandler` (`test.src`), `TestEditHandler` (`test.edit`) |
| `client/bench/__init__.py` | `BenchSourceHandler` (`bench.src`), `BenchEditHandler` (`bench.edit`) |

Shared: `ReviewClientHandler`, `Channel`, `announce_section`,
`trunc_display_content` in `client/shared.py`.

## Env / URLs

- `EDG_ACKNOWLEDG_API_{HOST,PORT}`, `EDG_ACKNOWLEDG_UI_{HOST,PORT}`,
`EDG_ACKNOWLEDG_API_KEY`
- Defaults: API `localhost:2727`, UI `localhost:5173`; non-localhost hosts
default port 80
- Print UI section URLs as soon as the section exists (`form_section_http_url`)
- Timestamps: `utc_now_iso` / `is_iso_timestamp` — no `Z` / `+00:00` munging

## Python style here

Follow skill **edg-python-style** (2-space indent, ` = ` in kwargs,
`f"..."` / `'...'`, match neighbors).
108 changes: 108 additions & 0 deletions .agents/skills/edg-changes-example/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
name: edg-changes-example
description: >-
Encodes and decodes non-UTF-8 example bytes in Changes changelogs with
edg-changes-example («HHHH...» markers) and writes Changes files as UTF-8.
Use when editing any Changes file (src/Changes, util/Changes, …), adding
examples with Latin-1/EUC-JP/Shift-JIS/other non-UTF-8 bytes, or verifying
changelog encoding.
---

# `edg-changes-example` and Changes files

## Policy (all `Changes` files)

Any path named `Changes` under the monorepo (for example `src/Changes`,
`util/Changes`, `lib_src/Changes`, `include_c++/Changes`, `nt_util/Changes`)
must remain **valid UTF-8** on disk.

- Write and save Changes entries as **UTF-8**.
- Ordinary Unicode that is valid UTF-8 may appear as real characters (or as
C/C++ escapes such as `\u00b6` in examples). Do **not** wrap valid UTF-8
in `«…»` unless you intentionally need a raw-byte example
(`encode --escape-all-non-ascii`).
- Examples that need **non-UTF-8** bytes (legacy Latin-1, EUC-JP, Shift-JIS,
etc.) must use `«HHHH…»` markers produced by `edg-changes-example`, never
raw non-UTF-8 bytes in the committed file.

CLI: `dev_tools/bin/edg-changes-example` (on `PATH` when direnv is active).

## Note line (required for encoded examples)

When an entry’s example contains one or more `«…»` hex markers, add this
note on its own line after the example, matching existing changelog style:

```text
Note: The above example must be decoded using the edg-changes-example tool.
```

Use that exact wording. Place it immediately after the example block (blank
line before the next dated entry is fine).

## Encode (raw → UTF-8 Changes text)

Given a fragment or file that still contains raw non-UTF-8 bytes:

```bash
edg-changes-example encode raw-fragment.txt > utf8-fragment.txt
# or:
edg-changes-example encode -o utf8-fragment.txt raw-fragment.txt
printf '...' | edg-changes-example encode
```

Default encode keeps maximal runs of bytes `>= 0x80` that form valid UTF-8,
and replaces only non-UTF-8 runs with one marker `«` + hex + `»` (two hex
digits per byte, no separators). `\uXXXX` in the text is left alone.

Optional: `--escape-all-non-ascii` escapes every byte `>= 0x80`, including
valid UTF-8 sequences (rare; only when the example must show raw bytes).

## Decode (UTF-8 Changes text → raw bytes)

To rebuild the real example bytes (for local reproduction, not for commit):

```bash
edg-changes-example decode utf8-fragment.txt > raw-fragment.txt
edg-changes-example decode -o raw-fragment.txt utf8-fragment.txt
# Whole file:
edg-changes-example decode src/Changes > /tmp/Changes.raw
```

Decode replaces each `«HHHH…»` (or legacy `«HH»«HH»`) with the corresponding
bytes. No other escapes are interpreted.

## Workflow for a new changelog entry

1. Draft the entry as UTF-8 prose.
2. If an example needs non-UTF-8 bytes, author the example in a small raw
file (or produce the markers by hand only when the bytes are already
known), then run `edg-changes-example encode` and paste the UTF-8 result
into the entry.
3. Immediately after that example, add:

`Note: The above example must be decoded using the edg-changes-example tool.`

4. Insert the entry into the appropriate `Changes` file with a **UTF-8**
write (keep the rest of the file valid UTF-8).
5. Verify the file still decodes as UTF-8 (raises on failure):

```bash
python3 -c "open('path/to/Changes', encoding='utf-8').read()"
```

Use the real path (for example `src/Changes`). A clean exit means the
whole file is valid UTF-8; a `UnicodeDecodeError` means the write
introduced non-UTF-8 bytes and must be fixed before continuing.

## Do not

- Commit raw Latin-1 / Shift-JIS / etc. bytes inside a `Changes` file.
- Invent alternate note wording; match the existing sentence above.
- Run `encode` on the entire historical `src/Changes` and rewrite it unless
that is an explicit, reviewed task — encode **new example fragments**, then
splice the UTF-8 text into the entry.

## Related

Always-on summary: [AGENTS.md](../../../AGENTS.md). Front-end context:
skill **edg-cpfe-frontend**.
65 changes: 65 additions & 0 deletions .agents/skills/edg-cpfe-bisect/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
name: edg-cpfe-bisect
description: >-
Finds the commit that introduced a test regression or improved behavior
between known good and bad front-end revisions using
edg-docker-test-bisect (and edg-bisect for manual steps). Use when
troubleshooting a failing or newly-passing test across two commits,
searching for the first bad commit, or locating a fix commit.
---

# EDG cpfe test bisection

Prefer Docker. Put `dev_tools/bin` on `PATH` (direnv /
[HACKING.md](../../../HACKING.md)). Project root = directory containing
`src/cfe.c` and `dev_tools/`.

Full tutorial: [dev_annex/tutorials/TEST_BISECT.md](../../../dev_annex/tutorials/TEST_BISECT.md).
Build/test/record recipes: skill **edg-cpfe-build-test**.

## When to use

You need a **test that differs** between two revisions, plus:

| Goal | Newer tip | Older tip |
| --- | --- | --- |
| Find the **regressing** commit | bad (fails) | good (passes) |
| Find the **fixing** commit | good (passes) | bad (fails) |

The CLI detects which situation you mean after verifying that the test
behaves differently on the two commits.

## Automatic bisect

Canonical CLI: **`edg-docker-test-bisect`** (not `edg-docker-bisect`).

```bash
edg-docker-test-bisect [--posture POSTURE] <bad_commit> <good_commit> <test_path>
```

- **Overwrites** front-end sources during the run; restores them to **HEAD**
afterward (HEAD itself is not rewritten as history).
- Tip: `--posture=quick-c-fe` for speed (C-generating `edg_x86_64` only).

### Test path forms

- `sandbox/foo.sft.cpp` (short form; preferred)
- `tests/sandbox/foo.sft.cpp`
- `tests/tests/sandbox/foo.sft.cpp`
- Or a cwd-relative / absolute path into a valid suite

If the reproducer is throwaway, put it under `sandbox`.

## Manual bisect

Wrapper around git-bisect style steps: `edg-bisect`
(`start|good|bad|skip|reset|…`). Use when automatic mode is too heavy or you
need to mark skips yourself.

## Checklist

1. Confirm the test **fails on bad** and **passes on good** (or the inverse
for a fix hunt) with `edg-docker-test --non-interactive …`.
2. Run `edg-docker-test-bisect` with those commits and the test path.
3. After it finishes, sources should match **HEAD**; verify with `git status`.
4. Inspect the identified commit; do not commit or push unless asked.
Loading
Loading