Contributing a Spec-Driven Change
June 8, 2026 · View on GitHub
For non-trivial changes — new commands, new index types, behavior changes, anything that affects the public API or persistence format — RediSearch uses a lightweight spec-driven workflow. Before you write code, you capture the why, the how, and the plan as a small set of review artifacts, so maintainers can agree on the design while it is still cheap to change.
This workflow is framework-neutral. What gets reviewed is a set of artifacts (described below), not any particular tool. You can produce them however you like:
- by hand, in plain Markdown — no tooling required;
- with OpenSpec, which this repo ships a setup for under
openspec/(see the worked example); - with GitHub Spec Kit or another spec framework you prefer.
The artifacts and maintainer review are the contract; the tool that generates them is your choice. You do not need to install or run any AI tooling to follow this workflow.
If your change is a small bug fix, a refactor, a test, or a docs update, you can ignore this file and use the standard PR flow described in CONTRIBUTING.md.
When you need a spec
Open an issue first if your change involves any of:
- A new
FT.*command, or a new option/argument on an existing command. - A new field type or index type.
- A behavior change visible to existing users (different results, different errors, different defaults).
- A cross-module refactor that touches both C and Rust.
For these, a maintainer will either:
- Reply on the issue with the design decisions and treat the issue as the spec, or
- Ask you (or do it themselves) to draft a spec under
openspec/changes/<your-change-name>/. This is the path described below.
You do not need to draft a spec to open an issue. The issue comes first; the spec is a follow-up only if the change is large enough to warrant one.
Where specs live
This repository's convention is the OpenSpec directory layout, which any of the tooling options above (or a hand-written change) can target. The shape is what matters, not the tool that produced it:
openspec/
├── config.yaml # OpenSpec config (don't usually need to touch)
├── specs/ # Long-lived specs describing current behavior
└── changes/
├── <your-change-name>/ # In-progress change
│ ├── proposal.md # Why and what (the pitch)
│ ├── design.md # How (the technical plan)
│ ├── tasks.md # Checklist of implementation steps
│ └── specs/ # Delta specs that will land in openspec/specs/ on merge
└── archive/ # Completed changes, kept for history
If you use a different framework whose on-disk layout differs, that's fine — note it in your PR so a maintainer can help fold the artifacts into this convention on merge.
For shape and tone, see the worked example openspec/changes/example-add-ft-foo/ — a small, deliberately fictional change carrying a complete set of the artifacts described below. Completed changes are kept under openspec/changes/archive/ (empty until the first change is archived).
The four artifacts, in plain English
-
proposal.md— the pitch. Two sections: Why (what problem this solves, who is affected, what costs the status quo has) and What Changes (the user-visible surface — new commands, changed defaults, removed behavior). Aim for a page, not a dissertation. No code. -
design.md— the technical plan. How you intend to implement it: which subsystems change, what the data model looks like, what edge cases exist, what you considered and rejected. This is where reviewers push back on the approach before you write the code. -
tasks.md— the implementation checklist. Numbered, checkbox-style. Group by subsystem or by phase, so each top-level item maps to one reviewable unit of work. Granularity should be roughly "one task = one PR or one self-contained commit." -
specs/<capability>/spec.md— delta specs. The diff against the long-lived specs inopenspec/specs/. If your change adds a command, you describe the command here; when the change is merged, this content is folded intoopenspec/specs/and removed fromchanges/. For your first PR, you can ask a maintainer to handle this — it's a mechanical step.
Suggested flow for outside contributors
- Open a GitHub issue describing the problem and what you'd like to change. Reference any prior issues or community discussion. Wait for a maintainer to agree it's worth a spec-driven change.
- Draft
proposal.mdin a fork, underopenspec/changes/<change-name>/proposal.md. Open a draft PR with just that file. This is the cheapest moment to get directional feedback — before you've written code or a detailed design. - Add
design.mdto the same draft PR after the proposal is roughly accepted. Iterate on review. - Add
tasks.mdonce the design is settled. From here, you can start implementing; each task can be its own commit or follow-up PR. - Implement. Update the checkboxes in
tasks.mdas you go. The same PR that contains the implementation should also include the delta specs underchanges/<change-name>/specs/. - Validate before requesting final review. A spec-driven change is done when all of the following hold. Treat this as a self-check before flipping the PR out of draft, and call it out explicitly in the PR description:
- Every checkbox in
tasks.mdis either checked off or moved to a follow-up issue with a link. - The delta specs under
changes/<change-name>/specs/describe what actually shipped, not what was originally proposed — update them if the implementation diverged. - The new or changed behavior is covered by tests (unit, Rust, and/or Python end-to-end as appropriate). No silent gaps.
- The full build, lint, and test suites pass locally (see
CONTRIBUTING.md) and CI is green. - Any user-facing surface — commands, error messages,
FT.INFOoutput, configuration — is reflected in the docs, and the PR's release-notes checkbox is set correctly. - All review threads from earlier draft rounds are resolved.
- Every checkbox in
- Maintainer archives. On merge, a maintainer moves the change into
openspec/changes/archive/and folds the deltas intoopenspec/specs/. You don't need to do this step.
A draft PR per artifact is fine; one PR with all four is also fine. Match the size of the change.
Naming
Use kebab-case for the change directory and keep it short, action-oriented, and descriptive — the name lives in the archive forever, so favour clarity over cleverness. A verb-noun shape (add-X, fix-Y, deprecate-Z) usually reads well.
What if I don't follow this exactly?
It's fine. The spec workflow is here to make large changes reviewable, not to add procedural overhead. If you have a working implementation and an issue describing it, a maintainer can help you back-fill the artifacts during review. The goal is shared understanding before merge, not paperwork.
See also
CONTRIBUTING.md— the general contributor guide (setup, coding standards, PR workflow).openspec/changes/example-add-ft-foo/— a worked example change. Completed changes are archived underopenspec/changes/archive/.- Spec-framework options (all optional — pick one or write the artifacts by hand):
- OpenSpec — the tool this repo's
openspec/layout matches; its CLI can scaffold the artifacts. - GitHub Spec Kit — spec-driven development toolkit.
- OpenSpec — the tool this repo's