Skip to content

Development

Set up a local Chickpea checkout, run the serial verification gate, and open a pull request.

Updated View as Markdown

A development checkout is a local clone of Chickpea that runs the Node target from source. Where a deployment serves one Slack workspace, a checkout serves one contributor and keeps its state in disposable local files. This page covers the checkout, the everyday commands, the verification gate every change runs through, and the branch and pull request rules.

Chickpea has no hosted continuous integration. This is deliberate: the repository uses no GitHub Actions workflows and requires no GitHub-hosted test results for merging, so every check below runs on your machine.

Prerequisites

  • Node 24.x, minimum 24.20.0. Both Chickpea packages set engines.node to >=24.20.0 <25; .nvmrc pins 24.20.0 for development, builds, and verification. Run nvm install && nvm use. This is the only routinely tested Node major; update the pin for future Node 24 patch/security releases and verify that baseline once.
  • A GitHub account. Fork pejmanjohn/chickpea if you do not have push access.
  • Your own Slack test workspace, credentials, and data, for any change you want to exercise against real Slack. Never point a checkout at a workspace or state you did not create for testing.

Set up a checkout

git clone https://github.com/pejmanjohn/chickpea && cd chickpea
nvm install && nvm use
npm ci
npm run dev

npm run dev starts the Node lane through Vite (vite dev --config vite.node.config.ts). To reach /admin you also need a setup link, which means exporting CHICKPEA_AUTH_SECRET and the two variables npm run setup:link prints. The Node get started page has that sequence in full.

Chickpea is offline-safe by default. Left unset, most variables take a local path and the app makes no external calls, so a fresh checkout runs and its tests pass without a single credential. State defaults to SQLite; set TAG_DB_PATH=:memory: and SLACK_STATE_DB_PATH=:memory: only for disposable development.

Everyday commands

npm test                    # typecheck, then node --test over tests/
npm run dev                 # local Node lane
npm run build               # Cloudflare build

npm test runs tsc --noEmit first and then Node’s test runner over tests/*.test.ts and tests/usage/*.test.ts, with DO_NOT_TRACK=1 set so a test run sends no telemetry. Tests live beside the source areas they cover; CONTRIBUTING.md maps each area to its directory under src/.

npm run build is the Cloudflare build. It runs vite build against vite.config.ts and then validates the artifact in dist-cf, proving Wrangler will consume a real bundle and that the Node-only database entry stayed out of the Cloudflare discovery path.

Verify

Run builds and tests serially. Tests inspect generated artifacts, so a concurrent build can make them read a partially written artifact. Select the applicable checks in this order:

For iteration, select the relevant checks below. Once the candidate is stable and committed, run npm run verify:regression -- --mode release once. The clean export performs the full suite and offline turn/durability/provider checks, so do not repeat them as outer release gates.

npm run build
TAG_DB_PATH=:memory: SLACK_STATE_DB_PATH=:memory: CHICKPEA_AUTH_DB_PATH=:memory: TAG_REQUIRE_LOOPBACK=1 npm test
npm run verify:admin-ui
DO_NOT_TRACK=1 node scripts/verify-flue-offline-turn.mjs
DO_NOT_TRACK=1 npm run verify:durability
DO_NOT_TRACK=1 npm run verify:providers
DO_NOT_TRACK=1 npm run verify:cf-smoke
npm run verify:lockfile-integrity
Command What it proves
npm run build The Cloudflare bundle builds and its artifact validates.
npm test Typecheck passes and the test suite is green.
verify:admin-ui The /admin configuration loop works with no Slack network access: real routes, in-memory store, an Owner, Agents, channel grants, and the one-body memory contract.
verify-flue-offline-turn.mjs The full turn policy under the built Node server with zero external traffic: signature checks, one streamed final, and a sanitized failure when the provider returns 500.
verify:durability Conversation state survives a process restart. A second process on the same SQLite file replays the first turn, and a different file does not.
verify:providers The same signed mention is answered through every API-backed provider route, against local fake provider endpoints.
verify:cf-smoke The Cloudflare target really works in workerd.
verify:lockfile-integrity No package-lock.json entry is missing its subresource integrity hash.

The offline verifiers use fake Slack and provider services and isolated local state. They need no production credentials.

Isolated databases

The environment prefix on the test line is not decoration. TAG_DB_PATH, SLACK_STATE_DB_PATH, and CHICKPEA_AUTH_DB_PATH set to :memory: keep the run off any SQLite file on disk, so a test never resets state an operator is using. TAG_REQUIRE_LOOPBACK=1 turns a silent skip into a hard failure: the loopback-dependent suites skip themselves when a sandbox denies listen(127.0.0.1), and a silently skipped parity suite lets a regression through.

What the Cloudflare smoke gate does

verify:cf-smoke builds both Cloudflare deployment profiles, boots the core profile under a real workerd through wrangler dev, and drives the first-run story against the in-memory fake Slack and fake provider: app creation, bot OAuth, a signed Events proof, and first-Owner sign-in. It then asserts what only workerd can prove. A signed synthetic app_mention verifies against the stored signing secret and delivers a final. An identical redelivery is deduped. A workerd restart on the same persistence directory still dedupes the original event and still admits a thread reply. A tampered signature is rejected. Every outbound URL points at 127.0.0.1.

It takes several minutes.

After you commit

npm run verify:oss-export

This one checks an archive of HEAD, not your uncommitted edits, so commit first. It installs from the lockfile into a temporary directory, then runs tests, the offline runtime checks, and a deployment dry run there. Private working documents must stay outside the public export; adding a public document requires updating the explicit export allowlist in the script.

Local automated checks cannot establish real Slack or provider acceptance. Changes to setup, authority, delivery, or persistence also need the attended checks from the maintainers’ release checklist. Do not describe a build or a fake-backend result as live acceptance.

Drive the local server with real Slack events

npm run slack:bridge opens a Slack Socket Mode connection, re-signs each event envelope with your app’s signing secret exactly as Slack’s HTTP delivery would, and posts it to your local server. Use it when you want real Slack events without running a public tunnel. It is a development tool, not a transport: it has a single consumer, it acknowledges immediately, so Slack’s retry semantics are not exercised.

  1. In the Slack app console, enable Socket Mode. While Socket Mode is on, Slack stops delivering events to the HTTP Request URL.
  2. Create an app-level token with the connections:write scope. It starts with xapp- and is separate from the bot’s xoxb- token.
  3. Put that token and the app’s signing secret in .env.slack.local at the repository root, as SLACK_APP_TOKEN and SLACK_SIGNING_SECRET. Shell values win over file values, and --env <path> points at a different file.
  4. Start the local server, then run npm run slack:bridge. It forwards to PORT, which defaults to 3583.

Do not run the bridge at the same time as another Socket Mode consumer; they steal events from each other. Disable Socket Mode again before testing the normal HTTP Events API path.

Open a pull request

For a substantial feature or behavior change, open an issue first so the problem and the scope are agreed before you write code.

  1. Create a topic branch from current origin/main, or upstream/main in your fork.
  2. Make the change and add regression coverage for changed behavior. Keep unrelated refactors and generated-asset changes out of the pull request.
  3. Push the topic branch and open a pull request against main. Explain the problem, the change, and what you actually verified. Link the issue if there is one.
  4. Record your local verification results in the pull request and resolve review feedback.

Maintainers squash-merge on GitHub. A maintainer may instead merge a worktree into root main, verify the merged result locally, and push directly; contributors without push access still use pull requests. Never force-push main. Merging or pushing does not deploy Chickpea and does not publish a release.

What never to commit

  • .env and .dev.vars files, SQLite databases, credential keyrings, setup links, and live test transcripts. .gitignore covers the usual paths, but the rule is yours to keep.
  • Hand edits to generated files. Use the documented builder instead, such as npm run avatars:build or npm run brand:build.
  • Base64 images in TypeScript. Public raster images live in assets/; add a new runtime image to src/assets/public-assets.ts and to the source-export binary allowlist.
  • A dependency change without its lockfile. When a dependency changes, include package-lock.json and run npm audit --omit=dev along with npm run verify:lockfile-integrity.
  • A vulnerability. Report it through GitHub’s private vulnerability report form, with no working credentials, private Slack content, or exploit details in a public issue or pull request.

Contributions are provided under the repository’s Apache 2.0 license.

Next steps

  • Contribute: the architecture and releasing pages that follow this one.
  • How Chickpea works: the layers a change travels through, from the Slack app to the model provider.
  • Get started on Node: the Node get started path, including the setup link and the variables npm run dev expects.
  • Deploy and operate: what production hosting looks like on Cloudflare and on Node.
  • Environment variables: every environment variable a deployment reads, with its default.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close