Skip to content

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.

  1. 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.
  2. 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 referencing ai_docs/ or another doc by path, because planning docs are transient. The issue body links to it.
  3. Branch off dev.
  4. Implement, keeping .claude/issues/issue-{N}/task.md checked off as you go.
  5. Self-review the diff before asking anyone else to. Run the gates for every stack you touched.
  6. Open the PR against dev with fixes #N in the body, so the merge closes the issue.
  7. Get it reviewed — see Review and merge. Peer review is the default; self-review is the fallback.
  8. Merge it yourself once approved. The author merges, never the reviewer.
  9. 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.

Peer review is the default. Assign a reviewer and wait for them:

Terminal window
GH_HOST=dnb.ghe.com gh pr edit <PR> --repo radicalAI/factflow --add-reviewer HANDLE

From there the work is the reviewer's, not yours. The reviewer:

  1. 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.
  2. Runs /ff-address <PR> to carry out the fixes their review called for, pushing them to the same branch.
  3. 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.

Terminal window
GH_HOST=dnb.ghe.com gh pr merge <PR> --repo radicalAI/factflow --merge

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 reviewSelf-review
Who reviewsAssigned teammateAuthor
Who runs /ff-reviewReviewerAuthor
Who runs /ff-addressReviewerAuthor
Review verdict--approve / --request-changes--comment only
Who mergesAuthorAuthor
KindPattern
Featurefeature/issue-{N}-{short-description}
Bug fixfix/issue-{N}-{short-description}
Docsdocs/issue-{N}-{short-description}
Refactorrefactor/issue-{N}-{short-description}
No issuefix/{short-description}
Hotfixfix/hotfix-{short-description}main, cherry-picked to dev

Everything targets dev. Only a release PR targets main.

type(scope): description

Types 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 .env files and confidential pipeline output sitting next to tracked code.
  • No AI attribution, ever. No Co-Authored-By trailers, no tool URLs.

Scope labels are required — at least one. Sync the full set with just labels, which applies scripts/github/setup-labels.sh.

GroupLabels
Scopebackend, frontend, cli, cross-stack, api
Typebug, enhancement, refactoring, documentation
Triagegood 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.

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.

Terminal window
just install # all dependencies (uv + bun + go)
just docker-up # PostgreSQL + Artemis
just serve-backend # backend in embedded mode
just serve-frontend # frontend dev server

just docker-reset stops services and removes volumes for a clean slate — use it after a schema change. just docker-wipe additionally removes orphaned testcontainers.

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.

StackCommands
Backendcd backend && uv run ruff check . && uv run ruff format --check . && uv run ty check
Backend testscd backend && uv run pytest packages/<package>/tests/ -v
Frontendcd frontend && bun run check && bun run test && bun run build
CLIcd cli && go build ./cmd/factflow/ && go vet ./... && go test -race ./...
Docsitecd 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 check does not typecheck. Only bun run build runs tsc -b. A green check with 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.

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:

Terminal window
just test-cli help # list scenarios
just test-cli s1 # or: ./scripts/test-cli/run.sh s1

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

The authority is the repo, scoped by file path: