Skip to content

Latest commit

 

History

History
546 lines (381 loc) · 21.6 KB

File metadata and controls

546 lines (381 loc) · 21.6 KB

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.

  • 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 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.


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.

./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. 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
  • 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, not JavaDoc inline tags: `code` instead of {@code code}, and [ClassName] instead of {@link ClassName}.

Comments

  • Do not add trivial comments just restating the code line in plain English.
  • When commenting, focus on the "why" and general idea.

Example for trivial comments (to be avoided):

// 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

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

    Following is fine:

    bibEntry.getResolvedFieldOrAlias(...)
            .ifPresent(value -> doSomething(value));
  • If the java.util.Optional is really present, use one of the following:get()

    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 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:

    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:

    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.
  • 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:

    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:

        @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:

            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:

    <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:

    <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

./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

# 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 fetcherTest — hits live external APIs

Quick check of core library:

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

Requirements tracing (OpenFastTrace)

JabRef uses OpenFastTrace to trace requirements to implementation and tests.

For a new feature or significant bug fix, at minimum add the requirement to the appropriate docs/requirements/<area>.md file. Full tracing (Needs: impl + implementation comments) is encouraged but can be skipped if the effort is disproportionate.

Defining a requirement in docs/requirements/<area>.md:

### Example
`req~ai.example~1`

Description of the requirement.

The identifier must follow the heading with no blank line between them. Add <!-- markdownlint-disable-file MD022 --> at the end of the file.

Optionally — linking an implementation to a requirement (full trace):

Needs: impl
// [impl->req~ai.example~1]

Checking coverage:

./gradlew traceRequirements   # output: build/tracing.txt

See docs/requirements/ for existing requirements and docs/requirements/index.md for full guidance.


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.

See ADR-0000 for the rationale and 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:

    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.

Commits

  • One logical change per commit
  • Clear, technical commit messages
  • 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. Fill "AI usage": disclose every AI tool used and the exact model ID (for example Claude Code (model claude-opus-4-7)).
  5. Keep all checklist items. Mark each [x] (done), [ ] (TODO), or [/] (not applicable). Never [ x] or [.].
  6. Remove all HTML comments before opening the PR.
  7. Write the body to a temp file and run gh pr create --body-file <file> — never --body, which bypasses the template.
  8. Only if the CHANGELOG.md entry used a TODO placeholder (meaning no issue has been confidently identified yet — an existing issue link always stays): 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. 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.
  • The CHANGELOG.md entry should be for end users (and not programmers).
  • Do not add extra blank lines in CHANGELOG.md
  • 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

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 — 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)

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:

> [!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.