Skip to content

0000 — Spec-driven development

WARNING

This RFC is draft. It is under refinement and is not the contract.

Summary

Non-trivial changes to neoc land in two pull requests: a spec PR that ships the user-facing description of the change to the public docs site (deploying to a Cloudflare branch preview), and an implementation PR that brings the runtime in line with what the merged docs promise. The deployed preview of the spec PR is the spec review surface.

Motivation

Refinement happens in GitHub issues and chat, but historically it has not been captured as a reviewable artifact before code lands. Implementation PRs end up carrying both the what and the how in the same diff, which makes them harder to review and harder for downstream readers (humans and LLM agents in the Seeker fleet) to reconstruct intent from.

The repository already publishes a VitePress documentation site at https://neoc.org. The site exposes llms.txt and llms-full.txt feeds via vitepress-plugin-llms, and Cloudflare Pages deploys a preview for every branch. Together these give us everything needed to make the docs site itself the spec surface:

  • A rendered review surface — reviewers (or LLMs) read the spec on the deployed site, not in raw markdown.
  • A machine-readable review surface — <preview>/llms.txt and <preview>/llms-full.txt are consumable by Seekers without parsing diffs.
  • A public record — accepted specs are visible to anyone using the runtime, not buried in an internal directory.

External tooling (Spec Kit, Kiro, ADR CLIs) was considered and rejected: they assume an engineer at a terminal driving a CLI. The Seeker fleet interacts with this repo as teammates would — through issues, PRs, comments, and labels — and any tool requiring local state or initialisation steps does not fit that operating model. GitHub plus the existing docs site is the toolkit.

Detailed design

Where specs live

Three shapes of long-lived design artifact live in docs/src/, distinguished by whether the change is module-shaped, forward-looking, or retrospective:

PathKind of artifactLifecycle
docs/src/<ns>/<name>.mdModule-shaped: add or change a module's Lua surfaceLiving; edited forever as the module evolves
docs/src/rfcs/NNNN-name.mdForward-looking: cross-cutting design proposals not yet acceptedFrozen on accept/reject; status front matter
docs/src/adrs/NNNN-name.mdRetrospective: records of decisions already in force, in npryce/adr-tools formatFrozen on acceptance; superseded by later ADRs

The distinction between RFCs and ADRs is timing. RFCs are proposals — they capture a design before it lands and may be rejected or revised during review. ADRs are records — they document a decision that is already in force, so a future reader can understand the rationale without reconstructing it from code archaeology. An accepted RFC introducing a load-bearing decision typically produces a corresponding ADR; the RFC carries the design discussion, the ADR carries the decision and its consequences.

Investigation notes and ad-hoc working memos do not belong on the docs site. They live in the issue, the PR description, or are not written down at all.

Lifecycle

  1. Refinement — issue is opened. Discussion, scoping, and option analysis happen on the issue. The issue starts with the draft label per the fleet operating discipline; a CODEOWNER removes it when refinement is complete.
  2. Spec PR — once the issue leaves draft, anyone (Seeker or human) opens a docs-only PR adding or editing the relevant page(s). The PR references the issue. Cloudflare deploys a branch preview. Review happens on the preview URL. CODEOWNER approval is required to merge.
  3. Implementation PR — opened against the merged spec, citing it in the PR description. Brings the runtime in line with the contracted surface. Required pipelines must be green and CODEOWNER must approve before merge.

The wip label gates a PR that is not yet ready for review.

When an RFC is needed

A separate RFC under docs/src/rfcs/ is required when the work is not module-shaped. Concretely:

  • New namespace (e.g. introducing ext:*)
  • Cross-cutting change touching multiple modules in concert
  • Process, build, infrastructure, or operational change
  • New module whose surface is large or contentious enough that the design merits standalone discussion

A small change to an existing module's surface, or a new module with a self-evident shape, does not need an RFC — the docs PR on the module page is the spec.

Trivial escape hatch

The two-PR split is overhead. Changes labelled trivial skip it:

  • Typo fixes, doc-only edits that do not introduce new surface
  • Dependency bumps with no surface change
  • Internal refactors not visible to Lua scripts

The label is applied by the PR author and accepted at CODEOWNER discretion. If a reviewer disagrees, the label is removed and the change splits.

RFC numbering and status

  • Filename: NNNN-kebab-name.md. NNNN is zero-padded, monotonic. 0000 is reserved for this RFC.
  • Front matter: status (one of draft, accepted, rejected, superseded, implemented), created (ISO date), implemented-in (PR number or commit SHA, set when the implementation lands).
  • A draft status banner appears at the top of every RFC until it is accepted, so readers of the deployed site cannot mistake a proposal for the contract.
StatusMeaning
draftUnder refinement. Not the contract. May be edited freely.
acceptedMerged. Awaiting implementation. The contract for what the runtime should become.
implementedMerged and the runtime is in line. The contract for what the runtime is.
rejectedClosed without acceptance. Kept for design history.
supersededReplaced by a later RFC, which is linked from this one.

Acceptance verification

Module reference pages include runnable Luau acceptance examples in fenced code blocks. To prevent drift between the contracted surface and the runtime, those examples must be reachable from the test suite — either by extracting them into tests/<ns>/<name>.test.luau, or by linking the docs page to an existing test file that exercises the same scenarios. The exact mechanism is left to follow-up work; until it is wired up, drift between docs and runtime is possible and reviewers must catch it manually.

This is an open question — see below.

Roles

  • Author (Seeker or human): opens issue, opens spec PR, opens implementation PR. May be different parties at each stage.
  • CODEOWNER (per CODEOWNERS): reviews and approves spec PRs and implementation PRs. Removes draft and needs-spec labels. Adjudicates trivial disputes.

Required labels

LabelWhereMeaning
draftissues, PRsNot ready for work / review. Only CODEOWNERS remove it.
wipPRsAuthor still working; do not review yet.
needs-specissuesRefinement complete; ready for a spec PR.
spec-approvedissuesA spec PR referencing this issue has merged.
trivialPRsEscape hatch; spec/dev split waived. CODEOWNER discretion.
blockedissues, PRsCannot proceed; blocker stated in a comment.

Migration from specs/

The repository currently has an internal specs/ directory containing lib/json.md and std/workers.md. These files are already shaped like reference docs and will be migrated to docs/src/lib/json.md and docs/src/std/workers.md in a follow-up PR; the specs/ directory will be retired at the same time. Migration is intentionally scoped out of this RFC to keep the process change and the file moves separately reviewable.

Drawbacks

  • Two PRs per change is more friction than one. Mitigated by the trivial escape hatch, but the friction is real for medium-sized work that does not qualify as trivial.
  • Discipline-only enforcement. Nothing automated stops an implementation PR from merging without a corresponding spec PR. The gate is the CODEOWNER. A future addition could be a GitHub Action that fails the implementation PR if it cannot find a merged spec PR linked from its body, but that is out of scope here.
  • Acceptance-verification mechanism is not yet wired up. Until docs acceptance examples are pulled into the test suite, spec and implementation can drift silently between merge of the spec PR and merge of the implementation PR. Reviewers carry that load manually for now.
  • Public draft RFCs. RFCs in draft are visible on the deployed site. The status banner is the mitigation; readers ignoring the banner will see proposals that may never land.

Alternatives

  • Internal specs/ directory only (current state). Works for module reference, but does not deploy, has no preview-review loop, and is invisible to readers of the runtime. Rejected because the deploy/preview/llms.txt loop is the load-bearing benefit of moving to the docs site.
  • Spec Kit / Kiro / external SDD tooling. Adds a CLI dependency and assumes a local engineer driving it. Does not fit the Seeker fleet operating model. Rejected.
  • ADR-only. Architecture Decision Records are narrower than full specs (decisions, not designs) and would not carry module surface. Rejected as insufficient on its own; ADRs may compose with this RFC layer in future but are not required.
  • Single PR carrying both docs and code. The status quo. Rejected on the original motivation: review surface is the diff, not the rendered site, and intent is mixed with implementation.

Open questions

  • How are docs acceptance examples wired into the test suite? Options: extract fenced ```luau blocks tagged with an acceptance info string and write them to tests/; or include explicit links in each module page to the test file that exercises its acceptance scenarios. The former is more rigorous; the latter is cheaper. To be answered by a follow-up RFC if non-trivial.
  • Project board automation. Which actions move cards through which columns, and is that driven by labels, PR state, or both? Out of scope for this RFC; to be answered when the board is set up.
  • What replaces specs/README.md after migration? Either redirect the content into docs/src/index.md, or retire it and let this RFC plus the module reference pages stand on their own.

Implementation notes

This RFC is itself the first artifact of the process it defines. The spec PR introducing it lands first (this file plus template.md, index.md, and the VitePress nav update). Follow-up PRs:

  1. Migrate specs/lib/json.mddocs/src/lib/json.md, specs/std/workers.mddocs/src/std/workers.md. Retire specs/.
  2. Wire up the GitHub plumbing — issue templates, PR template, label set, CODEOWNERS for docs/, project board.
  3. Decide and implement the docs-acceptance-to-tests mechanism (separate RFC if non-trivial).