AGENTS.md@plugins/prismantis · git:20261011.397dbb2 · 2026-10-11 · sha256 ddcaeb6138db0525

AGENTS.md@plugins/prismantis git:20261011.397dbb2A

Immutable. This exact content never changes and is served at /api/v1/blob/ddcaeb6138db0525.

# 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.

## 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.

Performance and render regressions (CI gates snapshots and node counts):

```
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.

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).

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/live-check` | seeing a change render in a real session |
| `.claude/skills/release` | cutting a version |