AI-only: token-dense repo map for coding agents; humans start at docs/README.md.
PDF document engine (invoices/slips): template YAML + params JSON → layout → PDF.
Pipeline: Template/Definitions → Bundle → Layout → Render → Preview → Sign → Verify → Archive.
The file-by-file repo map is split per component under docs/code-map/
(AI-only, token-dense; entry granularity + maintenance rules:
docs/code-map/README.md). Read the map file
for every directory you are about to search or edit — it is cheaper
than searching cold. Seam work (the wasm boundary, the GUI↔engine wire)
reads BOTH sides' maps.
Rust workspace lives in engine/ (crates named shojiku-<dir>). Every .rs
file opens with a //! role header, so head -1 identifies a file without
opening it; unit tests live in #[cfg(test)]-only sibling files
(<mod>/tests.rs, further split under <mod>/tests/), near-e2e suites in the
crate's tests/ directory.
| You touch | Read first | It carries |
|---|---|---|
engine/core, engine/diagnostics |
docs/code-map/core.md | template/definitions/params wire model, two-pass parse, validation walks, the diagnostics code registry |
engine/layout, engine/layout-box |
docs/code-map/layout.md | positioning/pagination, text·wrap·fonts, the layout tree (the ONLY layout↔renderer contract), the box index, the e2e suite map |
engine/formatter, engine/image |
docs/code-map/formatter-image.md | display strings, locale packs (CLDR builtins, 和暦), the font-pack wire; asset policy/SVG/raster |
engine/render-pdf, engine/render-png |
docs/code-map/render.md | the two draw backends (krilla / tiny-skia) over the layout tree |
engine/signing |
docs/code-map/signing.md | the PDF incremental-update writer (tail/xref/object reading under the hostile-input posture, revision append, the signature placeholder + byte ranges) AND the signer: PKCS#8 key loading, CMS SignedData, the prepare_sign/complete_sign split; the shared PDF model + OID table engine/verify reads through |
engine/verify, engine/fuzz |
docs/code-map/verify.md | the verifier: the structural walk to a signature, the byte-range COVERAGE rule (a valid signature over an incomplete range is a forgery), CMS/certificate checking, and the report that states what it did NOT check (seam work reads signing.md too); plus the out-of-workspace libFuzzer crate and its corpus-replay contract |
engine/authoring, engine/cli, engine/mcp, engine/wasm, engine/capi, engine/napi, engine/fetch |
docs/code-map/hosts.md | the ONE bytes-first authoring surface + its five thin hosts (CLI / MCP stdio / browser WASM / C ABI cdylib the FFI SDKs load / the N-API addon the npm package loads, which reaches the engine THROUGH the cdylib host rather than beside it) + the host-only font fetch crate, capability keys |
gui/designer-core |
docs/code-map/gui-core.md | headless document model (parse/serialize, patch ops, editor session) + the workspace/toolchain preamble |
gui/designer |
docs/code-map/gui-designer.md — split by area, read the one you touch: canvas (engine transport, preview loop, paint/overlay/zoom, drag·resize·snap) · panel (property panel, placement, borders, page setup, styles, columns, diagnostics) · insert (insert menu/dialogs, scaffolds, image·paste import, field palette, sample data, data-item editor) · chrome (menubar, toolbar, help, i18n, ui/ primitives, theme/CSS, chip text editor) · tutorial (step data, coach mark, launcher, practice-document swap) |
the embeddable React component; the index file itself carries the assembled Designer, layer tree, sidebar, editor session, the ShojikuGui hook registry and test substrate |
gui/designer-app |
docs/code-map/gui-app.md | the standalone app host: services seam, presets/fonts/persistence, build/assemble, integration suites (Designer work spanning packages reads the neighbor's map too) |
sdk/ |
docs/code-map/sdk.md | the seven language wrappers, all built — the ruby gem (the REFERENCE the other six mirror: result/trace shape, template-root hardening, the fiddle ownership rules), the python package mirroring it over ctypes, the .NET package over function pointers and the JVM one over JNA, the npm package over the N-API addon, and the two SUBPROCESS ones (php, go) that script the CLI instead, plus their gate containers |
packs/, examples/, docker/, docs/, site/, skills/, scripts/, mk/, .claude/hooks/ |
docs/code-map/repo.md | font/locale packs, bundled examples (+ the output-refresh rule), runtime image, doc set, homepage pitch pages, product-facing AI skills (npx skills add layout), repo gate scripts, the per-scope Makefile includes, the tracked Claude Code hooks (the deterministic half of the development rules) |
The table above answers WHERE — which files own the thing you are changing. These answer HOW, and each is the single canonical home for its concern (component docs reference them rather than restating):
| You need | Read |
|---|---|
| where a feature BELONGS; which boundary must not move | docs/architecture.md (the constitution) + docs/agents/<area>.md for the component |
| the style / lint / 100%-coverage bar, in any language | docs/guidelines.md |
| how correctness is ESTABLISHED at all — and why you may not invent a check | docs/agents/verification.md + the allowlist at head -30 Makefile |
| a gate failed and you do not recognise the failure | the FAIL block itself — it names the TREE it ran over, WHERE it broke and WHAT IT IS, and prints the command that fixes it; kept at .make-logs/last-error.log. The follow-up questions are commands too: make investigate:tree / :docker / :gates / :coverage / :render / :pins (make help) |
| what is BUILT vs merely DECIDED | docs/engine/features.md vs docs/agents/<area>.md |
Working order for a change: code map → rulebooks → build → the verification rulebook says whether it worked. The forward-looking queue (what to work on) is deliberately NOT in this repository.
- No local cargo: run gates via
make(Docker wrapper). The merge bar is CI, not a local full run (user decision):make verifystill exists and is still the whole mirror (line budget, fmt, clippy -D warnings, test, coverage 100% lines blocking, cargo-deny, the key-catalog + reference-table drift gate (make reference:check; regenerate viamake reference:generate, which rewrites both the catalog and the generated tables indocs/engine/*.md), examples output-hash check (make examples:check; refresh viamake examples:render), docker build/render/trivy), but it is NOT the routine pre-PR step — CI runs a strict superset of it (see below) in parallel, in minutes rather than the ~20 a local serial run costs. Reach for it only when you cannot use CI: working offline, or changing a gate's own recipe and wanting the answer before pushing. - Checking a result ≠ reading output. To check, use the
<scope>:<job>grid —make engine:verify/gui:verify/site:verify/docker:verify, and the faster slicesengine:budget/engine:lint/engine:test(same shape forgui:andsite:;make helplists them all). Each prints ONE PASS/FAIL line, exits with the gate's REAL code, and keeps the full log in.make-logs/. A quiet failure always lands at the fixed path.make-logs/last-error.log(headed with the target, exit code and the last== step ==reached, cleared when that target next passes) —catit instead of re-running to find where it broke.V=1streams the raw output INSTEAD: it bypasses the quiet wrapper, so it writes no log and runs neithergate-culprits.shnorgate-diagnose.sh. Reach for it when you are reading a failure you already have, not when you are asking whether something passed. A correctness claim comes from a sanctionedmaketarget — never invent an equivalent (user rule): the sanctioned commands are the allowlist at the top of theMakefile(head -30 Makefile), and the rulebook behind it — what counts as a claim vs mere inspection, never wrapping a gate in a pipe /; echo $?/make -n, what to do when the command you need is missing, and when a greenverifystill stands — is docs/agents/verification.md. Read it before saying anything works. Every job has exactly ONE name and is quiet by default; addV=1to any of them (make gui:verify V=1) for the raw output while debugging a failure.make make:checkis the gate over the surface itself — it refuses a target filed under the wrongmk/<scope>.mk, and any tracked file naming a target that does not exist (a doc placeholder like<scope>:lock, and a CI matrix's${{ }}name, are checked as patterns). - CI runs the same
maketargets you do (.github/workflows/ci.yml), in the same pinned containers, as ~30 parallel checks (14 job definitions; the SDK and install-proof matrices expand them) — engine, gui, wasm, site, docker, and every SDK across its supported language versions. There is no second definition of a gate to keep in sync. It is a strict superset ofmake verify: every one of that target's prerequisites has a CI job (theengine-cachecomposite runsmake <target>), and CI additionally runssite:build, the SDK version MATRIX (local runs one version per language) and theproof:<lang>install checks. So a green CI is a stronger statement than a green local mirror, never a weaker one. It fires onpull_request, so the PR is what starts it — a feature-branch push alone runs nothing. maintakes no force pushes and cannot be deleted (repository ruleset); history accumulates through PRs. A push or PR starts CI normally — the exception is a change whose every path matchesci.yml'spaths-ignore(**/*.md,docs/**,.gitignore,LICENSE*), where noci.ymljob runs at all.gh workflow run ci.yml --ref mainis the manual trigger for that case and for re-running a gate without a new commit. Three of those ignored paths are not inert:docs/engine/**is projected onto the site, where the page set, the per-pagereference:front-matter and the link round-trip are all asserted,CHANGELOG.mddecides which engine versions the site may pin, andREADME.mdfeeds the generated gallery — sosite-docs.ymlrunsmake site:verifyover exactly those three (one job, no Rust, no wasm). A path filter is evaluated against the EVENT, so a commit-message marker can only ever SKIP a run ([skip ci]), never start one.- GNU Make 4 or newer. make is the only tool that does not run in a container, so it is the one place local and CI can disagree; macOS ships 3.81, which parses recipe quoting differently. The Makefile refuses (everything but
help) and names the fix. - Line budget: WHY there is a cap, and why hitting one is a signal about the design rather than an instruction to trim, is docs/guidelines.md § File length — read it before splitting anything. Every non-test
.rsunderengine/is ≤300 lines (≤160 is the design target for a NEW file, and is not gated) and every.rs, tests included, must start with a//!role header —make engine:budget(scripts/check-line-budget.sh) gates both in CI; exceeding 300 needs an in-fileline-budget-exempt: <reason>waiver.clippy::too_many_lines(threshold 150,engine/clippy.toml) gates function length. The gui side has the same shape: every non-test.ts/.tsxundergui/is ≤150 executable lines (blank lines and comments excluded, so documenting a file costs no budget) viamake gui:budget(scripts/check-gui-line-budget.sh, the first step ofmake gui:verify), sameline-budget-exempt: <reason>waiver token; Biome'snoExcessiveLinesPerFunction(150,gui/biome.json) gates function length there with no waiver list. Test files are outside the length budget on both sides — a suite is a list of independent cases, not a unit of design. - Test placement (canonical: docs/guidelines.md § Where tests live): focused unit tests go in
#[cfg(test)] mod tests;sibling files (<mod>/tests.rs), NOT the Book's inlinemod tests { … }. A grown suite splits under<mod>/tests/, where the directory names the target and the file names the aspect — and every split suite's//!header says what it covers, as a behaviour where the suite drives a public entry point (most of them) and as a source module only where it is genuinely scoped to one. There is no file-to-file mapping between a module's sources and its suites, and chasing one costs what the names carry. Near-e2e suites go in the crate'stests/as one binary (tests/<name>/main.rs+ modules). The coverage bar is the workspace run (make engine:coverage, 100% lines blocking); per-cratecargo llvm-cov -p <crate>is a diagnostic, not a gate. engine/Cargo.lockis committed and stages normally (git add); the one deliberately gitignored lockfile isengine/fuzz/Cargo.lock. (An old rule said the global gitignore hides it and-fis needed — that was a misread of*.login the global excludes, and a tracked file's changes never need-fanyway.)- deny.toml has zero advisory ignores; don't add deps that break that (e.g. anything pulling ttf-parser or lopdf).
- Renderers never re-measure/re-format; layout never draws. Locale data →
packs/locale/, fonts →packs/fonts/, business rules → futureplugins/, never into engine crates. - Coverage counts each crate twice (its own unit-test binary + the copy linked into dependents' test binaries); a line only covered in one copy can still fail the 100% gate — cover new code in the crate's own unit tests first.
- The roadmap is not in this repository.
docs/agents/<area>.mdstates what is DECIDED anddocs/engine/features.mdwhat is BUILT; anything absent from both is unsettled, so propose it rather than assuming it was rejected. When something ships, its substance goes todocs/engine/features.md+ thedocs/engine/reference (the implemented-capability list + decision log). - Architecture/policy docs:
docs/architecture.md,docs/engine/features.md(what's built),docs/engine/(authorable-syntax reference, one page per feature),docs/agents/<area>.md,docs/guidelines.md. - The code map (
docs/code-map/, routed by the table above) is the token-saving entry point: read the touched component's file BEFORE searching, and update it in the same PR whenever crates/modules/boundaries change.