Development process
Work on Factflow is issue-first and lands on dev. A GitHub issue is the unit of work, a design doc explains it, a branch implements it, and a PR closes it. main only ever moves during a release.
Everything on this page is plain git and GitHub. The /ff- commands automate these steps, and the steps hold whether or not you use them.
Issue → merged PR
Section titled “Issue → merged PR”- Create the issue. Describe why and what — never how. No file paths, no code structures in the issue body; those live in the design doc and go stale the moment the implementation moves.
- Write the design doc at
.claude/issues/issue-{N}/design.md. Required for anything non-trivial (skip only for typos, one-line changes, dependency bumps). It must be self-contained: copy the relevant specs in rather than referencingai_docs/or another doc by path, because planning docs are transient. The issue body links to it. - Branch off
dev. - Implement, keeping
.claude/issues/issue-{N}/task.mdchecked off as you go. - Self-review the diff before asking anyone else to. Run the gates for every stack you touched.
- Open the PR against
devwithfixes #Nin the body, so the merge closes the issue. - Get it reviewed — see Review and merge. Peer review is the default; self-review is the fallback.
- Merge it yourself once approved. The author merges, never the reviewer.
- Archive after merge — move
.claude/issues/issue-{N}/to.claude/history/issue-{N}/. Not before: the folder is live working state until the PR lands.
Both .claude/issues/ and .claude/history/ are tracked, so the reasoning behind a change stays in the repo next to the change itself.
Review and merge
Section titled “Review and merge”Peer review is the default. Assign a reviewer and wait for them:
GH_HOST=dnb.ghe.com gh pr edit <PR> --repo radicalAI/factflow --add-reviewer HANDLEFrom there the work is the reviewer's, not yours. The reviewer:
- Runs
/ff-review <PR>— checks out the branch, reads the changed files rather than only the diff, runs the gates for every affected stack, and submits a structured review. - Runs
/ff-address <PR>to carry out the fixes their review called for, pushing them to the same branch. - Approves, or pre-approves if something still needs your input.
You merge. The author merges their own PR once it is approved and CI is green — the reviewer approves but does not merge.
GH_HOST=dnb.ghe.com gh pr merge <PR> --repo radicalAI/factflow --mergeWhen no teammate is available
Section titled “When no teammate is available”Self-review is the fallback, not a shortcut to be preferred — take it when no teammate is around and the change does not warrant waiting for one. Run /ff-review against your own PR and act on what it finds.
One constraint shapes this: GitHub will not let you approve your own PR. A self-review must be submitted with --comment, never --approve or --request-changes. /ff-review checks the PR author against the current user and switches automatically, so a self-reviewed PR carries a review comment rather than an approval. Merge on green CI.
| Peer review | Self-review | |
|---|---|---|
| Who reviews | Assigned teammate | Author |
Who runs /ff-review | Reviewer | Author |
Who runs /ff-address | Reviewer | Author |
| Review verdict | --approve / --request-changes | --comment only |
| Who merges | Author | Author |
Branch names
Section titled “Branch names”| Kind | Pattern |
|---|---|
| Feature | feature/issue-{N}-{short-description} |
| Bug fix | fix/issue-{N}-{short-description} |
| Docs | docs/issue-{N}-{short-description} |
| Refactor | refactor/issue-{N}-{short-description} |
| No issue | fix/{short-description} |
| Hotfix | fix/hotfix-{short-description} → main, cherry-picked to dev |
Everything targets dev. Only a release PR targets main.
Commits
Section titled “Commits”type(scope): descriptionTypes are feat, fix, docs, refactor, test, chore. Scope is backend, frontend, cli, or omitted for cross-stack work.
- Keep them short — two lines maximum.
- Split by type. Don't mix a feature, a fix and a docs change in one commit.
- Stage explicit paths. Never
git add .— the repo has gitignored.envfiles and confidential pipeline output sitting next to tracked code. - No AI attribution, ever. No
Co-Authored-Bytrailers, no tool URLs.
Labels
Section titled “Labels”Scope labels are required — at least one. Sync the full set with just labels, which applies scripts/github/setup-labels.sh.
| Group | Labels |
|---|---|
| Scope | backend, frontend, cli, cross-stack, api |
| Type | bug, enhancement, refactoring, documentation |
| Triage | good first issue, help wanted, question, duplicate, invalid, wontfix |
api means the change alters an API contract, which obliges you to update the frontend types and the CLI types in the same PR. cross-stack means coordinated work across backend, frontend and CLI — start with the backend, since it's the source of truth for response shapes.
Local setup
Section titled “Local setup”The four apps are independent projects with their own toolchains. A justfile at the repo root wraps the common entry points; just on its own lists everything.
just install # all dependencies (uv + bun + go)just docker-up # PostgreSQL + Artemisjust serve-backend # backend in embedded modejust serve-frontend # frontend dev serverjust docker-reset stops services and removes volumes for a clean slate — use it after a schema change. just docker-wipe additionally removes orphaned testcontainers.
Gates before you push
Section titled “Gates before you push”Run the checks for every stack you touched. CI runs the same ones, so a local failure is a CI failure you haven't waited for yet.
| Stack | Commands |
|---|---|
| Backend | cd backend && uv run ruff check . && uv run ruff format --check . && uv run ty check |
| Backend tests | cd backend && uv run pytest packages/<package>/tests/ -v |
| Frontend | cd frontend && bun run check && bun run test && bun run build |
| CLI | cd cli && go build ./cmd/factflow/ && go vet ./... && go test -race ./... |
| Docsite | cd docsite && bun run build |
Paths in this repo are written relative to the repo root, but the per-stack tools run from their own directory — hence the cd in every row.
Three things that catch people out:
- Never run bare
uv run pytest. Always give it a path. The full suite starts Testcontainers for PostgreSQL, Artemis, RabbitMQ and Pulsar; unscoped runs take a long time and tell you little. (A release is the one place that runs it unscoped, on purpose.) - Run ruff across the whole backend, not the files you edited.
ruff format --check .is what CI runs, and per-file invocations miss formatting in files your change pulled in. bun run checkdoes not typecheck. Onlybun run buildrunstsc -b. A greencheckwith a broken type is entirely possible.
Python always goes through uv. Never python3 or .venv/bin/python directly — a stray root venv with stale versions will answer instead.
Exercising a pipeline end to end
Section titled “Exercising a pipeline end to end”Unit and integration tests live beside their packages. Above them sits the test-cli harness, which drives real configs through a running backend. Run it from the repo root:
just test-cli help # list scenariosjust test-cli s1 # or: ./scripts/test-cli/run.sh s1Scenarios s1 through s14 are numbered step sequences you can run whole (s7), as a range (s7 4 8), or one step at a time (s7.4). AUTO=1 runs unattended instead of waiting on Enter between steps.
See Testing an adapter for where the harness sits relative to unit and integration tests and how to add a scenario, and Verifying delivery at volume for s14, which drives synthetic load through the real engine to prove message delivery holds under redelivery pressure.
End-to-end tests under tests/e2e/ are excluded from CI deliberately — scenarios can run for hours. They are a local gate; run them before merging anything that touches orchestration.
Where the conventions live
Section titled “Where the conventions live”The authority is the repo, scoped by file path:
CLAUDE.md— monorepo layout, per-app commands, the issue workflow.claude/rules/— constraint files that apply to matching paths, described in Coding assistants