pscat
AGENTS.md2 starsChanged 25 days agoApache-2.0View on GitHub
Operating conventions for any AI coding agent working in this repo. Start with docs/dev/HANDOFF.md for where the project stands and what to work on; the original vision and staged milestones are history, kept in docs/archive/INIT.md. This file covers how to work, not what to build. - Read docs/dev/HANDOFF.md, then docs/dev/ARCHITECTURE.md; the other contributor docs are indexed in docs/dev/README.md. - Read docs/archive/INIT.md for the project's original vision and priorities. Every stage in it is done, so use it for intent, not…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository5 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- announce-releaseSkill
- issue-summarySkill
- sdlcifySkill
- work-issueSkill
The file
We split this AGENTS.md into the rules it gives an agent
9 rules. About 0 are common, 1 is distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 10
Read docs/dev/HANDOFF.md, then docs/dev/ARCHITECTURE.md.
- Very specific: line 12
Read docs/archive/INIT.md for the project's original vision and priorities.
- Very specific: line 30
Run the suite as cargo test --profile ci, the same command CI runs: optimized (the tests run PostScript through the interpreter, ~14x …
- Distinctive: line 49
Commit at reasonable checkpoints within a stage too — don't hold an entire stage's worth of work in one giant commit.
- Very specific: line 59
Don't file new issues while working one.
- Very specific: line 148
Do not use Instant in a test.
- Very specific: line 162
Keep the docs accurate to *current* state, not aspirational state.
- Very specific: line 163
Update them as capabilities land, not just at the end.
- Very specific: line 214
If you're an agent that just needs to *run* PostScript — render a file, debug one, generate a handwriting-style note — read …
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md
Operating conventions for any AI coding agent working in this repo. Start
with docs/dev/HANDOFF.md for where the project stands and what to work on;
the original vision and staged milestones are history, kept in
docs/archive/INIT.md. This file covers how to work, not what to build.
Before you start
- Read
docs/dev/HANDOFF.md, thendocs/dev/ARCHITECTURE.md; the other contributor docs are indexed indocs/dev/README.md. - Read
docs/archive/INIT.mdfor the project's original vision and priorities. Every stage in it is done, so use it for intent, not as a to-do list. - Per-issue summaries of past work are in
notes/(one file per issue); the summaries from beforenotes/existed are in the frozendocs/archive/NOTES.md. Check them before re-deciding a settled question. - The staged roadmap (with the model tier suited to each task and an
escalation rule for tasks that touch the object model or execution machine)
is
docs/archive/ROADMAP.md. It is complete; the guidance still applies to how to scope new work, and a reason for departing from it belongs in anotes/fragment.
Toolchain & commands
- Language: Rust, stable toolchain unless a specific need arises to pin otherwise (explain why if you do).
cargo buildandcargo testshould pass before considering any stage or sub-step complete.- Run the suite as
cargo test --profile ci, the same command CI runs: optimized (the tests run PostScript through the interpreter, ~14x slower in a debug build) but withdebug-assertionsandoverflow-checkson ([profile.ci]inCargo.toml; output intarget/ci/). Plaincargo teststill works, just slowly: the absolute CPU budgets and the default child deadline stretch in a dev-profile build (see "Timing assertions intests/" below), so it passes too. cargo clippyshould be clean (or warnings explicitly justified in a comment) before considering a stage complete.cargo fmtbefore committing.
Workflow
- Work from issues (see
SDLC.md). If you resequence work the issues ordocs/dev/HANDOFF.mdlay out, say so explicitly rather than silently reordering. - At the end of each stage: summarize what was built, what tradeoffs were made, and what was explicitly deferred. Commit with a message that reflects the stage, not just the last edit made.
- Commit at reasonable checkpoints within a stage too — don't hold an entire stage's worth of work in one giant commit.
- Make judgment calls on genuine coin-flip decisions and note them; don't stall waiting for input on things you're equipped to decide. Do flag decisions that are hard to reverse later.
- Goals, in order: correctness and output quality first, then finishing the
ticket. pscat performance is a guard, not a goal; Ghostscript is a
correctness reference only, never timed. A render is judged against the
issue's perceptual acceptance check and
docs/dev/TASTE.md, never traded for a metric. - Don't file new issues while working one. List proposed follow-ups, each
with its user-visible impact, in your final report; the dispatcher files
the ones that matter and puts polish on the parked parent epic
(
docs/dev/DISPATCH.md). Never file anything that assigns physical work to the owner.
Software development lifecycle
Nontrivial work (a new stage, a feature, a fix worth more than a
one-line diff) goes through issue → feature branch → PR → review →
merge → cleanup, not straight onto main. The full lifecycle, and the
current merge-authority policy (who/what may merge once CI is green
and review is clean), is SDLC.md — read that, not this section, for
the authoritative process; .claude/skills/work-issue/ automates it
end to end. A PR that touches a path in .github/CODEOWNERS is merged by the
owner, never by an agent (SDLC.md, "Who merges"; check with
python3 scripts/codeowners_check.py --base origin/main). The one exception is
Dependabot PRs, which agents may merge once CI is green (same section). A
visible render change is merged by the agent after an independent perceptual
review, and logged for the owner's read before the release (same section,
tier (b)). This applies going forward; it doesn't retroactively
obligate rewriting the stage-by-stage direct-to-main history that
predates it.
Code quality bar
- No
unwrap()/expect()on anything derived from program input (parsed PostScript source, user-supplied files). Reserve those for states that are genuinely impossible to reach, and prefer proper error types (thiserroror similar) otherwise. - Comments explain why, not what — skip comments that just restate the code; do explain non-obvious design choices (e.g. why the dict stack is structured a particular way).
- Tests live alongside the code they test. Add integration or golden-image
tests where
docs/archive/INIT.md's stages call for demonstrable rendering output. - Test scratch files come only from
common::scratch_dir(tag)(tests/common/mod.rs,mod common;), never from a bare temp-dir call or a fixed/tmpname. Agents runcargo testin several worktrees at once and a shared name gets clobbered by the other run. The helper returns a new directory unique per call and per process; put a file under it with.join(name).tests/scratch_paths.rsfails on any other route (notes/487-unique-test-scratch-paths.md). - A test that runs
pscatorpscat-mcpnever waits on it without a deadline (notes/841-test-child-deadline.md). A child that does not exit (a limit that regressed, a test written before its feature) would block the suite forever, and an interrupted runner leaves the orphan spinning (one ran for 2.8 days). Intests/common/mod.rs:cmd.output_deadline()/cmd.status_deadline()replaceoutput()/status()(use common::CommandDeadline as _;),cmd.output_with_input(bytes)feeds stdin,common::wait_output(child)replaceswait_with_output(), andcommon::Watchdog::new(&child, ..)guards an interactive child (the MCPServer). Each has a*_within(duration)form for a per-call deadline. The default is 60 s (PSCAT_TEST_DEADLINE_SECSoverrides it); a hang kills the child and its descendants and fails the test with the command in the message.tests/child_deadline.rsproves it and scans for raw calls. - Timing assertions in
tests/measure CPU time, never wall time (notes/329-cpu-time-render-budget-tests.md). Wall time includes every slice the scheduler gave to other processes, so with several agents or a shared CI runner a 0.6 s render reads 2 s and the test flakes. The helpers are intests/common/mod.rs(mod common;), on the test thread's CPU clock (CLOCK_THREAD_CPUTIME_ID; a process-wide clock would count the neighbouring tests). Two shapes, both best-of-N so one slow sample cannot fail the suite:- An absolute budget (DRYMEDIA.md 6.2: a CI test render takes 2 s or
less):
within_cpu_budget(what, budget, 3, || ...). The closure rebuilds whatever it mutates; it returns the fastest try's value. The 2 s cap is DRYMEDIA's, not a tuned bound: the four render scenes measure 0.25-0.7 s of CPU, so this catches a 3-8x regression, not 2x. - A "the guard must not walk the path first" test: a bound relative to a
same-run baseline (
pitfalls.md, "Timing assertions in tests"), with both sides timed bycpu_time_of/thread_cpu_time(every_walkpath_preset_refuses_a_huge_segment_count_promptlyis the model). - Build profile (#886). The absolute budgets are written for the
optimised
ciprofile. A plain dev-profilecargo testruns the same PostScript 11-18x slower (35x for one test under load), sowithin_cpu_budgetmultiplies its budget byUNOPTIMISED_BUDGET_FACTOR(20) there, anddefault_deadline()(the child-process deadline, 60 s) by 3, unlessPSCAT_TEST_DEADLINE_SECSsets it. The render still runs and the assertion still catches a catastrophic regression; it just does not assert an optimised-build figure on an unoptimised one. The build is "unoptimised" only when the test executable sits intarget/debug/(common::unoptimised_build();cfg!(debug_assertions)cannot tell, since[profile.ci]keeps debug assertions on).--profile ci,--release, a custom profile, or an unreadable path keep the budgets exactly as written. A bound relative to a same-run baseline is profile-neutral and usesscale_budgetonly. Usewithin_cpu_budgetfor any new absolute budget, never a hand-rolledDurationcompare. Do not useInstantin a test. Negative check for a new budget test:PSCAT_TEST_BUDGET_SCALE=0.001 cargo test --profile ci --test <file> <name>scales every budget in the run and must make it fail (applyscale_budgetto a hand-rolled bound).
- An absolute budget (DRYMEDIA.md 6.2: a CI test render takes 2 s or
less):
- A change to
lib/*.psthat adds or tightens a validation guard belongs in a%%SelfTestblock in that file, next to the guard —./scripts/selftest.shruns them anddocs/dev/SELFTEST.mdhas the convention. Rust stays the right home for anything needing pixel readback or a realgs. - A procedure added to
lib/artkit.ps,lib/paintkit.ps,lib/drykit.ps,lib/drychalk.psorlib/drycharcoal.psis defined} bind def, not} def: unbound, gs looks up every operator in it at run time and runs 1.5 to 3 times slower (notes/307-bind-library-procedures.md,notes/318-bind-drychalk.md,notes/326-bind-drycharcoal.md).tests/libbind.rsenforces it. - Keep the docs accurate to current state, not aspirational state.
Update them as capabilities land, not just at the end. User-facing
how-to goes in the matching page of
docs/guides/;README.mdis a short front door and gets a line only for a new headline capability.
Where things live
docs/dev/— contributor docs, indexed indocs/dev/README.md:HANDOFF.md(read first),VM.md,FONTS.md,CAPABILITIES.md, the selftest and library-coupling notes, anddesign/(the per-library design records and their evidence images).docs/dev/ARCHITECTURE.md— the design writeup (object model, execution model, rendering-crate leanings, deliberate deferrals). Read before changing anything structural.docs/guides/— user guides, one per library or topic (the old README's long tour);docs/archive/— history kept as written, including the stage log. Check links withpython3 scripts/check_md_links.py(CI runs it as thelinksworkflow); it skipsdocs/archive/andnotes/.docs/archive/NOTES.md— per-stage summaries, newest first, frozen as of 2026-10-13.notes/— newer entries, one file per issue (notes/<issue>-<slug>.md, format innotes/README.md). Write new notes here, not indocs/archive/NOTES.md, so concurrent PRs never conflict.src/lexer.rs— tokenizer; unit tests in-module.src/object.rs—Object/Value/Dict/Num.src/interp.rs— the execution machine (operand/dict/exec stacks), including thebegin_source/step_nAPI front ends drive.src/gfx.rs— graphics state, paths, tiny-skia rasterization.src/window.rs— live winit window stepping the interpreter.src/ops/— operator implementations, grouped like the PLRM's operator summary (stack.rs,arith.rs,graphics.rs,misc.rs; new groups get new modules).src/main.rs— CLI: window /--headless/--png/-eeval / REPL modes, error reporting.tests/eval.rs— end-to-end tests (PostScript source in, operand-stack contents out, compared via==-style reprs).tests/render.rs— headless pixel tests (source in, canvas pixels out).examples/*.ps— sample programs used for manual testing (the Stage 2/3 demo targets).gallery/— generative-art PostScript programs with rendered stills ingallery/renders/and a slideshow script (show.sh); seegallery/README.md. Art files stay within the interpreter's current operator set — they double as its most demanding integration tests.gallery/manifest.jsonis the one list of what the gallery shows. A new piece adds an entry there (title, category, libraries, blurb, alt text, page size, speed) plus its.psandrenders/*.png, then runspython3 scripts/gallery.py syncandcheck. The site'sgallery.htmland per-piece pages, the README highlights, thegallery/README.mdtable andshow.share generated from it: nobody editsgallery.htmlby hand.cargo testruns the check, so agallery/*.pswith no entry fails CI.
Using pscat as a tool (not working on it)
If you're an agent that just needs to run PostScript — render a
file, debug one, generate a handwriting-style note — read
plugin/skills/pscat/SKILL.md: it has the common-path command recipes and
pitfalls (advanced sections in its references/), regardless of which agent runtime you are.
MCP-wired agents can use pscat-mcp instead (docs/guides/agents.md).
Choosing a tool. Reach for pscat when the output must be exact and checkable (text, QR codes, charts, math, fixed layout, print-ready PDF/SVG, reproducible seeded art) or when you have no image output of your own; suggest an image model for photorealism or a loose mood image with nothing exact in it. When to use pscat has the decision table.
<!-- sdlcify:managed:start -->SDLC
SDLC.md is the authoritative record of this repo's development
lifecycle and the branch-protection/merge policy currently enforced on
GitHub — generated and kept in sync by the sdlcify skill
(.claude/skills/sdlcify/). Day-to-day issue execution is handled by
.claude/skills/work-issue/.
<!-- sdlcify:managed:end -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

