Imported from Tsuyumi25/EhHyperlink (
AGENTS.md). Install upstream withnpx skills add Tsuyumi25/EhHyperlink. Copyright stays with the author.
EhHyperlink — AI agent rules
Rules for AI coding assistants (Claude Code, Codex, Cursor, ...) working in this repository. The document is in English; the code it describes is not — see Language below.
What this is
A userscript bundled by vite-plugin-monkey and injected into
e-hentai.org / exhentai.org gallery pages. It finds galleries related to
the one on screen — the same book in other languages, other books of the
series, the magazine a chapter came from, the chapters a magazine contains —
by structured analysis of gallery titles, and shows them as badges on the
title box.
The rules were derived from a census of the public URenko/e-hentai-db SQLite snapshot (4.9 million title strings, 2026-09). Numbers quoted in comments and in this file come from that census.
Scope — what the matcher promises and what it does not
- The target is "galleries with enough correct information find their
relatives", not "every gallery is handled". Titles are maintained by
people; the ehwiki
Renaminggrammar is the spec and rename petitions push the corpus toward it. Corpus census (2026-09): 99.4% of titles carry unwrapped work text; the 0.6% that do not (unbalanced brackets, block-only titles, digit-only titles) are bad samples and get no search. - Do not add a rule for a single gallery. A rule earns its place with a corpus count: the marker table, the sequel-word list and the trailing numeral rules all cite how often the pattern occurs and how often it has a same-creator sibling. Rerun the census against the nightly snapshot before adding to a table.
- Do not add semantic judgement (LLM, embeddings) anywhere in the matching path. The whole point of the project is that structure + tables are enough for the maintained part of the corpus.
- A trailing run of one to three kanji that is not in the sequel-word list
is a subtitle about half the time and a series marker the other half; such
tokens are deliberately not stripped.
chapter.tslists the words the corpus does support. - Only the current gallery is expanded into containers / chapters. Results are never expanded in turn.
Host page
We are a guest on someone else's document:
- The host DOM is a black box. Classes,
onclickhandlers, list-mode markup and API shapes were learned by observation; record what you learn in a comment, that knowledge cannot be recovered from our source. - Never trust visible text for anything a page translator can rewrite. The
category is read from the badge's
onclicknavigation target andct<N>class (src/core/eh/category.ts); titles come from#gn/#gjand are re-fetched through the metadata API for candidates. - Search pages: one
fetchper quoted phrase, first page only, any of the five list display modes must parse. Metadata:api.e-hentai.orggdata, 25 galleries per request, pause after 4 requests (https://ehwiki.org/wiki/API). The API sends CORS for both hosts, so plainfetchworks; no@connectneeded. - The root element carries
translate="no"; mount points read the host's painted background and border into--ehl-bg/--ehl-borderso the box is opaque on both the light and the dark site.
Readability
The next person to read a line matters more here than anything that line saves. Two shapes are banned outright:
- A condition that joins clauses. An
&&or||between two clauses, and above all a negated one, gets extracted into a predicate named for what it decides (Fowler, Decompose Conditional).source.category !== CONTAINER_CATEGORY && !source.tags.includes(ANTHOLOGY_TAG)makes the reader apply De Morgan to find out it means "not a Manga and not an anthology";inContainerChain(source)states it. The predicate's doc comment is where the domain reason for each clause belongs — one clause is a category convention, the other is a tag, and the condition alone cannot say that. A predicate earns its name by joining clauses; wrapping one call does not.source.tags.includes(ANTHOLOGY_TAG)already reads as itself. - A boolean parameter, and any parameter the callee can derive from what it
already has.
planContainerSearch(source, hasLetters, terms, true)tells the call site nothing, andsourcewas already carrying the tag thattruestood for.
The regex policy below is this same rule with a whole file's worth of syntax behind it.
Regex policy
- Regex syntax is written in exactly one file,
src/core/title/pattern.ts. Everything else builds patterns with magic-regexp (exactly,anyOf,maybe,.times,.notAfter, ...) and takes character classes from that file — magic-regexp's ownletter/digit/whitespace/wordBoundaryare ASCII and this corpus is mostly kana and CJK. src/regexBudget.test.tsenforces this: a regex literal,new RegExp, or an ASCII-class import outsidepattern.tsfails the suite.- Lay a rule out as one statement per named concept, chained with indentation
that mirrors the grouping (see
chapter.ts). Neither a one-line chain nor one constant per atom. - Domain vocabulary (markers, sequel words, chapter labels) lives in tables and arrays, never inside a pattern string.
Public repository — no real works
Treat commit messages, comments, README, tests and fixtures as permanent. Quoting a real gallery here would tie the project to that work, so:
- No real gallery titles, circle names, artist names or gids anywhere in
the repo. Tests use invented stand-ins (
Circle Alpha,Work Beta,作品乙,COMIC Alphabeta Monthly); the search-page fixture copies the live markup structure with made-up content. A name that merely sounds invented — a plausible two-word Japanese phrase — usually collides with a real work; stick to the Alpha/Beta/甲/乙 vocabulary. - Convention names from the ehwiki table (
C104,例大祭,天狗様のお仕事) and category / namespace names are fine; they are not works. - No concrete tag values as examples beyond the namespace prefixes
(
artist:,language:,other:rough translation).
Language
- Identifiers, file names, type names: English.
- Comments: Traditional Chinese or English, nothing else. Write one only when the WHY is non-obvious; host-page quirks and corpus numbers count as WHY.
- Commit messages: Traditional Chinese,
type(scope): effect. The subject states the effect, not the action; a body only for a real WHY. - User-facing strings: through
src/i18n.ts(en / zh / ja). A new key goes to all three.
Architecture
src/core/title/— how a title is read: bracket parser, marker table, chapter / sequel / numeral rules,pattern.tssrc/core/search/— what to search and how a hit relates to the source: search plan, Manga container rules, edition-vs-series relationsrc/core/rank/— scoring, creator verdict, language detection, groupingsrc/core/eh/— talking to E-Hentai: search page, metadata API, URL, gallery page, categorysrc/core/pipeline.ts— the flow only: plan → search → enrich → classify → group. No title logic here; add a module and call it.src/settings.ts,src/components/— settings store, badges, lists, settings popup- Tests sit next to the module they cover. Pure functions get unit tests; the live site is checked by hand with a built script injected into a headless tab.
Setup
pnpm install
pnpm dev # dev server; the URL it prints installs into a script manager
pnpm build # vue-tsc --noEmit && vite build → dist/eh-hyper-link.user.js
pnpm test # vitest
Type checking runs vue-tsc, not bare tsc — the latter does not
understand .vue and reports phantom missing modules.