Contributing

PRs welcome. This page is the orientation: where the code lives, how to build and test, what we care about in reviews, and what to skip.

Repository layout

Cargo workspace at the root, with a apps/ directory for the JS apps and a docs/ directory for design specs. Five Rust crates, two web apps:

flowchart LR
  root(("<b>giff/</b>"))
  crates["<b>crates/</b>"]
  apps["<b>apps/</b>"]
  docs["<b>docs/superpowers/</b><br/><span style='color:#86868b;font-size:11px'>specs &amp; plans</span>"]
  compose["<b>docker-compose.yml</b><br/><span style='color:#86868b;font-size:11px'>runs the runner</span>"]
  gaps["<b>GAPS.md</b><br/><span style='color:#86868b;font-size:11px'>known gaps</span>"]
  root --> crates
  root --> apps
  root --> docs
  root --> compose
  root --> gaps

  core["<b>giff-core</b><br/><span style='color:#86868b;font-size:11px'>pure Rust · no I/O</span>"]
  git["<b>giff-git</b><br/><span style='color:#86868b;font-size:11px'>GitBackend · shells out to git</span>"]
  forge["<b>giff-github</b><br/><span style='color:#86868b;font-size:11px'>ForgeBackend · ureq, sync</span>"]
  cli["<b>giffstack (CLI)</b><br/><span style='color:#86868b;font-size:11px'>crate at crates/giff-cli/<br/>ships the giff binary</span>"]:::brand
  runner["<b>giff-runner</b><br/><span style='color:#86868b;font-size:11px'>axum + sqlite + worker</span>"]:::brand
  wasm["<b>giff-wasm</b><br/><span style='color:#86868b;font-size:11px'>future browser binding</span>"]
  crates --> core
  crates --> git
  crates --> forge
  crates --> cli
  crates --> runner
  crates --> wasm

  web["<b>web</b><br/><span style='color:#86868b;font-size:11px'>dashboard · adapter-static</span>"]
  site["<b>docs</b><br/><span style='color:#86868b;font-size:11px'>this site · adapter-vercel</span>"]
  apps --> web
  apps --> site

  classDef brand fill:#ff0035,stroke:#ff0035,color:#ffffff;
Workspace layout — strict layering, low coupling.

Architectural rules

  • giff-core is I/O-free. No std::fs, std::process, network, no tokio. Anything touching the outside world lives in giff-git, giff-github, the CLI, or the runner. Keeps the core compilable to WASM without a porting layer.
  • giff-github is sync. Uses ureq, not async. Async-ifies only inside the runner, which wraps sync calls in tokio::task::spawn_blocking.
  • The CLI is single-binary. Don't introduce daemons, IPC, or background services in the CLI — that's the runner's job.
  • The web dashboard talks only to GitHub directly. No backend dependency. Static deploy.

Build & test

bash
# clone
git clone https://github.com/nidheesh-m-vakharia/giff.git
cd giff

# build everything
cargo build

# test everything (~120 tests as of this writing)
cargo test

# build a single crate
cargo build -p giffstack
cargo test -p giff-runner

# run a single test
cargo test -p giff-core stack_frame_bottom_has_no_parent

# the CLI in dev mode
cargo run -p giffstack -- log --all

For the JS apps:

bash
cd apps/web        # or apps/docs
npm install
npm run dev        # local dev server
npm run check      # svelte-check
npm run build      # production build

What "done" looks like for a PR

Roughly, in priority order:

  1. Tests for the change. New behaviour gets a test that would have failed before. Bugfixes get a regression test.
  2. CI green. .github/workflows/ci.yml runs Rust build + test, fmt, clippy, web type-check + build, and docs type-check + build on every PR. PRs don't merge with red CI.
  3. The whole workspace builds locally too. cargo build + cargo test from the workspace root.
  4. No new TypeScript / Svelte errors. npm run check for whichever apps you touched.
  5. Style matches the surrounding code. Rust uses rustfmt; JS uses Prettier (run npm run lint).
  6. One commit per PR. Yes, even when developing this tool we eat our own dog food. Use giff publish + giff commit --amend to keep the layer clean.

Releases

Pushes to main trigger .github/workflows/release.yml, which auto-publishes any of giff-core, giff-git, giff-github, giffstack whose Cargo.toml version is newer than the version on crates.io. Pushes without a version bump are no-ops. The workflow tags the repo v<giffstack-version> and creates a GitHub release when giffstack publishes.

The publish job is scoped to a GitHub environment named crates-io. Setup:

  1. Repo Settings → Environments → New environment, name it crates-io.
  2. Add a secret CARGO_REGISTRY_TOKEN to that environment (token from crates.io/me).
  3. (Optional but recommended) under "Deployment branches" restrict to main, and tick "Required reviewers" with one or more maintainers.

With reviewers enabled, every push to main that bumps a version pauses the workflow with an "Approve deployment" prompt before the secret is injected and cargo publish runs — a manual gate against accidental publishes.

To cut a release: bump the version in the relevant Cargo.toml(s), commit, push to main, approve the deployment.

What we look for in reviews

  • Validation invariants. Adding a stack mutation? Add a stack.validate() call after the mutation. Adding a new error path on an external call? Hook into the retry queue if the operation is retry-safe.
  • Idempotent operations. The runner re-tries everything, the CLI re-runs after crashes. Operations that hit GitHub need to handle "already in target state" cleanly.
  • Error messages with a fix in them. "could not derive a branch name from !!!" is OK; "ParseError(InvalidInput)" is not. Tell the user what they can do about it.
  • Don't leak abstractions across crates. If giffstack needs something from giff-core that isn't there, add it to giff-core with a test. Don't reach in and re-implement.

What we don't want

  • Dependencies-as-features. Adding tokio to giffstack "because it'd be nicer with async" is a no. The CLI staying sync keeps binary size and startup time predictable.
  • Premature multi-tenant work. The runner is single-tenant by design. Don't add tenant_id columns or auth scaffolding until Phase 2 lights up.
  • SaaS-only features in the open-source repo. If a feature only makes sense behind paid SaaS billing, it doesn't belong here.
  • Refactors without a tied feature. "Reorganising for clarity" PRs are politely declined unless they're paired with new work that benefits from the reorg.

Filing issues

What to include:

  • The command you ran (full giff ... invocation).
  • giff --version output.
  • giff log --all if relevant — shows the stack state.
  • What you expected to happen vs what did.
  • If it's a runner issue, the relevant lines from docker compose logs and the contents of /data/state.db's events table around the time of the issue.

Where to start

Good first PRs:

  • Pick a P3 from GAPS.md — they're small, scoped, and won't conflict with anything in flight.
  • Improve a CLI error message you found unhelpful.
  • Add a test for a code path that doesn't have one.
  • Fix a typo in these docs.

Bigger work — discuss in an issue first so we can sanity-check the direction.

giff stack · open source · MIT Source