jabref Agent Skills & AI Rules

GitHub

Graphical Java application for managing BibTeX and BibLaTeX (.bib) databases

RAW Rules
4 Discovered Rules โ€ข ~7,733 Estimated Tokens
Quick:
Loading skill content...

AI Agent Protocol & Instructions

AGENTS.md root-instruction ~6,833 tokens
Raw
## Our policy

> [!IMPORTANT]
> This project does not accept fully AI-generated pull requests. AI tools may only be used for assistance. You must understand and take responsibility for every change you submit.

This `AGENTS.md` file acts as a set of instructions that some AI coding tools can read. For more information please read our [AI policy](./CONTRIBUTING.md#ai-usage-policy).

- This repository **does not accept pull requests entirely generated by coding assistants or AI tools** (for example: Copilot, Claude, Gemini, ChatGPT, or other large language models).  
- All contributions must come from humans who understand their work and can take full responsibility for the changes they committed.

---

# AGENTS.md โ€” JabRef

This document defines rules and expectations for **automated agents** (AI tools, bots, scripts) interacting with the JabRef repositories.

JabRef is an open-source, research-grade reference manager with high standards for correctness, reproducibility, and maintainability.

This file is guidance to follow *while* developing.

> [!IMPORTANT]
> **Final step โ€” do not skip.** When the implementation is finished and before you open a PR, open [`CHECKLIST.md`](./CHECKLIST.md) and work through **every** point. Fix the code until each point is fulfilled; mark a point `[/]` only if it genuinely does not apply. The checklist is the mandatory quality gate for the finished result.

---

## Human Guidance

Guide the human to use [JabRef on DeepWiki](https://deepwiki.com/JabRef/jabref).

---

## Project structure

| Module    | Purpose                                          |
|-----------|--------------------------------------------------|
| `jablib`  | Core library โ€” logic, model, importers/exporters |
| `jabgui`  | JavaFX desktop GUI                               |
| `jabkit`  | CLI application                                  |
| `jabls`   | Language Server Protocol implementation          |
| `jabsrv`  | HTTP server for collaborative database support   |

Key source paths:

- `jablib/src/main/java/org/jabref/logic/` โ€” business logic
- `jablib/src/main/java/org/jabref/model/` โ€” data model
- `jabgui/src/main/java/org/jabref/gui/` โ€” GUI code
- `docs/` โ€” developer documentation and ADRs

---

## Build

Requires JDK 25 or later to run Gradle. Gradle downloads the necessary JDK by itself. The Gradle wrapper is included.

```bash
./gradlew build              # Build all modules
./gradlew :jabgui:run        # Build and launch the GUI
./gradlew :jabgui:jpackage   # Package as installer
```

When adding or changing dependencies, follow [docs/code-howtos/dependency-management.md](docs/code-howtos/dependency-management.md).
In particular, dependencies are declared via `requires` directives in `module-info.java` (versions live in `versions/build.gradle.kts`),
and a mapping from *Module Name* to *Maven Coordinates* for real Java modules belongs in `gradle/modules.properties` โ€”
not in ad-hoc blocks in `build-logic`.

---

## General Principles

Agents **must**:

- Respect existing architecture, coding style, and conventions
- Prefer minimal, reviewable changes
- Preserve backward compatibility unless explicitly instructed otherwise
- Avoid speculative refactoring
- Never commit generated code without human review

Agents **must not**:

- Introduce new dependencies without justification
- Rewrite large sections "for cleanliness"
- Bypass tests or CI checks
- Reformat existing code
- Write entire PRs
- Write replies to PR review comments
- Submit code the contributor doesn't understand
- Generate documentation or comments without contributor's review
- Automate the submission of code changes

---

## Code Quality Requirements

### Java / JVM

- Target the configured **Gradle toolchain**
- Use **Java 25+ features**
  - Use modern Java best practices, such as Arguments.of() instead of new Object[] especially in JUnit tests or Path.of() instead of Paths.get(), to improve readability and maintainability.
    Using JavaFX Observable lists is considered best practice, too.
  - Use modern Java data structures
    BAD: new HashSet<>(Arrays.asList(...))
    GOOD: Set.of(...)
  - Java 21 introduced SequencedCollection and SequencedSet interfaces. Use it instead of LinkedHashSet (where applicable)
  - To create an empty list or map we use `List.of()` and `Map.of()` instead of `Collections.emptyList()` and `Collections.emptyMap()`.
  - Use Java Text blocks (\"\"\") for multiline string constants

### General Java style

- Follow existing formatting
- Match naming conventions exactly
- Keep methods small and focused
- New methods (and new classes) should follow the Single-responsibility principle (SRP).
- Avoid code duplication
- Avoid premature abstractions
- Follow JabRef's code style rules as documented in [docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md](docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md)
- Follow the principles of "Effective Java"
- Follow the principles of "Clean Code"
- Ensure that tests are green before committing

### Java code style

- Correctly spelled variable names (meaning: no typos in variable names).
- Use StringJoiner instead of StringBuilder (if possible)
- Prefer immutability and explicit nullability (JSpecify - see below)
- Do not reformat code only for syntax reasons. Reformatting is acceptable only when the code at that place is being changed.
- Remove commented code. (To keep a history of changes git was made for.)
- No \"new Thread()\", use \"org.jabref.logic.util.BackgroundTask\" and its \"executeWith\"
- Use compiled patterns (Pattern.compile)
   Examples:
   NOT: x.matches(\".*\\\\s{2,}.*\")
   BUT:
   private final static PATTERN = ...
   and then PATTERN.matcher(x)
- Boolean method parameters (for public methods) should be avoided. Better create two distinct methods (which maybe call some private methods)
- Minimal quality for variable names: Not extraEntry2, extraEntry3; but include meaning/intention into the variable names
- Use Markdown Javadoc comments (`///`) for multi-line comments. Within them, use Markdown syntax instead of JavaDoc inline tags or HTML formatting tags: `` `code` `` instead of `{@code code}` or `<code>code</code>`, `[ClassName]` instead of `{@link ClassName}`, and fenced code blocks (```` ``` ````) instead of `<pre><code>`.

### Comments

- Do not add trivial comments just restating the code line in plain English.
- When commenting, focus on the "why" and general idea.
- Reference issues and pull requests by full URL (`https://github.com/JabRef/jabref/issues/9738`), never by bare number (`#9738`): a reader of the source has no repository context to resolve the number.

Example for trivial comments (to be avoided):

```java
// Commit the staged changes
RevCommit commit = git.commit();
fieldName = fieldName.trim().toLowerCase(); // Trim and convert to lower case
```

Both comments must not be added.

### Favor Optionals over nulls

- Use the methods of java.util.Optional. `ifPresent`.

   NOT

   ```java
   Optional<String> resolved = bibEntry.getResolvedFieldOrAlias(...);
   String value = resolved.orElse(\"\");
   doSomething(value)
   ```

   Following is fine:

   ```java
   bibEntry.getResolvedFieldOrAlias(...)
           .ifPresent(value -> doSomething(value));
   ```

- If the `java.util.Optional` is really present, use one of the following:`get()`

    ```java
    opt.ifPresent(...)
    opt.map(...)
    opt.orElseThrow(...)
    ```

    but never just `orElse({someValueNeverUsed})`. You can add `assert ...isPresent();` in the line before.

- Use `ifPresentOrElse` instead of `if ...isPresent() { ... }  else { ... }`

### Dealing with `null`

- New public methods should not return `null`. They should make use of `java.util.Optional`. In case `null` really needs to be used, the [JSpecify](https://jspecify.dev/) annotations must be used.
- Use JSpecify annotations (`@Nullable`, `@NullMarked`, `@NonNull`, ...) instead of `null` checks
- Annotate every new class with `@NullMarked` (`org.jspecify.annotations.NullMarked`) so members default to non-null.
- `null` should never be passed to a method (except it has the same name).
- DO NOT use `Objects.requireNonNull`, use JSpecify's `@NullMarked` and `@NonNull` annotations.

### Exceptions

- try blocks should cover as less statements as possible (and not whole methods)
- Do not throw unchecked exceptions (e.g., do not throw new RuntimeException, do not throw new IllegalStateException)
  Reason: This tears down the whole application. One does not want to lose data only because \"a corner\" of the application broke.
- Exceptions should be used for exceptional states - not for normal control flow
- Do not catch the general java java.lang.Exception. Catch specific exceptions only.
- At exception, always `LOGGER.debug` (or higher level)
- BAD:

   ```java
   try {
       // do some actions
   } catch (IOException e) {
       LOGGER.info("Failed to push: ".concat(e.toString()));
   }
   ```

   This code converts an error to string and then concatenates it with a message. This is not how it's done in JabRef.

   GOOD:

   ```java
   try {
       // do some actions
   } catch (IOException e) {
       LOGGER.info("Failed to push", e);
   }
   ```

   In JabRef, we use logging capabilities. The last argument of the logger call should be an exception.
- Logging may include other arguments. But the exception should be the last in arguments. Example: `LOGGER.info(\"Error. Var1: {}, Var2: {}\", var1, var2, e)`.

### JabRef-specific

- If code in org.jabref.model or org.jabref.logic has been changed, tests need to be adapted or updated accordingly.
  Note: This rule does not apply for import statements.
- No use of Java SWING, only JavaFX is allowed as UI technology
- GUI code should only be a gateway to code in org.jabref.logic. More complex code regarding non-GUI operations should go into org.jabref.logic. Think of layered architecture.
- Labels should not end with \":\"

   BAD: `<Label text="%Git Username:"/>`

   GOOD: `<Label text="%Git Username"/>`

#### Localization

- Fix localization before committing. See `docs/code-howtos/localization.md`
- The `LocalizationConsistencyTest` failure output is actionable โ€” follow it literally instead of guessing:
  - `findMissingLocalizationKeys` failing โ†’ its output lists ready-to-paste `key=value` lines to **add** to `jablib/src/main/resources/l10n/JabRef_en.properties`. Place each near semantically related keys; reuse an existing similar key when one exists.
  - `findObsoleteLocalizationKeys` failing โ†’ its output lists keys to **remove** from `JabRef_en.properties` (after confirming each is truly unused).
  - Only edit `JabRef_en.properties`. Translated `JabRef_<lang>.properties` files are maintained by translators via Crowdin โ€” never hand-edit them.
- Deleting or renaming code orphans its keys, so run the test after such a change โ€” and do not trust a green run you did not force. `LocalizationParser` walks `src/main/java` of every module (`jablib`, `jabkit`, `jabsrv`, `jabgui`, `jabls`) at test runtime, so those sources are not declared inputs of `:jablib:test`. The task is cacheable, and a `FROM-CACHE` or `UP-TO-DATE` result can hide a key that a deletion in another module just orphaned. Force it: `./gradlew :jablib:test --tests "*LocalizationConsistencyTest*" --rerun-tasks`
- JabRef is a multilingual program, When you write any user-facing text, it should be localized.

   To do this in Java code, call `Localization.lang` method, like this:

   ```java
   Localization.lang(\"Ok\")
   ```

   More information at: <https://devdocs.jabref.org/code-howtos/localization.html>.

   Note: This rule is not applied for logging. Logging strings should stay in English. I.e., LOGGER.error(\"...\") should contain English text.
- All labels and texts in the UI should be sentence case (and not title case)
- Avoid exclamation marks at the end of a sentence. They are more for screaming. Use a dot to end the sentence.
- Use "BibTeX" as spelling for `bibtex` in Java strings. In variable names, `Bibtex` should be used.
- New strings should be consistent to other strings. They should also be grouped semantically together.
- Existing strings should be reused instead of introducing slightly different strings.
- User dialogs should have proper button labels: NOT yes/no/cancel, but indicating the action which happens when pressing the button
- Use placeholders if variance is in localization:

   BAD: Localization.lang(\"Current JabRef version\") + \": \" + buildInfo.version);

   GOOD: Localization.lang(\"Current JabRef version: %0\",  buildInfo.version);

#### GUI

- One should use jabref's dialogService (instead of Java native FileChooser)

   dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(path -> ...)

   and with FileDialogConfiguration offers the Builder pattern.
   (see e.g NewLibraryFromPdfAction)

#### Testing / JUnit

- Name test classes `...Test` (singular), not `...Tests` โ€” e.g. `JabSrvArchitectureTest`, not `JabSrvArchitectureTests`. This holds even for ArchUnit classes that bundle several `@ArchTest` rules.
- In JabRef, we don't use `@DisplayName`, we typically just write method name as is. The method name itself should be comprehensive enough.
- Instead of `Files.createTempDirectory` `@TempDir` JUnit5 annotation should be used.
- If `@TempDir` is used, there is no need to clean it up

   Example for wrong code:

   ```java
       @AfterEach
       void tearDown() throws IOException {
           FileUtils.cleanDirectory(tempDir.toFile());
       }
   ```

- Assert the contents of objects (assertEquals), not checking for some Boolean conditions (assertTrue/assertFalse)

   Example for wrong code:

   ```java
           assertTrue(
                   entry.getFiles().stream()
                        .anyMatch(file -> file.getLink().equals(newFile.getFileName().toString()) ||
                                file.getLink().endsWith(\"/\" + newFile.getFileName().toString()))
           );
   ```

- Do not catch exceptions in Test - let JUnit handle

   BAD: try {...code...} catch (IOException e) {
               throw new AssertionError(\"Failed to set up test directory\", e);
           }

   GOOD: ...code...
- When creating a new BibEntry object \"withers\" should be used: Instead of `setField`, `withField` methods should be used.
- Whenever you include a text in FXML (text labels, buttons, prompts in text fields, window titles, etc.), it should be localized.

   To localize a string in FXML, prefix it with `%`.

   Bad example:

   ```xml
   <Label text="Want to help?"/>
   ```

   In this code `text` property is the field that is used to show text to the user. This must be localized.

   Fix:

   ```xml
   <Label text=\"%Want to help?\"/>
   ```

- Plain JUnit assert should be used instead of org.assertj (if possible)

   BAD: assertThat(gitPreferences.getAutoPushEnabled()).isFalse();

   GOOD: assertFalse(gitPreferences.getAutoPushEnabled());

---

## Tests

Agents must:

- Add or update tests when behavior changes
- Keep tests deterministic and fast
- Respect existing JUnit parallelization and resource locks
- Never disable or weaken assertions
- Follow the rules at `docs/code-howtos/testing.md`

If a change cannot be reasonably tested, explain **why**.

### Linting checks

```bash
./gradlew checkstyleMain checkstyleTest checkstyleJmh
./gradlew modernizer
./gradlew --no-configuration-cache :rewriteDryRun || git diff
./gradlew javadoc
npx markdownlint-cli2 "docs/**/*.md"
npx markdownlint-cli2 "*.md"
```

### Fix formatting issues

- Run `./gradlew rewriteRun` to fix Java formatting issues.
- Run `docker run -v $(pwd):/github/workspace ghcr.io/leventebajczi/intellij-format:master "*.java" "" ".idea/codeStyles/Project.xml"` to fix more Java formatting issues.

### Logic tests

```bash
# Recommended during development (core library only)
./gradlew :jablib:check

# Full check (all modules)
./gradlew check

# Per-module
./gradlew :jablib:test
./gradlew :jabgui:test

# Single test class
./gradlew test --tests "org.jabref.logic.l10n.LocalizationConsistencyTest"

# Coverage report (output: build/reports/jacoco/test/html/index.html)
./gradlew jacocoTestReport
```

Tests requiring external resources have dedicated tasks:

- `./gradlew databaseTest` โ€” requires PostgreSQL
- `./gradlew externalServicesTest` โ€” hits live external APIs

Fetcher tests must always hit the live endpoints โ€” do not mock or stub the remote API in fetcher tests.

Quick check of core library:

```bash
./gradlew :jablib:check -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x modernizer
```

---

## Requirements tracing (OpenFastTrace)

JabRef uses [OpenFastTrace](https://github.com/itsallcode/openfasttrace) (OFT) to trace requirements to implementation and tests.
Requirements capture what JabRef should do as a structured representation of issues and features, enabling bidirectional traceability.

For a new feature or significant bug fix, add the requirement to the appropriate `docs/requirements/<area>.md` file.

Read and use the [`openfasttrace`](https://github.com/itsallcode/openfasttrace-ai-skills/blob/main/skills/openfasttrace/SKILL.md) skill.

In JabRef we use OFT this way:

- The requirements are put in `docs/requirements/<area>.md`.
- The requirements are grouped by areas: `docs/requirements/ui.md`, `docs/requirements/git.md`, etc.
- Title style:
  - `req~` โ†’ **"Subject must verb"** (e.g., `## GitHub personal access token push access must be verifiable before sharing`).
  - `feat~` โ†’ **"User can verb"** (e.g., `## User can share library via GitHub`).
  - Avoid nominalizations: Write "must be verified" instead of "verification".
  - One item, one requirement: If a title needs "and", split it into two separate requirements.
  - The title carries the full normative statement: Anyone reading only the heading should understand the complete requirement constraint.
- Use the description only for details that the title cannot carry:
  - Triggering condition.
  - Edge cases and boundary behavior.
  - Brief rationale (if needed).
  - Relevant GitHub issue link or context.

Artifact types used:

- `feat`: User-facing capability or broad feature ("User can verb").
- `req`: Specific constraint, nuance, cross-cutting requirement, or bug fix the system must satisfy ("Subject must verb").
- `impl`: Code implementation.
- `utest`: Unit test.
- `adr`: Architectural Decision Record.

### Traceability chains

In JabRef, we use these traceability chains:

- `feat` --> `impl`/`utest`
- `req` --> `impl`/`utest`
- `feat` --> `req` --> `impl`/`utest`
- `adr` --> `impl`/`utest`

Additional guidelines:

- Link the issue the requirement originates from (if there is).
- Respect INVEST criteria. Prefer high-level requirements over overly detailed ones.
- For ideas or planned requirements not yet implemented, mark them as draft so they are preserved without failing coverage checks: `Status: draft`.

Checking coverage:

```bash
./gradlew traceRequirements   # output: build/reports/tracing.txt
```

See `docs/requirements/` for existing requirements and [docs/code-howtos/requirements.md](docs/code-howtos/requirements.md) for full guidance. Also refer to the `openfasttrace` skill.

---

## Architecture decisions (MADR)

When a significant design or implementation decision is made, create a new MADR in `docs/decisions/`:

1. Copy `docs/decisions/adr-template.md` to `docs/decisions/<NNNN>-<short-title>.md` (next free number).
2. Fill in **Context and Problem Statement**, **Considered Options**, and **Decision Outcome**.
3. Add an entry to `docs/decisions/index.md`.

To link code to a decision, give the ADR an OpenFastTrace identifier directly below its title (no blank line in between)
and declare what has to cover it:

```markdown
# Hardcode `StandardField` names
`adr~hardcode-fieldnames~1`

Needs: impl
```

```java
// [impl->adr~hardcode-fieldnames~1]
```

The identifier's name part must not start with a digit, so drop the file's number prefix.
Add `<!-- markdownlint-disable-file MD022 -->` at the end of the ADR.

See [ADR-0000](docs/decisions/0000-use-markdown-architectural-decision-records.md) for the rationale and [adr-template.md](docs/decisions/adr-template.md) for the full template.

---

## Git & PR Etiquette

### Syncing with upstream

- **Never** use `git rebase`, `git pull --rebase` / `-r` / `--rebase-merges`, or any force-push (`--force`, `--force-with-lease`, `--force-if-includes`, `-f`, or `+`-prefixed refspecs). Rebasing rewrites commit SHAs already pushed and breaks review threads pinned to commits; force-push would then be required to publish the rewritten history.
- **Preferred** sync via explicit fetch + merge:

  ```bash
  git fetch upstream --prune
  git merge upstream/main
  ```

- Plain `git pull` is acceptable for updating the branch as long as your local config does not set `pull.rebase=true` (the enforcement hook blocks the explicit rebase variants regardless).
- Resolve conflicts inside the merge commit. Do not squash or reorder existing commits.
- Before committing the merge, make sure no conflict marker is left: with `merge.conflictStyle=diff3` (the default here) a hunk has **four** markers โ€” `<<<<<<<`, `|||||||` (the common-ancestor block), `=======`, `>>>>>>>` โ€” and a resolution that only removes the outer ones leaves the ancestor block in the file. `git diff --cached --check` reports every leftover marker; run it after staging the resolved files.

### Branches

- `main` is the development branch; pull requests target it. `stable` is the last release plus ported fixes; CI labels a PR `dev: into-stable` when it links a bug issue (maintainers may add or remove the label by hand) and ports it after the merge. Never add that label to a port PR (`port-<number>-to-<branch>`). See [docs/contributing.md](docs/contributing.md#branching-strategy).

### Commits

- One logical change per commit
- Clear, technical commit messages
- Wrap annotations and other `@`-words in backticks in commit messages and PR texts (`` `@Nullable` ``); GitHub turns a plain `@Nullable` into a mention of that user
- Do not reference issues in commits
- Avoid force-pushes
- No generated artifacts unless required

### Pull requests

PR title:

- Contains a short title of the issue fixed (or what the PR addresses), not just \"Fix issue xyz\".

PR body โ€” **must** be built from `.github/PULL_REQUEST_TEMPLATE.md`:

1. Read `.github/PULL_REQUEST_TEMPLATE.md`.
2. Fill every section: \"Related issues and pull requests\", \"PR Description\", \"Steps to test\", \"AI usage\".
3. The PR Description must explain **intent**, not implementation trivia. Do not list modified classes one by one.
4. \"Steps to test\" is a numbered list of concrete steps ending in what the reviewer should see, plus a screenshot cropped to the relevant UI area for every visible change. Never a video: reviewers relate a failure to a step number (\"at step 3 I could not click X\"), which a video does not allow. A video is acceptable only when the interaction involves another program (drag and drop from a file manager, push to a word processor, ...) and the steps are still listed.
5. Write keyboard shortcuts anywhere in the body with `<kbd>` tags: `<kbd>Ctrl</kbd> + <kbd>,</kbd>`, not `Ctrl+,`.
6. Fill \"AI usage\": disclose every AI tool used **and the exact model ID** (for example `Claude Code (model claude-opus-4-7)`).
7. Keep **all** checklist items. Mark each `[x]` (done), `[ ]` (TODO), or `[/]` (not applicable). Never `[ x]` or `[.]`.
8. Remove **all** HTML comments before opening the PR.
9. Write the body to a temp file and run `gh pr create --body-file <file>` โ€” never `--body`, which bypasses the template.
10. Only if the CHANGELOG.md entry used a `TODO` placeholder (meaning no issue has been confidently identified yet โ€” an existing issue link always stays): create the PR with `--draft` (an automated review starts as soon as a PR is ready and would flag the placeholder), immediately after the PR is created replace `TODO` with the real PR-number link (`[#NUM](https://github.com/JabRef/jabref/pull/NUM)`), then commit and push that change, then mark the PR ready (`gh pr ready <number>`). If an issue is identified or created later, switch the link to the issue per the precedence rule above.

---

## Documentation

- Add a CHANGELOG.md entry only if the change is visible to the user.
- Do not add an entry when fixing something that was itself introduced after the last release (e.g. a bug in a feature that only exists in `## [Unreleased]`) โ€” users of the last release never saw the bug. Instead, update the existing unreleased entry if the fix changes what it should say.
- The CHANGELOG.md entry should be for end users (and not programmers).
- **One sentence, maximum 20 words.** No sub-bullets, no code blocks.
- **Describe what changed for the user, never why or how it was implemented.** No class names, method names, or internals.
- Start the entry with `We added` / `We changed` / `We fixed` / `We removed`, and place it under the matching `### Added` / `### Changed` / `### Fixed` / `### Removed` heading in `## [Unreleased]`.
- Within the section, sort the entry in next to existing entries about the same component or feature (e.g. a jabkit fix goes next to the other jabkit fixes) instead of appending it at the end.
- Do not add extra blank lines in CHANGELOG.md
- Do not reorder or reword existing entries (except the unreleased entry your fix relates to, per the rule above), and do not create a new version heading.
- CHANGELOG.md entries link the issue number when an issue exists; the PR number is used only as a fallback when there is no issue.
- When no issue is known and the PR is not yet created, use `TODO` as the issue/PR reference placeholder โ€” never invent a fake number.
- Before using `TODO`, search <https://github.com/JabRef/jabref/issues> and <https://github.com/JabRef/jabref-koppor/issues> for a matching issue. Link it only on a confident match; otherwise list candidates for human review and keep `TODO`. Never use `closes`/`fixes` keywords for a merely-similar issue.
- User documentation is available in a separate repository <https://github.com/JabRef/user-documentation>.
- No AI-disclosure comments inside source code
- Keyboard shortcuts in Markdown (CHANGELOG.md, `docs/`, PR descriptions, issue and review comments) use one `<kbd>` tag per key, first letter capitalized, keys joined by ` + ` (e.g. `<kbd>Ctrl</kbd> + <kbd>Enter</kbd>`).

### CHANGELOG.md example

Good:

```markdown
- We fixed an issue where the entry editor lost focus after saving a library. [#1234](https://github.com/JabRef/jabref/issues/1234)
```

Bad โ€” explains the implementation, names internals, too long:

```markdown
- We fixed a bug in the entry editor where, due to a race condition in the JavaFX
  focus handling inside `EntryEditor#setFocus`, the focus was lost after the library
  was saved. This was especially annoying for users who ... The fix introduces a
  guard flag that ...
```

### Developer documentation

When changing behaviour or adding features, update the relevant files under `docs/`.
For complex flows or new architecture, consider adding a Mermaid sequence or class diagram to the relevant `docs/` file.

- [devdocs.jabref.org](https://devdocs.jabref.org/) โ€” full developer reference. Resides in `docs/`
- `docs/getting-into-the-code/` โ€” workspace setup, code style, IntelliJ config
- `docs/code-howtos/` โ€” localization, testing, fetchers, tools
- `docs/decisions/` โ€” Architecture Decision Records
- `docs/requirements/` โ€” Requirements (OpenFastTrace)

When adding a package or changing a package's or module's public surface, add or update its `package-info.java` / `module-info.java` Javadoc following [skills/developers/module-documentation/SKILL.md](skills/developers/module-documentation/SKILL.md).

When adding or editing a `uses:` line in a workflow, follow [skills/developers/github-actions/SKILL.md](skills/developers/github-actions/SKILL.md) โ€” external actions are pinned to a full commit SHA.

---

## Authority

Human maintainers have final authority.
Agents are assistants, not decision-makers.

When uncertain: **do nothing and ask**.

---

## License

All contributions must comply with JabRef's existing license (MIT).
Do not introduce incompatible licenses or code.

## Standard header block

Use this exact block for all generated files:

```text
> [!IMPORTANT]
> This project does not accept fully AI-generated pull requests. AI tools may only be used for assistance. You must understand and take responsibility for every change you submit.
>
> Read and follow:
> โ€ข [AGENTS.md](./AGENTS.md)
> โ€ข [CONTRIBUTING.md](./CONTRIBUTING.md)
```

### Placement and prominence

- The header must appear before any instructions for tools or contributors.
- Do not bury the header after long intros or tables of contents.

<!-- markdownlint-disable-file MD033 MD041 -->