You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by gc...@apache.org on 2026/07/16 16:57:32 UTC

(allura) branch claude-init updated: fixup! fixup! added CLAUDE.md

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch claude-init
in repository https://gitbox.apache.org/repos/asf/allura.git


The following commit(s) were added to refs/heads/claude-init by this push:
     new 9c8072b28 fixup! fixup! added CLAUDE.md
9c8072b28 is described below

commit 9c8072b287776ef5a618c2d921b3be0be9e5cfa5
Author: Guillermo Cruz <gu...@slashdotmedia.com>
AuthorDate: Thu Jul 16 10:57:23 2026 -0600

    fixup! fixup! added CLAUDE.md
---
 CLAUDE.md | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 146 insertions(+)

diff --git a/CLAUDE.md b/CLAUDE.md
index 71b1a033a..b5caa0dbb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -24,6 +24,12 @@ This is a multi-package monorepo. Each top-level directory is an independently `
 - `scripts/` — one-off admin/migration scripts (run with the app's Python env), e.g.
   `scripts/convert_encrypted_field.py`, `scripts/migrations/`.
 
+Sibling repos extend this one as plugins rather than living in this monorepo: SourceForge's commercial
+layer `forge-classic` (dev checkout typically `/src/forge-classic`, deployed `/var/local/forge-classic`)
+and its theme `sftheme` (`/src/sftheme`, `/var/local/sftheme`). This repo has zero references back to
+either — the dependency runs one-way, entirely through the entry-point mechanism described below. See
+"Working with sibling extension repos" further down for how to debug across that boundary.
+
 Each Forge package registers itself with the core platform via entry points declared in its
 `pyproject.toml` (older packages may still show these in `setup.cfg`/`*.egg-info`), most importantly:
 ```toml
@@ -148,6 +154,146 @@ see `allura.lib.macro` for existing examples.
 project-registration providers, spam filters, phone/2FA services, WSGI middleware, and more — all wired via
 entry points. Full list in `Allura/docs/development/extending.rst`.
 
+## Development patterns
+
+- **Adding a new tool**: subclass `allura.app.Application`, then register it under
+  `[project.entry-points.allura]` in that package's `pyproject.toml` (e.g. `Tickets =
+  "forgetracker.tracker_main:ForgeTrackerApp"`). Code alone doesn't register a tool — without the entry
+  point, `g.entry_points['tool']` never sees it (see `Allura/allura/lib/app_globals.py:346-352`) and it
+  won't be mountable, won't appear in the admin "add tool" list, etc.
+- **Adding a Markdown macro**: decorate a function with `@macro()` from `allura.lib.macro`, register it
+  under `[project.entry-points."allura.macros"]`. The entry point *name* is arbitrary; the macro is
+  invoked in Markdown by the function's own name (`[[hello]]`). See
+  `Allura/docs/development/extending.rst` for the full list of extension points (auth providers, theme
+  providers, spam filters, phone services, WSGI middleware, webhooks, `allura.timers`, etc.) — most
+  follow this same "subclass + entry point" shape.
+- **Resolving duplicate entry-point names**: if two installed packages register the same name under an
+  `allura`/`allura.*` group (e.g. a sibling repo overriding a stock tool), `iter_entry_points`
+  (`Allura/allura/lib/helpers.py:1193-1237`) requires one registered class to be a subclass of the
+  other(s); otherwise it raises `ImportError('Ambiguous ... entry points ...')` at the point something
+  tries to use that group. When subclassing another tool specifically to reuse its entry-point name,
+  make sure the inheritance is real (not just structurally similar).
+- **Disabling an entry point without removing the package**: `disable_entry_points.<group> = name1,
+  name2` in the `.ini` (see examples in `Allura/development.ini:519-525`, e.g.
+  `disable_entry_points.allura.importers = trac-tickets`). Useful for turning off a stock or
+  third-party plugin at config time instead of uninstalling it.
+- **Context helpers for scripts/tests**: `allura.lib.helpers.set_context(project_id, mount_point=...,
+  app_config_id=..., neighborhood=...)` sets `c.project`/`c.app` outside normal request dispatch; the
+  `push_config(obj, **kw)` context manager temporarily overrides attributes on any object (e.g. `c`, `g`,
+  a model instance) and restores them on exit — prefer it over manual save/restore boilerplate in tests
+  and scripts.
+- **Migration scripts** (`scripts/migrations/`): numbered `NNN-description.{py,js,sh}` files, run once
+  against a real environment and not re-run automatically — there's no migration-runner/version table,
+  just sequential numbering as a changelog. `.js` files are raw `mongo`/`mongosh` scripts; `.py`/`.sh`
+  ones are typically invoked via `paster script <ini> <script.py> -- <args>` (see "Debugging" below).
+  Field-conversion migrations often come in pairs — a `-field-encryption.sh`-style script that converts
+  values, and a later `...-cleanup.sh` companion that removes the old unencrypted copies once the
+  conversion is verified (e.g. `scripts/migrations/037-field-encryption.sh` /
+  `038-field-encryption-cleanup.sh`, both thin wrappers around
+  `scripts/convert_encrypted_field.py`). Follow that shape (convert now, clean up in a later,
+  separately-run script) for any similarly risky data migration.
+
+## Debugging
+
+- **Tracing a request**: dispatch for every request starts in
+  `Allura/allura/controllers/root.py`. If the request is handled by a tool, find the tool's entry point
+  under `[project.entry-points.allura]` in its `pyproject.toml`/`setup.py`, load the `Application`
+  subclass it points to, and follow its `root` attribute — that's the tool's root controller. Failing
+  that, grep for static text from the rendered page's HTML, or the template filename once you find it.
+  (`Allura/docs/development/contributing.rst` has more detail.)
+- **Interactive debugging (`ipdb`)**: insert `import ipdb; ipdb.set_trace()` at the line of interest,
+  then run the relevant process in the foreground so the debugger has a console to attach to:
+  ```bash
+  cd Allura
+  pkill -f gunicorn; gunicorn --reload --paste development.ini -b :8080      # web
+  pkill -f taskd; paster taskd development.ini --nocapture                    # taskd
+  ```
+  Docker equivalent:
+  ```bash
+  docker compose run --rm web pip install ipdb
+  docker compose stop web taskd
+  docker compose run --rm --service-ports web gunicorn --reload --paste Allura/docker-dev.ini -b :8088
+  docker compose run --rm taskd paster taskd docker-dev.ini --nocapture
+  ```
+- **One-off scripts**: `paster script <ini> <script.py> -- <args>` (implemented by
+  `allura.command.script:ScriptCommand`, registered under `[project.entry-points."paste.paster_command"]`
+  in `Allura/pyproject.toml`) loads the full app config (DB bindings, TG globals, logging) and executes
+  the script inside that context — no serving loop. Add `--pdb` to drop into `pdb.post_mortem()` on an
+  uncaught exception instead of just printing a traceback.
+- **Entry points silently not registering**: `Globals.__init__` caches every entry-point group once, per
+  process, into `g.entry_points` (`Allura/allura/lib/app_globals.py:328-338`, `_cache_eps`) — and that
+  cache function swallows exceptions from a failed `ep.load()`, only logging `Could not load entry
+  point [%s] %s` (`resources.py` similarly logs `Cannot import entry point %s` for static-resource
+  registration). If a class you expect to see registered (your own, or a sibling package's) isn't
+  showing up in `g.entry_points[...]`, check the web/taskd process logs for those messages before
+  assuming the entry point declaration itself is wrong — an import error inside the target module fails
+  silently from the caller's perspective. Also remember `g` is built once at process startup
+  (`Allura/allura/lib/app_globals.py:212-217`): adding/editing an entry point (even via `pip install -e`)
+  needs a process restart, a code *reload* (`--reload`) is not enough. Escalating checks, from
+  outside-in:
+  ```bash
+  # 1. is it discoverable at all, independent of Allura's own filtering?
+  python -c "import importlib.metadata as m; [print(ep) for ep in m.entry_points(group='allura.theme.override')]"
+  ```
+  ```python
+  # 2. is Allura's own loader (dedup/subclass/disable filtering) seeing it? run inside
+  #    `paster script development.ini <tmp_script.py>` (see docs/getting_started/administration.rst)
+  from allura.lib import helpers as h
+  list(h.iter_entry_points('allura.theme.override'))
+  list(h.iter_entry_points('allura.admin'))
+  # or inspect the live cache directly:
+  import tg; tg.app_globals.entry_points['admin']
+  ```
+  There's no `paster shell`/REPL command registered in this stack for ad hoc poking — use a throwaway
+  script under `Allura/scripts/` with `paster script` instead.
+
+## Working with sibling extension repos (forge-classic, sftheme)
+
+`forge-classic` layers SourceForge-specific tools, overrides, and business logic on top of this Allura
+checkout purely via the entry-point mechanisms above — see its own `CLAUDE.md` for what it registers.
+From this side, a few things make debugging across that boundary easier:
+
+- **Template overrides**: a sibling package's `override/` directory (e.g. forge-classic's
+  `ForgeSF/forgesf/override/`) is spliced into the Jinja search path by
+  `allura.lib.package_path_loader.PackagePathLoader`, driven by that package's
+  `[project.entry-points."allura.theme.override"]` registration. Default resolution order puts each
+  override just before the stock `allura` path (`_load_paths`,
+  `Allura/allura/lib/package_path_loader.py:162-174`); a package can reorder itself relative to another
+  override or to `allura` via a `template_path_rules = [['>', 'other-ep-name']]` class attribute
+  (`>`/`<`/`=`, see the module docstring at the top of that file for the full rule syntax). If a page
+  doesn't reflect an expected override, set `disable_template_overrides = true` in the `.ini` temporarily
+  to confirm what stock Allura renders, then check `template_path_rules`/override ordering rather than
+  assuming the template content itself is wrong. To see the actual resolved search order (which
+  directory wins), instantiate the loader directly in a `paster script`:
+  ```python
+  from allura.lib.package_path_loader import PackagePathLoader
+  PackagePathLoader().init_paths()   # ordered list of override dirs actually being searched
+  ```
+  A common failure is the physical directory being nested one level off — the convention is
+  `override/<top-level-package-of-the-module>/templates/...` (see the "magic directory" example in
+  `package_path_loader.py`'s module docstring), not `override/<full.dotted.module>/...`.
+- **Non-template overrides are monkeypatches, not templates**: some `override/` files are plain Python
+  that reassigns methods/attributes directly onto imported Allura classes at import time (forge-classic's
+  `repo_monkey_patching.py`, imported eagerly from its package `__init__.py`, is one example). If
+  behavior differs from what this repo's source implies, grep the sibling package for the Allura
+  class/method name before assuming you're looking at stock behavior — check whether the specific
+  override file in question is a template or a patch, since the two mechanisms are unrelated. Allura's
+  own idiomatic monkeypatch shape is `allura.lib.helpers.monkeypatch(*objs)` (used internally in
+  `allura/lib/patches.py`), which only takes effect once its module is actually imported — if a
+  sibling-package patch isn't applying, confirm its module is really being imported (e.g. as a side
+  effect of package `__init__.py`, or of an `allura.command_init`/`allura.macros` entry-point load) before
+  assuming the patch logic itself is wrong.
+- **Shared MongoDB, independently resolved dependencies**: sibling repos typically point at the *same*
+  physical Mongo (so a data bug can originate in either codebase's model layer — check both), but each
+  repo's `requirements.txt` is compiled independently, so their installed dependency versions can drift
+  even when both declare the same package. A sibling repo's test config often extends this repo's
+  `Allura/test.ini` via ConfigParser's `use = config:...` inheritance — when a test only fails in the
+  sibling repo, diff the two `.ini` files to see what's inherited vs. locally overridden before assuming
+  it's a real behavioral difference.
+- **Ambiguous/duplicate entry-point names across repos** hit the same `iter_entry_points` subclass
+  check described above — a sibling package overriding a stock tool/app under the same entry-point name
+  must genuinely subclass it, or loading that group raises `ImportError` at runtime.
+
 ## Miscellaneous conventions
 
 - Timezone safety: always use UTC-explicit datetime functions (see Linting section above) — this is enforced