austere-axi 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,10 @@
10
10
 
11
11
  Review pages for coding agents. The agent writes a short [OpenUI Lang](https://github.com/thesysdev/openui) program, not HTML. You open it in the browser, click on any part, and write a note. The agent gets each note with the name of the statement it points at. It edits only those statements, and the page updates in place.
12
12
 
13
+ ![An agent writes a page, the reviewer clicks a table cell and a diagram node and sends two notes, and the page patches in place with the changed statements flashing](docs/demo.gif)
14
+
15
+ <sub>The real loop, about 30 s: [MP4](docs/demo.mp4) · [WebM](docs/demo.webm).</sub>
16
+
13
17
  ```
14
18
  root = Stack([intro, flow, options, decide], "column", "l")
15
19
  intro = Card([CardHeader("Session storage migration", "Redis → Postgres"), introText])
@@ -29,6 +33,8 @@ strategyField = FormControl("Rollout", RadioGroup("strategy", [RadioItem("Big ba
29
33
 
30
34
  [`examples/session-migration.html`](examples/session-migration.html) is a full page (KPIs, diagram, table, chart, diff, decision form) in about 1,000 tokens.
31
35
 
36
+ **Website:** [mandalorianbatman.github.io/austere-axi](https://mandalorianbatman.github.io/austere-axi/) has a [gallery](https://mandalorianbatman.github.io/austere-axi/gallery/) of example pages (postmortem, API review, PR summary, sprint plan) next to their source and token counts, and a [playground](https://mandalorianbatman.github.io/austere-axi/play/) where you edit OpenUI Lang and see the page with nothing installed.
37
+
32
38
  ## Why
33
39
 
34
40
  The loop is the same as [lavish-axi](https://github.com/kunchenguid/lavish-axi). The difference: the agent never writes HTML, CSS or layout.
@@ -36,12 +42,18 @@ The loop is the same as [lavish-axi](https://github.com/kunchenguid/lavish-axi).
36
42
  | | lavish-axi | austere-axi |
37
43
  |---|---|---|
38
44
  | Agent writes | Full HTML + CSS + SVG | OpenUI Lang statements |
39
- | After feedback | Writes the file again | Edits the named statements; the page patches in place |
45
+ | After feedback | Edits the HTML; the page reloads | Edits the named statements; the page patches in place |
40
46
  | Feedback points at | CSS selector + text | Statement name and line, plus table cell, diff line, diagram node or selected text |
41
47
  | Validation | Layout audit in the browser | `austere-axi check`, in Node, no browser |
42
48
  | Look | Different for each agent | One dark design |
43
49
 
44
- Fewer output tokens means faster pages and lower cost.
50
+ Fewer output tokens means faster pages and lower cost. From the [benchmark](docs/benchmark.md):
51
+
52
+ <!-- bench:start -->
53
+ Across 36 paired runs (6 review pages, 2 models), lavish-axi spent a median **3.2×** the output tokens of austere-axi to produce the same page, took **2.4×** as long and cost **2.5×** as much. Answering one reviewer note took **2.7×** the output tokens.
54
+ <!-- bench:end -->
55
+
56
+ The benchmark also shows where lavish-axi does better; read its caveats before quoting the numbers.
45
57
 
46
58
  ## Install
47
59
 
@@ -70,6 +82,44 @@ npx skills add MandalorianBatman/austere-axi --skill austere # tells agents wh
70
82
 
71
83
  The `austere` skill is a stub that points at `--help` and `spec`, so it does not go out of date. A global install stays on its version until `npm update -g austere-axi`.
72
84
 
85
+ ## Install as a Claude Code plugin
86
+
87
+ The plugin bundles the `austere` skill (and `/austere`), the `austere-setup` skill and the MCP server below:
88
+
89
+ ```sh
90
+ claude plugin marketplace add MandalorianBatman/austere-axi
91
+ claude plugin install austere@austere-axi
92
+ ```
93
+
94
+ In a session: `/plugin marketplace add MandalorianBatman/austere-axi`, then `/plugin install austere@austere-axi`. The MCP server runs `npx -y austere-axi mcp`, so it needs Node 22.18 or later and npm access. `claude plugin update austere` picks up new skill text; the CLI comes from npm each time.
95
+
96
+ ## Use with MCP
97
+
98
+ `austere-axi mcp` is an MCP server on stdio. Its tools are `spec`, `check`, `open` (validate and open the review), `poll`, `reply`, `patch` and `end`. Each runs the CLI command of the same name and returns its output, so the review loop is the same as on the command line. `poll` returns `status: timeout` after 50 s by default, because many clients cancel longer tool calls; the agent polls again and nothing is lost. Pass `timeout_ms: 0` to wait until feedback arrives when your client allows long calls. `poll` and `reply` take the `cursor` a poll returned, as `--cursor` does.
99
+
100
+ **Claude Desktop** (`claude_desktop_config.json`) and **Cursor** (`~/.cursor/mcp.json`, or `.cursor/mcp.json` in a project):
101
+
102
+ ```json
103
+ {
104
+ "mcpServers": {
105
+ "austere": {
106
+ "command": "npx",
107
+ "args": ["-y", "austere-axi", "mcp"]
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ **Codex** (`~/.codex/config.toml`):
114
+
115
+ ```toml
116
+ [mcp_servers.austere]
117
+ command = "npx"
118
+ args = ["-y", "austere-axi", "mcp"]
119
+ ```
120
+
121
+ Or `codex mcp add austere -- npx -y austere-axi mcp`. On a machine with no screen, add `AUSTERE_AXI_NO_OPEN=1` to the server's `env` so `open` prints the link instead of launching a browser. The other [environment variables](#environment) work the same way. Pass absolute file paths: the server resolves relative ones against its own working directory, which depends on the client.
122
+
73
123
  ## Quick start
74
124
 
75
125
  ```sh
@@ -95,6 +145,7 @@ agent edits statements ◄── austere-axi poll plan.html ◄─┘ (long-pol
95
145
  - **Components**: 58 stock OpenUI components, plus `Architecture` (`Node`, `Edge`, `Group`), `Diff` and `FileTree`. `Mermaid` and nine shadcn-style components are bundled [packs](#components-and-packs).
96
146
  - **Architecture diagrams** lay out automatically ([dagre](https://github.com/dagrejs/dagre)). The agent never writes coordinates. Each node and edge is a click target. `lanes` puts each group in a column; `tone` colors a box or line to show risk. Use Mermaid for sequences, state machines, ER and gantt.
97
147
  - **Annotations** go to the smallest named statement under the click, with detail where it exists: table row and column, `file:+line` in a diff, a diagram node, or selected text. The reviewer can move a note to a parent section.
148
+ - **Changes**: after each agent turn the changed statements flash, and the bar shows `N changed`. That button is a toggle: on, it keeps the last turn's changes outlined until you switch it off, and each new turn replaces the outlines with its own changes.
98
149
  - **Buttons and forms** send feedback. `@ToAssistant("…")` sends a message with the form values. A button with no action sends its label.
99
150
  - **Phone review**: when Tailscale runs, the server also listens on the tailnet IP and prints a phone link. The link carries your owner key; keep it to yourself. The review UI has a mobile layout.
100
151
 
@@ -103,19 +154,24 @@ agent edits statements ◄── austere-axi poll plan.html ◄─┘ (long-pol
103
154
  ```
104
155
  austere-axi sessions + usage
105
156
  austere-axi <file> validate + open a review [--no-open] [--reopen]
106
- austere-axi poll <file> long-poll for feedback [--agent-reply "..."] [--takeover]
107
- austere-axi reply <file> post a reply without polling
108
- austere-axi end <file> end the review as the agent
157
+ austere-axi poll <file> long-poll for feedback [--cursor n] [--agent-reply "..."] [--after n] [--takeover]
158
+ austere-axi reply <file> post a reply without polling [--cursor n]
159
+ austere-axi end <file> stop as the agent; the reviewer can still add notes
160
+ austere-axi close <file> close the review: no more notes until reopen
161
+ austere-axi reopen <file> reopen an ended or closed review
162
+ austere-axi login [file] single-use link that signs a browser in as you (SSH, --no-open)
109
163
  austere-axi feedback <file> feedback no agent has answered yet [--all] [--peek]
110
164
  austere-axi check <file> validate the program (no browser) [--fix]
111
- austere-axi patch <file> merge statements from stdin into the program
165
+ austere-axi patch <file> merge statements from stdin into the program (reports dropped unreferenced ones)
112
166
  austere-axi spec [group|pack...] component reference (~5.8k tokens; groups narrow it)
113
167
  austere-axi components ... list | search | show | add | remove | new | build
114
168
  austere-axi export <file> one self-contained HTML file, works offline
115
- austere-axi hub on|off|status|list|notes|open persistent server with an index of every page
169
+ austere-axi hub on|off|status|list|notes|open home page with every page and note
116
170
  austere-axi share <file> --as "<name>" invite link for a teammate on your tailnet [--auto]
117
171
  austere-axi share list|revoke see invites, cut one off
118
172
  austere-axi stop stop the background server
173
+ austere-axi server --persist run the server in the foreground and never idle out
174
+ austere-axi mcp MCP server on stdio (see Use with MCP)
119
175
  ```
120
176
 
121
177
  Output is compact YAML for agents. Each `poll` item has its statement and that statement's line, so the agent does not need to read the file again.
@@ -123,10 +179,14 @@ Output is compact YAML for agents. Each `poll` item has its statement and that s
123
179
  Session rules:
124
180
 
125
181
  - One poll listens on a session at a time.
182
+ - Every note is written to `~/.austere-axi/hub.db` before the page is told it was sent, so a note survives a crash or restart of the server.
126
183
  - Feedback sent while no agent listens waits for the next poll.
127
- - A review the user ended does not open again unless you pass `--reopen`.
128
- - If all review tabs stay closed, `poll` returns `browser_disconnected`: 10 s after a tab closes, or 5 min after a dropped connection (for example, a locked phone).
129
- - `feedback <file>` returns notes no agent has answered, also from earlier days. Reading them marks them received; `reply` marks them answered.
184
+ - Feedback stays unread until the agent acknowledges it. `poll` prints a `cursor`. Passing it back (`--cursor n` on the next `poll` or `reply`), or replying at all, acknowledges everything up to it. A `poll` without either returns the same feedback again, so feedback lost on the way to the agent (a crash, a timed-out tool call) is delivered again. `--after n` replays from any point.
185
+ - Ending a review (the agent's `end`, or **End review** in the page) means the agent stopped. The reviewer can still send notes. The next `poll` returns every note the agent has not acknowledged, including ones sent before the end or released from hold after it, and reopens the review. A batch the reviewer sent with end stays final.
186
+ - Only the owner's **close** (`close <file>`, or **Close review** in the page) locks the composer. `reopen <file>` or **Reopen review** unlocks it.
187
+ - A review the user ended or closed does not open again unless you pass `--reopen`.
188
+ - If all review tabs stay closed, `poll` returns `browser_disconnected`: 10 s after a tab closes, or 5 min after a dropped connection (for example, a locked phone). The agent stops polling but does not end the review, so notes left later are picked up by the next `poll`.
189
+ - `feedback <file>` returns notes no agent has answered, also from earlier days. Reading them acknowledges them; `reply` marks them answered.
130
190
 
131
191
  ## Components and packs
132
192
 
@@ -162,7 +222,7 @@ A page names its packs in the head, so it renders wherever you open it:
162
222
 
163
223
  **Trust.** Pack code runs inside your review pages, where notes and button presses also come from. Packs under `@austere-axi/` on npm install directly. Other npm packs and any `.tgz` need `--trust`; read them first. A local folder counts as your own. A page with third-party packs shows a warning, and `poll` tells the agent.
164
224
 
165
- **Write a pack:**
225
+ **Write a pack:** [Writing a pack](docs/writing-a-pack.md) walks through one from scaffold to `npm publish`, using [austere-pack-gantt](https://github.com/MandalorianBatman/austere-pack-gantt), a Gantt chart pack built outside this repo, as the example. The short version:
166
226
 
167
227
  ```sh
168
228
  austere-axi components new austere-pack-milestones
@@ -174,16 +234,38 @@ Each component uses `austereComponent` from `austere-axi/pack`: a PascalCase nam
174
234
 
175
235
  ## Hub
176
236
 
177
- The server starts when needed and stops after 30 idle minutes. `hub on` keeps it running and indexes every page you open or check:
237
+ The server starts when needed and stops after 30 idle minutes. Every review is kept in `~/.austere-axi/hub.db` (SQLite) either way. `hub on` adds a home page over it and indexes every page you `check` or `patch`:
178
238
 
179
239
  ```sh
180
240
  austere-axi hub on # http://127.0.0.1:4388/: pages by project and day, with search
181
241
  austere-axi hub list # the same, for an agent: [words] [--project dir]
182
242
  austere-axi hub notes # every note, same for an agent: [words] [--project dir] [--unanswered]
183
- austere-axi hub off # back to on-demand; the index stays
243
+ austere-axi hub off # back to the plain session list; the database stays
184
244
  ```
185
245
 
186
- `/notes` lists every note on every page, newest first, with search, status and project filters. The home page shows which reviews are live and how much feedback the agent has answered. The index is SQLite (`~/.austere-axi/hub.db`), copied from the session files; the review loop does not need it.
246
+ `/notes` lists every note on every page, newest first, with search, status and project filters. The home page shows which reviews are live and how much feedback the agent has answered.
247
+
248
+ Session files from versions before 0.5 (`~/.austere-axi/sessions/*.json`) are imported into `hub.db` on first start and renamed to `*.imported`. One server runs per state dir; a second one refuses to start. `server.lock` names the running server's pid and port; a lock whose server no longer answers on that port is stale and is taken over.
249
+
250
+ ### Always on, behind a reverse proxy
251
+
252
+ To serve review pages at a public address, run the server under a supervisor with `--persist` so it never idles out, and point the proxy at it:
253
+
254
+ ```ini
255
+ # ~/.config/systemd/user/austere-axi.service
256
+ [Unit]
257
+ Description=austere-axi review server
258
+
259
+ [Service]
260
+ ExecStart=/usr/bin/env npx -y austere-axi server --persist
261
+ Environment=AUSTERE_AXI_LINK_URL=https://austere.example.com
262
+ Restart=on-failure
263
+
264
+ [Install]
265
+ WantedBy=default.target
266
+ ```
267
+
268
+ `systemctl --user enable --now austere-axi`. Commands on the same machine use the running server; a newer CLI that replaces an older server starts the new one with `--persist` too. `AUSTERE_AXI_PERSIST=1` does the same as `--persist`. Read the reverse-proxy note in [Team review](#team-review) first: behind a proxy on the same machine every request looks local.
187
269
 
188
270
  ## Team review
189
271
 
@@ -198,21 +280,63 @@ austere-axi share revoke Ana # name or id; takes effect at o
198
280
 
199
281
  Send the link to the teammate. The link needs Tailscale (or `AUSTERE_AXI_HOST`); without one, `share` stops with an error.
200
282
 
201
- - **Hold (default):** a teammate's note waits in your side panel. Press **Send** on one note, or **Send all**. **Discard** removes it. Held notes are not in `poll` or `feedback`, so the agent does not see them until you send them.
283
+ - **Hold (default):** a teammate's note waits in your side panel. Press **Send** on one note, or **Send all**. **Discard** removes it. Held notes are not in `poll` or `feedback`, so the agent does not see them until you send them. Each teammate can have up to 200 notes held at once; more are refused until you send or discard some.
202
284
  - **Auto:** the note goes to the agent without your approval.
203
285
  - **Author:** `poll` and `feedback` show `from: <name>` on each teammate's note. Your own notes have no `from`. The hub Notes view shows the author and can filter on it.
204
286
  - **Reviewers can:** read the page and the conversation, and add notes. They cannot end the review, export, or send held notes.
205
287
 
206
- Trust model: anyone who has the link, and can reach your machine on the tailnet, can post notes under that name until you revoke it. Share links only with people you trust, and revoke when the review is done. The link holds a random 192-bit token; the browser keeps it in a cookie and the address bar drops it.
288
+ Trust model: anyone who has the link, and can reach your machine on the tailnet, can post notes under that name until you revoke it. Share links only with people you trust, and revoke when the review is done. The link holds a random 192-bit token. Opening it shows a "Continue as <name>" button; pressing it (a same-origin POST) trades the token for a cookie and the address bar drops it. A link opened from another site therefore cannot switch your browser into a reviewer.
289
+
290
+ A request without a valid invite or the owner key is refused, on loopback too. The hub and its Notes view are yours only. Your own phone link carries the owner key (`~/.austere-axi/owner.key`); do not forward it. To make all old phone links and owner cookies stop working, delete that file and restart the server. A reverse proxy on the same machine gets no owner rights from looking local.
291
+
292
+ ## GitHub Action
207
293
 
208
- Off loopback, a request without a valid invite or the owner key is refused. The hub and its Notes view are yours only. Your own phone link carries the owner key (`~/.austere-axi/owner.key`); do not forward it. To make all old phone links stop working, delete that file and restart the server. Behind a reverse proxy on the same machine, every request looks local, so it acts as you.
294
+ Post an austere review page on every pull request. The action runs `austere-axi check` on the pages you commit, exports each one as a self-contained HTML file with `austere-axi export`, uploads them as a workflow artifact, and posts or updates **one** comment on the PR with a summary and a download link.
295
+
296
+ ```yaml
297
+ # .github/workflows/austere.yml
298
+ name: austere review
299
+ on: pull_request
300
+
301
+ permissions:
302
+ contents: read
303
+ pull-requests: write # to post the comment
304
+
305
+ jobs:
306
+ review:
307
+ runs-on: ubuntu-latest
308
+ steps:
309
+ - uses: actions/checkout@v7
310
+ - uses: MandalorianBatman/austere-axi/action@main # pin a release tag or SHA once you settle
311
+ with:
312
+ pages: reviews/*.html
313
+ ```
314
+
315
+ | Input | Default | |
316
+ |---|---|---|
317
+ | `pages` | required | paths or globs, one per line, relative to the workspace |
318
+ | `version` | `latest` | austere-axi version from npm; pin it for reproducible runs |
319
+ | `artifact-name` | `austere-review` | workflow artifact that holds the exported pages |
320
+ | `retention-days` | repository default | artifact retention |
321
+ | `comment` | `true` | `false` leaves only the artifact and the job summary |
322
+ | `comment-tag` | `default` | names the sticky comment; use one per use of the action in the same PR |
323
+ | `github-token` | `github.token` | needs `pull-requests: write` to comment |
324
+
325
+ Outputs: `ok` (`true` when every page passed) and `artifact-url`.
326
+
327
+ - **A failing check fails the job.** The `check` output, with each error's statement and line, is in the log and in the job summary; pages that passed are still exported.
328
+ - **Fork PRs** get a read-only token, so the comment is skipped with a warning. The job summary and the artifact still carry the report.
329
+ - **Sticky comment:** the action updates only a marked comment posted by its own token's account (a bot account for `github.token`). A comment someone else writes with the same marker is left alone, and the action posts its own.
330
+ - **Triggers:** use `pull_request`. Do not use `pull_request_target` to run pages from a fork.
331
+ - **Artifact:** GitHub zips it and requires a login to download it. Unzip and open the HTML in a browser; it works offline. A Pages preview is not part of the action.
332
+ - The action lives in this repository, under `action/`, so one ref pins the action and the CLI contract it calls. `version` still picks the CLI.
209
333
 
210
334
  ## Environment
211
335
 
212
336
  | Variable | Default | |
213
337
  |---|---|---|
214
338
  | `AUSTERE_AXI_PORT` | `4388` | server port |
215
- | `AUSTERE_AXI_STATE_DIR` | `~/.austere-axi` | sessions, server log |
339
+ | `AUSTERE_AXI_STATE_DIR` | `~/.austere-axi` | sessions, owner key, server log (mode 0700 on macOS and Linux) |
216
340
  | `AUSTERE_AXI_NO_OPEN` | | `1` = never open a browser |
217
341
  | `AUSTERE_AXI_HOST` | | extra bind address (never `0.0.0.0`) |
218
342
  | `AUSTERE_AXI_NO_TAILSCALE` | | `1` = loopback only |
@@ -220,18 +344,24 @@ Off loopback, a request without a valid invite or the owner key is refused. The
220
344
  | `AUSTERE_AXI_LINK_URL` | | full origin of the printed remote link, e.g. `https://austere.example.com` behind a TLS proxy; the host is accepted without `ALLOWED_HOSTS` |
221
345
  | `AUSTERE_AXI_ALLOWED_HOSTS` | | extra `Host` headers to accept (reverse proxy) |
222
346
  | `AUSTERE_AXI_DISCONNECT_GRACE_MS` | `300000` | how long a dropped connection can stay away before `poll` returns; `0` disables |
223
- | `AUSTERE_AXI_IDLE_TIMEOUT_MS` | `1800000` | idle shutdown; `0` disables (the hub never idles out) |
347
+ | `AUSTERE_AXI_IDLE_TIMEOUT_MS` | `1800000` | idle shutdown; `0` disables |
348
+ | `AUSTERE_AXI_PERSIST` | | `1`: never idle out, as `server --persist` |
224
349
  | `AUSTERE_AXI_NPM_REGISTRY` | `npm_config_registry`, else npmjs | where `components add` gets packs |
225
350
 
226
351
  ## Security
227
352
 
228
353
  The server binds to loopback, plus the Tailscale IP when Tailscale is up. It never binds to all interfaces.
229
354
 
230
- - `/api/*` answers only loopback callers and refuses browser requests.
355
+ - Loopback is not the owner: other OS users on the machine can reach it too. The owner key in `owner.key` is.
356
+ - `/api/*` answers only loopback callers that send the owner key, and refuses browser requests. `/api/health` needs no key; it proves the server holds the key with an HMAC of its port and the CLI's nonce, so the CLI never talks to, or hands the key to, another program on its port.
357
+ - A browser signs in with a single-use login code: when the CLI opens your browser, it asks the server for a code and opens the page with it; the server swaps it for the owner cookie and drops it from the address bar. The key is never on a command line or in printed output. A code works once and for 60 seconds. Another local user who reads it from the process list in that time can use it first; your browser then shows "used already", and `austere-axi login` gives you a new one.
358
+ - A printed `url` works in a browser that already has the cookie. For any other browser (SSH with a forwarded port, `--no-open`, a second browser), run `austere-axi login [file]` and open the link it prints.
359
+ - The owner cookie is an HMAC of the key, not the key. Browsers share `127.0.0.1` cookies across ports, so any program listening on another local port that your browser visits can read it. With it, that program can open your pages and the hub as you in a browser, but not call `/api/*`, which needs the key.
360
+ - On macOS and Linux the state dir is made mode 0700 on every run, and new files in it (`owner.key`, `server.log`, and `hub.db`, which holds every note) are mode 0600. Files an older version created keep their old mode, behind the 0700 dir. On Windows, file modes do not apply: the state dir is protected only by your user profile's permissions.
231
361
  - `Host` headers must be on an allowlist (DNS-rebinding defense).
232
362
  - Browser routes that change state refuse a foreign `Origin`.
233
363
  - Invite tokens are random, compared in constant time, and kept out of the log. A reviewer link can only add notes to its own page. See [Team review](#team-review).
234
- - The page runs in a sandboxed iframe with no same-origin access. Packs run there too.
364
+ - The page runs in a sandboxed iframe with no same-origin access. Packs run there too. Page files are also served with a `Content-Security-Policy: sandbox` header, so the page stays sandboxed when opened in a popup or its own tab. In review, links that open a new window are opened by the review page, and only if they are external `http(s):` or `mailto:` links. A popup the frame opens any other way stays sandboxed.
235
365
  - The server serves files only from the page's own directory, with no dotfiles.
236
366
  - Third-party packs need `--trust`. Installed packs must match their lockfile hash; CDN loads use Subresource Integrity.
237
367
 
@@ -252,25 +382,35 @@ npm run build # dist/: runtime, packs, pack kit, Mermaid, spec, catalog, rev
252
382
  npm test
253
383
  npm run typecheck # tsc over src/cli, src/shared and the pack types
254
384
  node dist/cli.mjs examples/session-migration.html
385
+ npm run site # _site/: the website (needs Chrome or CHROME_PATH); see site/ and examples/
255
386
  ```
256
387
 
257
388
  | Path | Contents |
258
389
  |---|---|
259
390
  | `src/runtime/components/` | Custom components (`Architecture` family, `Diff`, `FileTree`), one file each |
260
391
  | `src/runtime/stock.js` | Which stock components ship, which stay out of `spec`, their groups |
392
+ | `docs/` | [Writing a pack](docs/writing-a-pack.md) |
261
393
  | `packs/` | Packs bundled with austere-axi (`mermaid`, `shadcn`) |
262
394
  | `src/runtime/pack-api.js`, `src/pack-kit/` | `austere-axi/pack` and the `components build` pipeline |
263
395
  | `src/shared/catalog.js` | Component catalog and prompt tiers |
264
396
  | `src/runtime/` | Library, annotation layer, review bridge, styles |
265
397
  | `src/chrome/` | Review UI around the page iframe |
266
398
  | `src/cli/` | CLI, server, sessions, `check`, `export`, packs, hub |
399
+ | `action/` | The [GitHub Action](#github-action) (not published to npm) |
267
400
  | `skills/` | Published skills: `austere`, `austere-setup` |
401
+ | `.claude-plugin/` | Claude Code marketplace and plugin manifest (in git, not in the npm package) |
268
402
 
269
403
  Maintainer guides (not published): [`AGENTS.md`](AGENTS.md), [`.agents/skills/add-component`](.agents/skills/add-component/SKILL.md) to add a component, [`.agents/skills/review-change`](.agents/skills/review-change/SKILL.md) before you commit. Tests enforce the component rules: every component has a group, `spec` stays in its token budget, released prop orders are append-only (`npm run signatures`), and every example passes `check`.
270
404
 
271
405
  Visual checks: `npm run gallery`, `npm run shot`, and `npm run test:browser` (real review loop in headless Chrome; needs Chrome or `CHROME_PATH`).
272
406
 
273
- **CI** builds and tests every push to `main` and every pull request (Node 22, 24, 26 on Linux; Node 24 on Windows; browser suite). **Release:** bump `version` in `package.json`, push, and publish a GitHub Release. `publish.yml` tests again and publishes to npm with trusted publishing.
407
+ Website: `site/` holds the hand-written pages and CSS, `examples/` feeds the gallery (add a file there and it appears with a generated thumbnail), `scripts/site.mjs` builds `_site/`, and `.github/workflows/pages.yml` deploys it to GitHub Pages on every push to `main`. Examples need a `<meta name="description">` for the gallery blurb. None of it is published to npm.
408
+
409
+ Playground (`/play/`, source in `site/play/`): a textarea editor beside a sandboxed frame running `dist/austere.js`. It plays the part of the review chrome over the same `postMessage` bridge, so edits re-render in place and clicks arrive as the annotations `poll` prints. Problems come from `src/shared/check-program.js`, the same code as `austere-axi check`, bundled for the browser by `scripts/site.mjs` (with the spec, the component catalog and one template per example in `play/data.json`). The program travels in the URL hash (`#code=v1.<deflate-raw, base64url>`), so there is no server. `test/browser/play.browser.test.js` drives it in headless Chrome as part of `npm run test:browser`. Packs other than the bundled ones are not available there.
410
+
411
+ Benchmark against lavish-axi: `npm run bench` (needs model keys; see [docs/benchmark.md](docs/benchmark.md)). `npm run bench:report` rebuilds its tables from the committed results.
412
+
413
+ **CI** builds and tests every push to `main` and every pull request (Node 22, 24, 26 on Linux; Node 24 on Windows; browser suite). `action.yml` runs the GitHub Action on fixture pages (`test/fixtures/action/`). **Release:** bump `version` in `package.json`, push, and publish a GitHub Release. `publish.yml` tests again and publishes to npm with trusted publishing.
274
414
 
275
415
  ## License
276
416