git:20261011.397dbb2 to git:20261022.7f1445e

44 added, 44 removed. Audit A to A.

# AGENTS.md
Prismantis is a Claude Code mod that redraws assistant replies with themeable colors: tables, headings, lists, quotes, code, numbers, paths, links and mermaid diagrams. It ships as a single plugin from this repo, which is also its own marketplace.
## Layout
- | Path | What |
- | --- | --- |
- | `.claude-plugin/plugin.json` | manifest, every user option (`userConfig`), the `types` contract |
- | `.claude-plugin/marketplace.json` | makes the repo installable with `/plugin marketplace add NahumLitvin/prismantis` |
- | `hooks/register.tsx` | every hook (`ui.render` for AssistantMessage, CommandOutput, ToolUse, ToolGroup and TurnDuration, `prompt.submit`, the `/prismantis` command via `session.start` and `command.run`, and the `$.prismantis` noun via `engine.create` and `prismantis.markdown`) and all code that calls `$` |
- | `types/index.d.ts` | the contract for `$.prismantis`, which other mods type against |
- | `hooks/markdown.ts` | markdown to blocks and inline nodes, pure |
- | `hooks/render.tsx` | blocks to `Box`/`Text` trees, pure |
- | `hooks/theme.ts` | presets, color validation, option merging, pure |
- | `hooks/mermaid.tsx` | mermaid box art, pure |
- | `hooks/rtl.ts` | Hebrew and Arabic: simplified bidi over styled inline runs, line wrapping in reading order, comment tails, pure |
- | `hooks/vendor/` | generated by `npm --prefix scripts run build:vendor`, never edited by hand |
- | `tests/` | `claude plugin test` suites |
- | `scripts/` | the vendor build (two build-time patches to beautiful-mermaid: adjustable chart size, and the edge-label fix from upstream PR 157, to drop once it merges), the benchmark and its baseline check, the render snapshot script, and the dev-only `package.json` |
- | `docs/` | CHANGELOG, third-party notices, the demo reply and the README screenshot |
- | `docs/THIRD_PARTY_NOTICES.md` | every bundled library and borrowed palette, with its license |
-
- ## Rules
-
- - Bundled code and copied palettes must be MIT. Check the license at the source repo, not a port or fork: GitHub's `license.spdx_id` misses licenses declared only in a README (Gruvbox), and ports can relicense (Tokyo Night's Neovim port is Apache-2.0, the original VS Code theme is MIT).
- - Add a dependency only when writing it ourselves is unreasonable. Use its latest release, prefer well-starred maintained projects, and record it in docs/THIRD_PARTY_NOTICES.md. The runtime dependencies today are beautiful-mermaid and Prism, both bundled. esbuild is build-time only. The mod runs no external programs.
- - Never copy code from other projects, including other mods. Read them to learn the API, then write our own.
- - No code comments. The why goes in the commit message or PR description.
- - Plugin names cannot start with `claude-`, `anthropic-` or `cc-plugin-`, and must not use other products' trademarks (no "Codex" in names).
- - No new feature flags or config knobs unless the user asked for them. The exception is looks: a feature with more than one plausible look ships its top 3 or 4 styles behind one option (like `taskStyle`, `promptStyle`), Nahum picks only the default, every style is a step in `/prismantis setup` (#22), and each style has a real screenshot in the README next to the option.
- - Feature and fix PRs add their CHANGELOG entry under `## [Unreleased]` and leave the version alone. Only the release commit sets the version, so parallel PRs never fight over it.
- - Every user-visible feature ships with a sample in `/prismantis demo` (`showcaseText` in `hooks/help.ts`), and in the one-screen `/prismantis` help (`helpText`) when it fits. Extend the help tests in `tests/regressions.test.tsx` so dropping the sample fails.
-
- ## Mod API facts that bite
-
- - `$` cannot cross an import. Any function that takes `$` must be declared in `hooks/register.tsx`. `claude plugin validate` does not catch this; `claude plugin test` does ("hooks module did not load").
- - A render hook may not write `$.state`. Schedule work with `$.clock.after(0, ...)` and write from there; `read($, atom)` in the render subscribes and redraws when the value lands.
- - The built-in `Markdown` element has no theming props, which is why we parse and draw markdown ourselves.
- - `TextProps` has color, background, bold, italic, underline, strikethrough, dimColor and inverse. There is no font size on any surface.
- - `Image` draws only in terminals with the kitty graphics protocol (kitty, Ghostty); elsewhere it shows its `alt` text.
- - Installed (user-tier) plugins cannot change the system prompt: the built-in `sec-default` plugin skips their `prompt.compose`, `prompt.section` and `prompt.context` hooks. Add model-only context through `prompt.submit` instead. Tests do not run `sec-default`, so prove prompt changes in a live `claude --debug` session.
- - `userConfig` values are flat primitives. A `string` field with `options` becomes a picker in `/config`.
- - Hooks run without Node: no `require`, no bare package imports, no dynamic `import()`. Vendor libraries as one ESM file.
- - The authoritative API is the `.d.ts` Claude Code lays in `.claude-plugin/types/` after `claude --plugin-dir .`. Grep it before guessing a prop or event.
+ - `hooks/register.tsx` holds every hook and all code that calls `$`. Every other `hooks/*.ts(x)` file is pure.
+ - `.claude-plugin/plugin.json` is the manifest and every user option (`userConfig`); `types/index.d.ts` is the `$.prismantis` contract other mods type against.
+ - `hooks/vendor/` is generated by `npm --prefix scripts run build:vendor`, never edited by hand. The build patches beautiful-mermaid four times: adjustable chart size, a bar chart scale measured from the axis line, with bars drawn to the half cell in half blocks so they reach exactly the gridlines at or below their value and lines passing behind them, the edge-label fix from upstream PR 157, to drop once it merges, and flowchart node names that stop before an arrow's dashes, to drop once upstream issue 140 is fixed.
+ - `codex/`, `.codex-plugin/` and `.agents/plugins/marketplace.json` are the Codex plugin and its marketplace, generated from the Claude Code source by `npm --prefix scripts run build:codex`, never edited by hand: the hint from `HINT` and `FIGURE_HINT`, `$prismantis` from `showcaseText`, the `.tmTheme` files from `PRESETS` through `PRISM_COLORS`. PRs leave them alone. The release commit regenerates them, and CI checks them only while `plugin.json` holds a version with no tag yet.
## Verify
Run all of these before every commit:
```
claude plugin validate .
claude plugin test .
tsc -p .
```
- `tsc -p .` needs `.claude-plugin/types/`, which appears after running `claude --plugin-dir .` once. Then see it render for real (`.claude/skills/live-check`): any change to what gets drawn needs a screenshot of `/prismantis demo` (and `/prismantis` when the help changed), looked at before the PR opens. Tests prove the tree; only a screenshot proves the look. CI runs validate and test on Linux, macOS and Windows, plus a type-check, a check that `hooks/vendor` rebuilds byte for byte from `scripts/package-lock.json` and holds only MIT code, version agreement across `plugin.json`, `marketplace.json` and `docs/CHANGELOG.md`, a no-comments check, and a clean install. Pushing a `vX.Y.Z` tag publishes a GitHub release from the CHANGELOG section.
+ `tsc -p .` needs `.claude-plugin/types/`, which appears after running `claude --plugin-dir .` once. CI gates live in `.github/workflows/ci.yml`.
- Performance and render regressions (CI gates snapshots and node counts):
+ Tests prove the tree; only a real render proves the look. Any change to what gets drawn needs a `live-check` render of `/prismantis demo` (and `/prismantis` when the help changed), looked at before the PR opens and attached to it.
+ Render and performance regressions:
+
```
npm --prefix scripts run snapshot # demo.md drawn in 3 themes vs tests/snapshots, color and structure
npm --prefix scripts run bench:check # timings vs docs/bench-baseline.json (x1.6, normalized by a calibration loop) and node counts (+10%)
```
- CI runs `bench:check -- --no-time`: node counts gate, timings only print, because the calibration ratio differs between an M-series laptop and a Linux runner. Run the full check locally before a release.
+ CI gates snapshots and node counts but only prints timings (`bench:check -- --no-time`), because the calibration ratio differs between an M-series laptop and a Linux runner. Run the full check locally before a release. After an intended visual change run `snapshot:update` and review the diff; after an intended speed or size change run `npm --prefix scripts run bench -- --update`.
- After an intended visual change run `snapshot:update` and review the diff; after an intended speed or size change run `npm --prefix scripts run bench -- --update`. Claude Code refuses any tree over 20000 nodes and silently draws its own text, so a node-count rise lowers that ceiling (about 500 highlighted code lines, 1000 table rows).
+ CI also fails when a file's cognitive complexity grows: the sum of how far each function goes past 15, as Biome measures it. Split the function instead. When a change lowers it, run `npm --prefix scripts run complexity:update` so the lower number becomes the new limit.
- Test-kit gotchas:
+ Claude Code refuses any tree over 20000 nodes and silently draws its own text, so a node-count rise lowers that ceiling (about 500 highlighted code lines, 1000 table rows).
+ ## Rules
+
+ Code:
+
+ - No code comments. The why goes in the commit message or PR description.
+ - Never copy code from other projects, including other mods. Read them to learn the API, then write our own.
+ - Full customization is the product. When someone asks to hide, toggle or customize part of the drawing, the answer is yes unless there is a concrete reason to object (security, a mod-API limit, a real cost); treat such issues as accepted. Otherwise add no feature flags or config knobs. The exception is looks: a feature with more than one plausible look ships its top 3 or 4 styles behind one option (like `taskStyle`, `promptStyle`), the maintainer picks the default, every style is a step in `/prismantis setup`, and each style has a real screenshot in the README next to the option.
+ - Every user-visible feature ships with a sample in `/prismantis demo` (`showcaseText` in `hooks/help.ts`), and in the one-screen `/prismantis` help (`helpText`) when it fits. Extend the help tests in `tests/regressions.test.tsx` so dropping the sample fails.
+
+ Dependencies and licenses:
+
+ - Bundled code and copied palettes must be MIT. Check the license at the source repo, not a port or fork: GitHub's `license.spdx_id` misses licenses declared only in a README (Gruvbox), and ports can relicense (Tokyo Night's Neovim port is Apache-2.0, the original VS Code theme is MIT).
+ - Add a dependency only when writing it ourselves is unreasonable. Use its latest release, prefer well-starred maintained projects, and record it in docs/THIRD_PARTY_NOTICES.md. The bundled runtime dependencies today are beautiful-mermaid and Prism. esbuild is build-time only.
+ - Two external programs can run: the HTML table copy action runs macOS's built-in `osascript`, Windows' built-in `powershell.exe`, or an installed and running CopyQ on Linux, and LaTeX math runs RaTeX's `ratex-render`, installed by the person and off until they set `latex`. Nothing else runs.
+ - Plugin names cannot start with `claude-`, `anthropic-` or `cc-plugin-`, and must not use other products' trademarks (no "Codex" in names).
+ - Feature and fix PRs leave `docs/CHANGELOG.md` and the version alone, and say what changes for the user in the PR description's "What changes for the user" section. The release commit writes the version's CHANGELOG section from those, so parallel PRs never conflict over it.
+
+ ## Mod API facts that bite
+
+ - `$` cannot cross an import. Any function that takes `$` must be declared in `hooks/register.tsx`. `claude plugin validate` does not catch this; `claude plugin test` does ("hooks module did not load").
+ - A render hook may not write `$.state`. Schedule work with `$.clock.after(0, ...)` and write from there; `read($, atom)` in the render subscribes and redraws when the value lands.
+ - The built-in `Markdown` element has no theming props, which is why we parse and draw markdown ourselves.
+ - `TextProps` has color, background, bold, italic, underline, strikethrough, dimColor and inverse. There is no font size on any surface.
+ - `Image` draws only in terminals with the kitty graphics protocol (kitty, Ghostty); elsewhere it shows its `alt` text.
+ - Installed (user-tier) plugins cannot change the system prompt: the built-in `sec-default` plugin skips their `prompt.compose`, `prompt.section` and `prompt.context` hooks. Add model-only context through `prompt.submit` instead. Tests do not run `sec-default`, so prove prompt changes in a live `claude --debug` session.
+ - `userConfig` values are flat primitives. A `string` field with `options` becomes a picker in `/config`.
+ - Hooks run without Node: no `require`, no bare package imports, no dynamic `import()`. Vendor libraries as one ESM file.
+ - The authoritative API is the `.d.ts` Claude Code lays in `.claude-plugin/types/` after `claude --plugin-dir .`. Grep it before guessing a prop or event.
+
+ ## Test-kit gotchas
+
- `ui.find({ text: 'x' })` matches substrings, so it returns the parent line. Anchor it: `text: /^x$/`.
- A test whose plugin calls `next(e)` needs an engine stand-in: `on('ui.render', ($, e) => <Text>engine</Text>)`, returning a tree, not `null`.
- Loop visual tests over `['terminal', 'desktop'] as const`.
## Adding an option
1. Add the field to `userConfig` in `plugin.json` with a `title`, a `description` and a `default`.
2. Read it in `resolveStyle` in `hooks/theme.ts`, falling back safely on bad input.
3. Add a row to the README options table.
4. Add a test using `test(name, { options: {...} }, ...)`.
## Skills
| Skill | When |
| --- | --- |
| `.claude/skills/add-theme` | adding or renaming a color preset |
+ | `.claude/skills/demo` | printing the demo reply to eyeball every feature |
| `.claude/skills/live-check` | seeing a change render in a real session |
+ | `.claude/skills/drive-check` | pressing keys in a pane or button before merging, in a throwaway config |
| `.claude/skills/release` | cutting a version |