Skip to content

Contributing

TruePPM is in its early days and contributions are welcome. The project uses GitLab for issue tracking and merge requests.

Looking for something to work on? Browse issues carrying the backlog label — triaged and not yet scheduled. Check that an issue doesn’t already have an open merge request or an assignee before you pick it up.

For anything beyond a typo or one-line fix, open an issue first describing what you want to change and why. This avoids wasted effort if the change conflicts with the roadmap or the OSS/Enterprise boundary below.

Who reviews, and how fast: the maintainer monitors the issue tracker and merge requests, and responds within a few business days.

Community channel: GitLab issues and merge requests are the project’s only channel today — there is no separate chat or forum yet.

Code of conduct: participation is governed by the Contributor Covenant 2.1. Be respectful — disagreement about technical choices is fine, disrespect toward contributors is not.

Governance: who maintains TruePPM, how decisions are made and recorded, whether maintainership is open, and how the open-core boundary is decided are set out in GOVERNANCE.md.

Terminal window
git clone git@gitlab.com:trueppm/trueppm.git
cd trueppm
make setup # installs git hooks via pre-commit
make doctor # verifies all prerequisites

See Installation for Docker Compose setup.

The web app reads build-time settings from packages/web/.env (gitignored) — copy packages/web/.env.example and adjust. Vite only exposes variables prefixed with VITE_, and a change takes effect only after restarting the dev server.

VariableDefaultPurpose
VITE_API_BASE_URLunset (dev proxies to localhost:8000)Deployed API base URL for production builds
VITE_FEATURE_FLAGSunsetJSON build-time defaults for runtime feature flags
VITE_REACT_QUERY_DEVTOOLSoffSet to true to show the React Query devtools panel in dev builds

The React Query devtools panel is off by default so it never occupies screen real estate during normal development. To debug query-cache state, set VITE_REACT_QUERY_DEVTOOLS=true in packages/web/.env and restart Vite. It is gated on dev builds, so it is never present in a production bundle regardless of this value.

Branch from main with a conventional prefix:

Terminal window
git checkout main && git pull origin main
git checkout -b feat/my-feature # or fix/, docs/, chore/, test/, refactor/

Never commit or push directly to main — all changes go through a feature branch and merge request.

Follow Conventional Commits:

feat(web): add board/kanban view
fix(api): prevent duplicate membership creation
docs(scheduler): add Monte Carlo CLI examples
test(api): add permission tests for task deletion
chore(ci): upgrade Node to 22 in CI image

Scopes: scheduler, api, web, helm, sync, docs, ci.

Every MR that touches source code must include a changelog fragment in changelog.d/:

Terminal window
# Naming: <slug>.<type>.md
# Types: added, changed, fixed, security
echo "Add board/kanban view with drag-and-drop" > changelog.d/kanban-view.added.md

The CI changelog:check job blocks the pipeline if the fragment is missing. Fragments are assembled automatically at release time — never edit CHANGELOG.md directly.

Exempt: CI config, dependency bumps, test-only changes, docs-only changes.

Terminal window
make test # runs all packages
# Or per-package:
cd packages/scheduler && pytest
cd packages/api && pytest
cd packages/web && npm test
  • Scheduler: pytest, coverage >= 80%
  • API: pytest with testcontainers PostgreSQL, coverage >= 80%
  • Web: vitest, coverage >= 75%

The API suite bans real outbound network sockets: a test that reaches the live network (usually a misdirected mock) fails fast with a SocketConnectBlockedError instead of hanging on a connect timeout and flaking. Only the configured database and Redis hosts are allowed. A test that genuinely needs the network must opt out explicitly with @pytest.mark.enable_socket — keeping the exception visible and reviewable.

All MRs require a green pipeline before merge.

Run make pre-push before every git push — it mirrors the blocking CI gates (lint, typecheck, migrations-check, schema-check).

PackageFormatterLinterType checker
Schedulerruff formatruff checkmypy
APIruff formatruff checkmypy —strict
Webprettiereslinttsc —noEmit
Terminal window
make lint # runs all linters
make typecheck # runs all type checkers

The product screenshots in the docs (packages/website/src/assets/screenshots/) are captured from a running dev stack loaded with the Atlas Platform Launch sample, not edited by hand. When a UI change makes one stale, re-capture it:

Terminal window
make up
docker compose exec api python manage.py load_sample_project --with-personas
cd packages/website
npm run screenshots # every shot
npm run screenshots -- --only schedule,board
npm run screenshots -- --list # names and routes

The script (scripts/capture-screenshots.mjs) signs in through the API as the demo PM persona (atlas-alex, password demo under DEBUG) and a contributor (atlas-tom) for the personal surfaces, dismisses first-run prompts, and writes WebP files at a fixed 1600×1000 viewport. Add a new surface by appending an entry to its shot list; the ready selector should be something that only renders after the page’s data loads, not the chrome. Reference an image from a page as ![descriptive alt text](../../../assets/screenshots/<name>.webp).

  1. Push your branch and open an MR targeting main
  2. Wait for a green pipeline
  3. Include: description, testing done, screenshots (if UI), issue link
  4. Don’t merge with a failing pipeline — fix the root cause on the branch

Before writing code for a new feature, determine if it belongs in the community or enterprise repo:

  • Community (this repo): everything a PM or program team needs to run a program (including multi-project programs)
  • Enterprise (separate repo): cross-program/portfolio governance, compliance, and org-level policy

The community edition must never import from trueppm_enterprise. Verify with:

Terminal window
make enterprise-boundary-check # OK: no trueppm-enterprise imports in packages

A plain grep -r "trueppm_enterprise" packages/ is not the check. The tree legitimately names the package in extension-point docstrings and ADR pointers — that grep returns 12 lines across 8 files on a clean tree. The gate matches import syntax and quoted module paths, and ignores comments (#2603). The same check runs in CI as boundary:imports.

WebSocket events must have a consumer on both ends

Section titled “WebSocket events must have a consumer on both ends”

Adding a broadcast_board_event() call is half a feature. If nothing on the client registers a handler for the event type, the broadcast reaches the browser and is discarded, and the surface it was meant to refresh stays stale until something else invalidates it. Three separate audits each found a different slice of exactly that, so the reconciliation is now a gate:

Terminal window
make ws-handler-conformance-check # same as the lint:ws-handler-conformance CI job

It cross-references every event type reaching broadcast_board_event() / abroadcast_board_event() in packages/api — through the same AST sweep that enforces the frozen event set, including one level of wrapper indirection — against the on(...) registration table in packages/web/src/hooks/useProjectWebSocket.ts. It runs in both directions and also fails on a duplicate registration, because on() is last-write-wins: a second on('task_updated', …) silently replaces the first rather than adding to it.

When you add an event, add its handler in the same MR. When the omission is deliberate — or the event is structurally undeliverable, as the program_* events are until the program channel ships — record it in packages/api/tests/apps/sync/ws_handler_waivers.py with a reason and the issue number that removes it, and raise that ledger’s budget by one in the same diff. The budget is what makes an addition visible in review; the gate’s own staleness checks force an entry out again once the handler lands.

The job is deliberately not change-gated. Deleting a handler is a web-only diff, which the API test suite never runs on.