Before writing or modifying any extension, multisrc theme, or lib code in this repository, read CONTRIBUTING.md in full. It is the source of truth for conventions here and is updated frequently - do not rely on prior knowledge of this codebase or of Tachiyomi/Mihon extensions in general, since common patterns have changed.
A few points that are easy to get wrong from stale training data:
- New sources must extend
KeiSource(libVersion = "1.6"), neverHttpSourcedirectly.HttpSource/libVersion = "1.4"only exists in extensions/themes not yet migrated - see KeiSource.- Metadata is injected via KSP: Do NOT manually declare or
override val name,lang,id, orbaseUrlin your@Sourceclass. These are defined usingsource {}blocks inbuild.gradle.ktsand injected automatically.
- Metadata is injected via KSP: Do NOT manually declare or
SourceFactoryis obsolete: Do NOT implementSourceFactoryto create multiple sources. Instead, add multiplesource {}blocks in the extension'sbuild.gradle.kts.- Generated Preferences: Do NOT write manual
SharedPreferenceslogic for base URL mirrors or custom user URLs. UsebaseUrl { mirrors(...) }orbaseUrl { custom(...) }inside thesource {}block inbuild.gradle.kts. - Deeplinks are DSL-driven: Declare URL intent filters using the
deeplink {}block inbuild.gradle.kts. Do NOT modifyAndroidManifest.xmlor write manual intent filtering logic. - DTOs must be regular classes: Do NOT use
data classfor@SerializableJSON/Protobuf DTOs (it bloats bytecode). Use a regularclass, make fieldsprivatewhere possible, and only use@SerialNamewhen the JSON key differs from the camelCase property name. - No local JSON/Proto instances: Do NOT create
private val json: Json by injectLazy(). Use the sharedkeiyoushi.utilshelpers likeresponse.parseAs<T>(),toJsonRequestBody(), orparseAsProto<T>(). - Use
ext-bootstrap.pyto scaffold new extensions rather than hand-writing the module structure - see Using ext-bootstrap.py. - Prefer the helpers documented under keiyoushi.utils (core utilities) (JSON parsing, HTTP requests, date parsing, GraphQL, WebView execution, etc.) over hand-rolled equivalents.
- Check Available libs before implementing something from scratch
that a
lib/module may already solve. - Don't add excessive or obvious comments explaining what code does; only comment on non-obvious why (a workaround, a subtle invariant, a site-specific quirk).
- Don't over-engineer. No speculative abstractions, config options, or generalization beyond what was asked, and no "just in case" error handling, fallbacks, or validation for scenarios that can't actually happen.
If anything in this file conflicts with CONTRIBUTING.md, CONTRIBUTING.md wins.
For a typical "add/fix a source" task, only change files under src/ (individual extensions),
lib-multisrc/ (multisrc themes), or - in rare cases - lib/. Leave core/, compiler/,
common/, gradle/, and other build-logic/infrastructure files alone unless you were explicitly
asked to change them - that's a different, higher-risk category of work.
Run release lint on every Android module you touched:
./gradlew :src:en:mysource:lintReleaseRun lint directly on shared modules, for example
./gradlew :lib-multisrc:madara:lintRelease or ./gradlew :lib:myhelper:lintRelease.
Linting only an extension that depends on a shared module does not reliably report issues in the
dependency itself. Apply the same rule to :core:lintRelease when explicitly working on core/.
If you are an AI agent asked to open a pull request, disclose that at the end of the PR description: what you were asked to do, and that the PR was opened by an AI agent. Start that note with a 🤖 emoji. This is in addition to, not a substitute for, the "This PR is AI-assisted..." checklist item in the Pull Request checklist - that box still needs to be checked by the human who reviewed your changes.