Thanks for helping make document remediation more accessible! Iris is Open Source under AGPL-3.0 (see LICENSE) and maintained by Equalify Inc., the University of Illinois Chicago, and California State University.
By participating you agree to our Code of Conduct.
- Report an accessibility barrier — in the app/demo or in the HTML Iris produces. Use the Accessibility issue template. These are our highest priority.
- Report a bug / request a feature — use the matching issue template.
- Improve a prompt — the agent files are in
agents/, andagents/page.mdrenders every page, so a PR that sharpens it changes the product. (The Reader and Copy Editor prompts are code, insrc/pipeline/review.ts.) Iris also opensNew agent suggestion: <type>issues when it meets content a specialist would handle better, and you're welcome to open one yourself — but note thatagents/is not a directory of content types: a file added for a type the whole-page pass already covers is never loaded — the design notes say why, under "One agent per page, not one per content type".chartDataAgent.mdis the shape that earns its place. - Code — bug fixes and improvements via pull request.
A well-written issue may get a pull request without you doing anything else. An owner first labels
it maintainer (a bot tags them when it's time). A scheduled workflow then ranks those issues
Sun–Wed and opens one PR for the most pressing one it can finish well
(details) — accessibility barriers rank first, and small
user-visible fixes reported against the demo rank well because they review cleanly. It never
touches an issue labelled no-auto-pr, never files a second PR for an issue it has already tried,
and stops entirely when nothing is eligible. If you'd rather own the fix yourself, say so on the
issue and ask for the no-auto-pr label.
You get the credit for it. A PR from that workflow names you in its body and carries a
Co-authored-by trailer for your account on the commit, so the merged commit is attributed to you
as well as to the bot that typed it — the report is the contribution. The trailer uses your
GitHub users.noreply address, never your real email.
It also stays off any issue that already has an open PR — including yours. Open a PR for an
issue and the workflow leaves it alone; if every open issue has one, it opens nothing at all. Two
things claim an issue: Closes #<n> in your PR body (which also closes the issue on merge, so
this is the one to use) or an issue-<n> in your branch name. Merely mentioning #<n> in prose
does not claim it — too many PRs reference issues they aren't fixing — so if you are working on
something, say Closes #<n> and the robot will stay out of your way.
Requires Node 24+ (runs TypeScript directly; uses built-in node:sqlite), git, and —
for PDF uploads — poppler-utils (brew install poppler / apt-get install poppler-utils).
npm install
cp .env.example .env # a GitHub PAT (required) + a model provider key
cp config.example.yaml config.yaml
npm start # http://localhost:8080 (app at /, API under /v1)Before opening a PR:
npm run typecheck # tsc --noEmit
npm test # the unit suite (node --test; run it through npm, see below)
./test/e2e.sh # full API lifecycle against mock GitHub + mock model (needs jq)Run the unit suite through npm, not as a bare node --test. The script carries two
things a bare run does not:
- A second reporter (
test/spec-with-signals.mjs) that printssignalandexitCodewhen a test file's process dies. Node's default reporter shows that as✖ some.test.tsand'test failed'— identical to a failed assertion, with nothing on stderr — and the tests after the death simply never run, so the pass count reads clean while being short. --no-sparkplug, which avoids a V8 bug that segfaults test children inside the garbage collector roughly once in ten full runs on macOS arm64. The crash needs code Sparkplug generates, so turning that tier off removes the path; it cost nothing measurable here (two runs each, 55.8 s either way). It is on the test script only:npm startandnpm run devkeep the Sparkplug path on purpose, because one dev server dying is loud, where a dead test child reads as a clean run with a short count. Drop the flag when nodejs/node#65753 ships in a 24.x release — it is the backport of the V8 fix, and no released 24.x has it yet.
Both exist because of #405, which has the crash stack and the evidence.
The demo page must stay accessible — it's audited with the project's own axe-core lint and should report 0 violations.
Docs here are written in concise plain language. That is a requirement, not a preference. It
covers README.md, everything under docs/, the comments in config.example.yaml, and the agent
prompts in agents/ — every one of them is read by someone deciding whether to trust Iris with a
document, and the prompts are read by a model as well.
What it asks for:
- One idea per sentence. If a sentence needs a second read, split it.
- The claim first, the caveat after. Never the reverse.
- A number instead of an adjective. "About 10.7¢ a page" beats "cost-effective". Anything you assert about behaviour should be checkable against code, a test, or a named benchmark round.
- No jargon without a gloss on first use, and no new term where a plain one exists.
- No repetition. A paragraph that restates the one above it gets deleted, not softened.
- One job per document, and no restating another one. A claim lives in the document whose job it is, and the others link to it — that is what keeps this set DRY and cheap to correct, since a claim written twice is a claim that goes stale in one place. README § Working on Iris says which document holds what.
- Shorter over completer. A page nobody finishes documents nothing.
Two things this is not. It is not a ban on detail — an exact number, a file path, or a caveat that saves a reader an hour all belong in. And it is not about formatting: there is no linter here, and nothing checks heading style, line length or word choice.
Who checks it: the automated review, then a maintainer. On a PR touching those files the reviewer quotes a sentence that breaks one of the rules above and names the rule, as a non-blocking note — at most three, the worst ones. It is the only prose it comments on, and a note is not a merge gate. It checks the last rule on what a PR adds: a new file or section where a line in an existing doc would do, or a paragraph that could be a sentence. It quotes the addition and writes the shorter version.
A PR that only makes an existing doc plainer is welcome, with no code change attached.
- Branch from
main, keep PRs focused, and describe the change + how you tested it. mainis protected: every change lands as a merged pull request. Direct pushes are refused for everyone — maintainers included — and force-pushes and branch deletion are blocked. @bbertucc merges; a review from them is requested automatically (CODEOWNERS).- No formal approval is required to merge, so don't wait for a green review checkmark that never comes. The gate is a maintainer reading the PR, not a count of approvals — GitHub won't let anyone approve their own work, and requiring approvals would block the maintainer's changes rather than yours.
- Match the surrounding code style (the codebase favors small, well-commented modules).
- New runtime dependencies should be justified — Iris aims to stay portable and lightweight.
- AGPL-3.0: contributions are licensed under the same terms.
Your PR gets a review from Claude in CI before a maintainer reads it (details). Useful things to know:
- It runs the checks itself and quotes their real output, so a failing
typecheckore2ecomes back as a blocking finding with the relevant lines. - It will not nit-pick style, formatting or naming. There's no linter or formatter in this repo on purpose. It's also told not to suggest alternatives when your approach is correct, and not to raise pre-existing issues your PR doesn't touch. If it does one of those anyway, that's a bug in the prompt — say so on the PR.
- One exception: docs prose. On a PR touching the files the Documentation section covers, it checks prose against those rules — quoting the sentence, naming the rule, three at most, always as a non-blocking note. The list of files lives in that section, not in this bullet, so this bullet cannot fall out of date with it. Not in scope even there: heading style, line length, Markdown layout, or wording it would have chosen differently.
### Non-blocking notesmeans "merge-ready". A finding only blocks if something reaches it on input the code accepts today; real-but-unreachable findings are notes on an approval. You don't have to resolve them to merge, and you don't have to argue your way out of them.- Its verdict is advisory, and its check is not a merge gate. A human merges. The check is
deliberately not required on
main, because it could not be a trustworthy gate: a PR touching only ignored paths never triggers it at all, and a fork PR's skipped job counts as satisfied with nothing having reviewed the code. If you think a blocking finding is wrong, reply on the PR — the reviewer sees its earlier reviews on re-runs, but it is the maintainer you're actually talking to. - Fork PRs aren't reviewed automatically (a fork PR gets no CI secrets). A maintainer dispatches the review manually; nothing is needed from you.
- A PR touching
.github/workflows/**gets a CI-security review first. The workflows are part of the app, so expect questions aboutpermissions:, secrets reaching PR-authored code, and${{ }}inrun:blocks before anything about accessibility. Changingcode-review.ymlitself also works now, with one visible difference: the review posts as github-actions[bot] rather than claude[bot], and the PR gets a comment explaining why and asking for a human read as well. iris-auto/*PRs aren't reviewed automatically either. Those come from the scheduled triage workflow, and GitHub doesn't start workflow runs for events raised byGITHUB_TOKEN. Each one carries a comment saying so with the dispatch command. Nothing is needed from contributors.
src/pipeline (extraction → assembly → review), src/providers (LLM provider abstraction),
src/routes (the /v1 API), src/auth (the deployment's one GitHub identity, and the API gate),
agents/ (the agent prompt files).
See README.md and docs/API.md.