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.nodeto>=24.20.0 <25;.nvmrcpins24.20.0for development, builds, and verification. Runnvm 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 devnpm 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 buildnpm 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-exportThis 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.
- In the Slack app console, enable Socket Mode. While Socket Mode is on, Slack stops delivering events to the HTTP Request URL.
- Create an app-level token with the
connections:writescope. It starts withxapp-and is separate from the bot’sxoxb-token. - Put that token and the app’s signing secret in
.env.slack.localat the repository root, asSLACK_APP_TOKENandSLACK_SIGNING_SECRET. Shell values win over file values, and--env <path>points at a different file. - Start the local server, then run
npm run slack:bridge. It forwards toPORT, which defaults to3583.
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.
- Create a topic branch from current
origin/main, orupstream/mainin your fork. - Make the change and add regression coverage for changed behavior. Keep unrelated refactors and generated-asset changes out of the pull request.
- 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. - 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
.envand.dev.varsfiles, SQLite databases, credential keyrings, setup links, and live test transcripts..gitignorecovers 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:buildornpm run brand:build. - Base64 images in TypeScript. Public raster images live in
assets/; add a new runtime image tosrc/assets/public-assets.tsand to the source-export binary allowlist. - A dependency change without its lockfile. When a dependency changes, include
package-lock.jsonand runnpm audit --omit=devalong withnpm 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 devexpects. - Deploy and operate: what production hosting looks like on Cloudflare and on Node.
- Environment variables: every environment variable a deployment reads, with its default.
