Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
b1b5a20
Add examples/scripts folder for GrampyScript with Open-dialog previews
dsblank Jul 6, 2026
b4d7801
Move Execute button above the results notebook
dsblank Jul 6, 2026
19b2833
Show the current script's filename in the status area
dsblank Jul 6, 2026
92f3405
Make the filename label bold and borderless, with more spacing
dsblank Jul 6, 2026
64fc471
Prompt to save unsaved changes before New or Open
dsblank Jul 6, 2026
c4cacef
Add update_script_descriptions.py to keep the examples catalog in sync
dsblank Jul 6, 2026
7db2746
Add import support, custom_filter(), and delete() to GrampyScript
dsblank Jul 6, 2026
434f768
Add jedi-based Tab completion to the GrampyScript editor
dsblank Jul 6, 2026
f7e5b92
Fix status label forcing the GrampyScript gramplet wider than its con…
dsblank Jul 6, 2026
6fef538
Add a Help item to the GrampyScript Script menu
dsblank Jul 6, 2026
c24b6fc
Make example script descriptions self-hosted and generated
dsblank Jul 6, 2026
e2640e0
Append () to function completions in GrampyScript editor
dsblank Jul 7, 2026
58bc0a7
Auto-insert single-match completions instead of showing a one-row popup
dsblank Jul 7, 2026
6321511
Give age and back_references proper types in the completion stub
dsblank Jul 7, 2026
d14fd47
Give each record type its own accurate completion fields
dsblank Jul 7, 2026
f2ad0f5
Make completions consistent for callables and drop classes entirely
dsblank Jul 7, 2026
b3d409a
Hint at Tab completion in the status bar and fix New not resetting fi…
dsblank Jul 7, 2026
3a35c8f
Add completion for columns() and other void DSL functions
dsblank Jul 7, 2026
bfe1a75
Close files explicitly instead of relying on GC
dsblank Jul 7, 2026
a01627d
Fix set_*() on nested DataDict2 wrappers silently discarding changes
dsblank Jul 8, 2026
705a9f6
Fix DataList2 re-wrapping already-wrapped items and corrupting root/path
dsblank Jul 8, 2026
e7a0bce
Fix .string on GrampsType-based fields returning raw custom-text, not…
dsblank Jul 8, 2026
e73e334
Fix names property nesting alternate_names and __radd__ reversing order
dsblank Jul 8, 2026
d51fe77
Fix DataList2 fan-out of set_*() methods across all items
dsblank Jul 8, 2026
0966aff
Fix Grampy bulk changes not appearing in Undo/Redo history
dsblank Jul 8, 2026
68bd175
Merge GrampyScript: bundled example scripts, Open-dialog previews, UI…
GaryGriffin Jul 9, 2026
a142b57
GrampsAssistant: document custom_filter() and delete() DSL functions
dsblank Jul 7, 2026
00fb474
Added help URL
dsblank Jul 7, 2026
dd6ee2f
Merge GrampsAssistant: document custom_filter() and delete() DSL func…
GaryGriffin Jul 9, 2026
c9171c9
Add DateOfDeathGramplet - gramplet listing death dates sorted by mont…
javadr Jul 3, 2026
21d1ce4
DateOfDeathGramplet: add proximity sort option, fix cross-calendar so…
javadr Jul 4, 2026
8ddd554
DateOfDeathGramplet: fill translations for updated description string
javadr Jul 4, 2026
3dbc07a
upd: gpr.py
javadr Jul 9, 2026
6eea8bc
Merge DateOfDeathGramplet - gramplet listing death dates sorted by …#973
GaryGriffin Jul 9, 2026
3a58376
lxml: don't pop a blocking dialog at import when gzip/lxml is missing
eduralph Jul 9, 2026
e4f858a
Merge lxml: don't show a blocking dialog at import when lxml/gzip is …
GaryGriffin Jul 9, 2026
a6f2e20
PDFForms: added help URL
dsblank Jul 11, 2026
6cfeb01
Merge PDFForms: added help URL #984
GaryGriffin Jul 13, 2026
80527f1
GrampyScript: fix histogram IndexError, add editor conveniences
dsblank Jul 11, 2026
2f19f7d
Merge GrampyScript: fix histogram IndexError, add editor conveniences
GaryGriffin Jul 13, 2026
c9ae50d
Themes: fix TypeError on preferences_activate(initial_panel=...)
dsblank Jul 15, 2026
0cf9795
Themes: empty tests/__init__.py, GTK pinning now handled repo-wide
dsblank Jul 18, 2026
7466c0b
Merge Themes: fix TypeError on preferences_activate(initial_panel=...…
GaryGriffin Jul 18, 2026
c93d9d9
Form: drop column-size sum-to-100 warning
dsblank Jul 15, 2026
707cfcb
Merge Form: drop column-size sum-to-100 warning#986
GaryGriffin Jul 18, 2026
35ee9ac
Remove WordleGramplet
dsblank Jul 19, 2026
2215697
Fix crash in IsFamilyFilterMatchEvent filter rule
dsblank Jul 19, 2026
6483ea6
Add regression tests for IsFamilyFilterMatchEvent
dsblank Jul 19, 2026
f2344d6
Drop redundant GTK/GDK pinning in filter rule test
dsblank Jul 19, 2026
32b0eeb
Merge Fix crash in IsFamilyFilterMatchEvent filter rule#990
GaryGriffin Jul 19, 2026
5868a5c
Add the Addon Development manual under docs/addon-development
eduralph Jul 20, 2026
813e841
Point README and CONTRIBUTING at the in-repo addon manual
eduralph Jul 19, 2026
e40e519
Strip trailing whitespace in Python source files
eduralph Jul 20, 2026
3eaf557
AssociationsTool: stop shadowing the gettext _ with a loop variable
eduralph Jul 20, 2026
2ad10b0
MediaVerify auto update media object names when they match old filena…
riskysnail Jul 20, 2026
6a56efe
Merge MediaVerify auto update media object names #12610 #997
GaryGriffin Jul 24, 2026
5c6572c
Add AGENTS.md as a tool-agnostic mirror of CLAUDE.md
dsblank Jul 19, 2026
3c7164f
Update AGENTS.md
dsblank Jul 20, 2026
d17a25a
Address review feedback from Nick-Hall on PR #991
dsblank Jul 20, 2026
2dc7050
Add Windows Development section addressing MSYS2 vs native Windows
dsblank Jul 26, 2026
04077f3
PersonRelationshipFilter: fix HandleError crash and mismatched name f…
dsblank Jul 17, 2026
06881f1
PersonRelationshipFilter: fix help_url to reference this addon's own …
dsblank Jul 17, 2026
0f1623a
Apply suggestion from @dsblank
dsblank Jul 23, 2026
76539a0
Fix ImportError in PersonRelationshipFilter test module import
dsblank Jul 26, 2026
d8c0f4f
Merge PersonRelationshipFilter#987
GaryGriffin Jul 26, 2026
26e0d5b
Sync addons-source@maintenance/gramps61 with upstream/gramps-project …
github-actions[bot] Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
310 changes: 310 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
# Agent Guidelines for gramps-project/addons-source

This document specifies rules and conventions that agents should follow when
making changes to this repository.

For the normative addon rules — MUST / SHOULD / MAY, with the origin of
each rule (upstream PR, maintainer ruling, Mantis id) cited inline — see
[docs/addon-development/16-guidelines.md](docs/addon-development/16-guidelines.md)
from the Addon Development manual (PR 994). This document covers the
hands-on agent workflow around those rules.

## Repository Overview

`addons-source` holds the source code for third-party Gramps addons —
gramplets, views, tools, reports, importers/exporters, filter rules, database
backends, and other plugins that are not part of Gramps core. It is one of
three tightly-linked repositories:

- **`gramps`** — Gramps core (the application these addons plug into).
- **`addons-source`** (this repo) — addon source code, developed here.
- **`addons`** — built `.addon.tgz` packages and listing JSON consumed by the
in-app Plugin Manager. Populated from this repo via `make.py`; not edited
by hand except by the maintainer doing a release build.

There is no unified build or CI pipeline for the whole repo (the checked-in
`.travis.yml` is stale/unused) — each addon is largely independent.

## Branch Model

Branches are per Gramps major or feature release, e.g. `maintenance/gramps60`,
`maintenance/gramps61` — not one branch per patch release. **Almost all addon
work should target the maintenance branch matching the current released
Gramps version.** A `master` branch exists but is not currently used for
addon work.

Pick the branch carefully:
- When creating a new addon or fixing an existing one, branch from
`origin/maintenance/gramps6X` (the current release line), not from a local
tracking branch that may carry unrelated WIP commits.
- When opening the PR, target that same branch on
`gramps-project/addons-source`.
- Before pushing, sync/rebase against upstream (`git pull --rebase`) so the
PR applies cleanly.

All PRs must come from a personal fork, never from a branch pushed directly
to `gramps-project/addons-source` (see `CONTRIBUTING.md`, "Commit Your
Changes": "you want to put your changes into _your_ fork, _not_ the upstream
`gramps-project/addons-source` repository").

Weblate translation PRs are the one exception where commits must **not** be
squashed on merge; everything else can be merged/rebased normally.

## Repository Layout

Each addon is a single top-level directory, CamelCase-named to match its
Python import name (e.g. `FilterRules/`, `DataEntryGramplet/`). A directory
typically contains:

- `AddonName.py` — the implementation.
- `AddonName.gpr.py` — the Gramps plugin registration file (required; see
below).
- `po/` — translation `.po` files, managed by `make.py`.
- `tests/` — optional unit tests (see Testing below).
- `MANIFEST` — optional; lists extra files (docs, data) to include in the
built package beyond the default `*.py`, `*.glade`, `*.xml`, `*.txt`,
`locale/*/LC_MESSAGES/*.mo`.
- `locale/` — generated `.mo` files; not something you hand-edit.

Addon directories generally have **no `__init__.py`** (they rely on Python 3
namespace packages) so that Gramps can add each one to `sys.path` at load
time and other addons can `import AddonName` directly.

## Commands

Addon packaging/translation tasks go through `make.py`, run from the repo
root. Point `GRAMPSPATH` at wherever *your* local Gramps checkout lives (it
defaults to `../../..`, i.e. it assumes `make.py` is being invoked from one
level inside a sibling-layout workspace — don't rely on that default, always
set it explicitly), and make sure that checkout has the matching branch
checked out too (e.g. `maintenance/gramps61` in core when building against
`gramps61` here). Setting `LANGUAGE=en_US.UTF-8` is no longer required on
Gramps v6.0 and later:

```bash
GRAMPSPATH=/path/to/your/gramps python3 make.py gramps61 build AddonDirectory
```

Common subcommands (first positional arg is the branch/version tag, e.g.
`gramps61`):

- `init AddonDirectory [lang]` — scaffold dirs / `.pot` for a new addon, or
an initial `po/<lang>-local.po`.
- `update AddonDirectory <lang>` — refresh a language `.po` from the `.pot`.
- `compile [AddonDirectory|all]` — compile `.po` → `.mo`.
- `build [AddonDirectory|all]` — produce the downloadable `.addon.tgz`.
- `listing [AddonDirectory|all]` — generate/update the Plugin Manager
listing JSON.
- `clean AddonDirectory` — strip generated files (`locale/`, `*.pot`, etc.)
before committing.
- `manifest-check` — validate `MANIFEST` files.
- `as-needed` — build/list/clean only what's out of date, repo-wide.

Unlike `GRAMPSPATH`, the `build`/`listing`/`check` subcommands write to a
**hardcoded relative path**, `../addons/<version>/...` — this isn't a
convention, it's how `make.py` itself resolves the output location, so it
only works if you've cloned the `addons` repo as a sibling of
`addons-source` (see `MAINTAINERS.md` for the recommended three-repo
workspace layout: `gramps` / `addons` / `addons-source` side by side). You
don't need that sibling checkout at all unless you're packaging/publishing
an addon.

You do not need `make.py` just to write/test an addon's Python code — only
when packaging, translating, or updating the listing.

## Testing

Not every addon has tests, but when adding or fixing one, add regression
tests under `AddonName/tests/`, mirroring existing examples (`Form/tests/`,
`DataEntryGramplet/tests/`, `Sqlite/tests/`, `FilterRules/tests/`).

Conventions here differ from Gramps core:

- Test files are named `test_*.py` (pytest-style), **not** the `*_test.py`
suffix core Gramps uses — `unittest discover` here is invoked per-addon or
from the repo root without the core `-p "*_test.py"` pattern.
- Still use the `unittest` framework (`unittest.TestCase`), not `pytest`
APIs, even though filenames follow the `test_*.py` convention.
- `AddonName/tests/__init__.py` should be **empty**. Do not add
`gi.require_version(...)` pinning there or in individual test modules —
see GTK/GDK below.
- Because the addon directory has no `__init__.py`, test modules need a
`sys.path` hack to import it as a namespace package:

```python
ADDON_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if ADDON_DIR not in sys.path:
sys.path.insert(0, ADDON_DIR)
```

then `from AddonName.addonmodule import SomeClass`.
- Build test data programmatically against a real (temp-dir) database rather
than relying on fixtures that don't exist in this repo:

```python
from gramps.gen.db.utils import make_database
from gramps.gen.db import DbTxn

db = make_database("sqlite")
db.load(tempfile.mkdtemp(prefix="myaddon_"))
with DbTxn("build test db", db) as txn:
...
```
- If a test needs `gramps.gen.filters.CustomFilters`, call
`reload_custom_filters()` **before** importing the `CustomFilters` name
from `gramps.gen.filters` — that function rebinds the module-level global
rather than mutating it in place, so importing the name first leaves you
with a stale `None`.

### GTK / GDK version pinning

The repo-root `tests/__init__.py` pins `gi.require_version("Gtk", "3.0")` and
`gi.require_version("Gdk", "3.0")` for the whole test suite (added in PR
#950). `gi.require_version` sets process-global state, so **individual addon
test files should not re-pin GTK/GDK** — that's now redundant. A new test
module that imports something pulling in `gramps.gui.*` (which happens at
import time for most GUI-facing addons) only needs a guard for hosts with no
PyGObject at all:

```python
try:
import gi
except ImportError as err:
raise unittest.SkipTest("PyGObject not available: %s" % err)
```

Do not add `gi.require_version(...)` calls back into new test files — if you
see them in an addon you're touching, they're safe to remove as redundant
(see the `Themes` addon's `tests/__init__.py` cleanup for precedent), but
don't remove them from files you aren't otherwise changing.

### Running tests

```bash
export GRAMPS_RESOURCES=/path/to/gramps/build/share # built Gramps checkout
export GDK_BACKEND=-
python3 -m unittest AddonName.tests.test_something -v
```

Run from the `addons-source` repo root so the addon's namespace package
resolves.

## Windows Development

Per `CONTRIBUTING.md`, addons here are developed and tested primarily on
Linux; Windows and macOS are possible but far less thoroughly reviewed.
"Windows" itself covers two different things agents should not conflate:

- **MSYS2** — the POSIX-like shell/toolchain environment Gramps's own
Windows installer is built with. A shell running inside MSYS2 behaves like
a Linux shell (`bash`, POSIX paths) and can run this repo's `make.py` and
`unittest` invocations normally.
- **Native Windows** — a bare `cmd.exe` or PowerShell host with no MSYS2 or
WSL layer underneath. The commands and paths used elsewhere in this
document (`make.py`, `python3 -m unittest ...`, the `GRAMPSPATH` /
`GRAMPS_RESOURCES` env vars) assume a POSIX shell and are not verified
against native `cmd`/PowerShell.

If you are running as an agent on a native Windows host:

- Reading, writing, and refactoring addon Python code is fine — the code
itself is portable.
- Do not run this repo's tests or `make.py` directly in `cmd`/PowerShell and
report the result as a real pass/fail — the tooling isn't validated there,
so a failure may be environmental noise rather than an actual regression.
- Ask the user to run the same command inside an MSYS2 shell, WSL, or a
Linux container/VM and share the output, rather than guessing at the
result yourself.

If you're already inside MSYS2 or WSL, treat it as a POSIX shell and follow
the rest of this document (Commands, Testing) as written.

## Code Style

- Every new `.py` file needs the same GPL-2.0-or-later header with copyright
used throughout Gramps core.
- Group imports under comment-banner sections (`Standard Python modules`,
`GTK/Gnome modules`, `Gramps modules`), matching the style already used
across this repo (e.g. `FilterRules/isfamilyfiltermatchevent.py`).
- [Black](https://black.readthedocs.io/) formatting is not currently required
for addons — there's no checked-in CI workflow enforcing it here, though
that may change in the future. The existing codebase is largely
Black-formatted, so running `black <file>` on files you're already
touching is a reasonable default.
- Docstrings: concise; full Sphinx `:param:`/`:returns:` style only when the
parameters aren't already obvious from the signature.

## Internationalization

Addons manage their own translations, separately from Gramps core, though
both go through the same Weblate instance: strings already translated in
Gramps core are matched first, so those strings are excluded from the
Weblate _Addons_ component and don't need separate translation there. At the
top of an addon's main module:

```python
from gramps.gen.const import GRAMPS_LOCALE as glocale

try:
_trans = glocale.get_addon_translator(__file__)
except ValueError:
_trans = glocale.translation
_ = _trans.gettext
```

(`get_addon_translator` raises `ValueError` if the addon has no
`locale/` translations yet — fall back to the core translator rather than
letting the import crash.) Use `ngettext(singular, plural, n)` instead of a
single `_()` call for any string that counts a noun, for the same reason as
Gramps core: many languages have plural rules English doesn't, and only
`ngettext` lets gettext apply the target language's actual rule.

## Plugin Registration (`.gpr.py`)

Every addon needs an `AddonName.gpr.py` alongside `AddonName.py`:

```python
register(
RULE, # or TOOL, GRAMPLET, REPORT, VIEW, GENERAL, IMPORT, EXPORT, ...
id="uniqueid",
name=_("Human-Readable Name"),
description=_("What it does."),
version="1.0.0",
gramps_target_version="6.1",
status=STABLE, # or UNSTABLE
fname="AddonName.py",
authors=["Author Name"],
authors_email=["author@example.com"],
maintainers=["Maintainer Name"],
maintainers_email=["maintainer@example.com"],
help_url="Addon:WikiPageName",
... # PTYPE-specific attributes, e.g. ruleclass/namespace for RULE
)
```

`gramps_target_version` and `version` are required. `help_url` should point
at the addon's Gramps wiki page (`Addon:PageName`), not a GitHub URL.
`maintainers`/`maintainers_email` identify who currently maintains the
addon and may differ from `authors`/`authors_email` (the original author)
once upkeep passes to someone else.

> **Do not manually change `version` in a PR.** The `MAJOR.MINOR.PATCH`
> version in `.gpr.py` is bumped automatically by the `addons` repo's
> packaging build (`make.py`) when a change is released — the patch
> component increments on its own. Hand-editing it in a source PR just
> creates spurious diffs and can conflict with what the release build
> assigns; leave it as-is unless you're intentionally doing a MAJOR/MINOR
> bump for a breaking addon change.

## Bug Reports and Commit Messages

Bugs against addons are tracked in two places: the shared
[Gramps Mantis BT](https://gramps-project.org/bugs/view_all_bug_page.php)
and, informally, the [Gramps Discourse forum](https://gramps.discourse.group/)
— many addon issues start as a discourse thread rather than a Mantis report.
Unlike Gramps core, this repo has no automated changelog tooling parsing
commit messages, so there's no required `Fixes #N` keyword syntax — just
describe *why* the change was made, and link whatever report (Mantis bug or
discourse URL) motivated it if one exists. Don't invent a Mantis bug number
if you only have a discourse link or user report — link what you actually
have.
Loading