Spektacular
Agent-agnostic CLI tool for spec-driven development, providing skills and integrations for coding agents (Claude, Bob, Codex) to plan and implement work from a written spec.
Status: early development — see the releases page for the latest version.
What is Spektacular?
Spektacular is a self-contained Go binary that brings spec-driven development to AI coding agents. You write a markdown specification; Spektacular turns it into a reviewed implementation plan and then drives a coding agent to implement it — keeping your intent reviewable at every stage.
Its core competencies:
- Self-contained binary plus installed agent skills. A single binary that, on
init, installs the skills (and commands) your coding agent needs to run the Spektacular workflows.
- State-machine-driven workflow. Spec, plan, and implement each run as a stepwise state machine. Spektacular hands the agent one per-step prompt at a time (
new / goto / steps), so every stage is resumable — stop, inspect, edit, and resume without losing work.
- Agent-agnostic, multi-agent support. Works with claude, bob, and codex; pick the one your team already uses, or register your own.
- Project knowledge base. A searchable, layered store of conventions, architecture, gotchas, and learnings that feeds context into planning.
How It Works
Spektacular follows a three-stage workflow — spec → plan → implement — each driven step by step by a state machine:
- Spec. You write a markdown spec (requirements, constraints, acceptance criteria);
spec new scaffolds one from a template.
- Plan.
plan new explores your codebase, asks clarifying questions, and writes a detailed implementation plan — plan.md, research.md, and context.md.
- Implement.
implement new drives the coding agent through each phase of the plan and validates the result against your acceptance criteria.
For the full pipeline, see the how-it-works documentation.
Install & getting started
Spektacular is a single self-contained Go binary.
# Homebrew
brew install jumppad-labs/homebrew-repo/spektacular
# Go 1.21+
go install github.com/jumppad-labs/spektacular@latest
Or download a pre-built binary from the releases page. See the install docs for apt and other methods. You also need a supported coding agent CLI (claude, bob, or codex) installed and configured.
Once installed, the minimal path is initialise → spec → plan → implement:
# 1. Initialise your project for a coding agent (claude, bob, or codex)
spektacular init claude
# 2. Scaffold a spec, then fill in your requirements
spektacular spec new --data '{"name":"auth-feature"}'
$EDITOR .spektacular/specs/<returned-spec-name>.md
# 3. Generate an implementation plan
spektacular plan new --data '{"name":"<returned-spec-name>"}'
# 4. Implement the plan
spektacular implement new --data '{"name":"<plan-name>"}'
Spec names are normalised and prefixed by the CLI, so use the returned spec_name and spec_path for follow-up commands rather than the name you passed.
Specs are plain markdown with a small set of structured sections (overview, requirements, constraints, acceptance criteria, and so on), and spec new scaffolds the template for you. For the full walkthrough and spec format, see the getting-started tutorial and the how-it-works documentation.
Supported agents
Spektacular ships with three coding-agent integrations. spektacular init <agent> runs the chosen agent's install step, writing its workflow skills (and, where the agent has no skill mechanism, command wrappers) into your project:
- claude — installs the workflow skills under
.claude/skills/ and ensures the project's CLAUDE.md imports @AGENTS.md, so the Spektacular agent rules take effect.
- bob — installs skills under
.bob/skills/ and command wrappers under .bob/commands/.
- codex — installs skills under
.agents/skills/.
Each integration is deliberately small: an agent implements a narrow Agent interface — Name() (its CLI identifier) and Install() (which writes its workflow artefacts) — and registers itself with the agent package from an init() function. Adding a new agent means implementing those two methods and registering the type.
Both the coding agent and the storage layer are pluggable behind defined Go interfaces — the Agent interface in internal/agent and the Store interface in internal/store (the read/write/search surface backing the spec, plan, and knowledge stores). Only the file store ships today. For the full interface signatures and how to add your own backend or agent, see the extending documentation and the plugins overview.
Project Structure
Running spektacular init <agent> creates:
.spektacular/
├── config.yaml # agent, command, debug, store settings, and the repo registry
├── repo.yaml # the colocated repo's own configuration
├── specs/ # your specification files
├── plans/ # generated plans (plan.md, research.md, context.md)
├── changelog/ # changelog records written by the implement workflow
└── knowledge/ # default project knowledge source
├── conventions/ # always-applied: standing rules, one per file
├── glossary/ # always-applied: shared domain/project terms
├── architecture/ # looked-up: how the system is built
├── gotchas/ # looked-up: sharp edges and traps
├── learnings/ # looked-up: empirical findings from past work
└── decisions/ # looked-up: the reasoning behind choices
Each knowledge category directory is scaffolded with a README.md describing what belongs in it. By default Spektacular reads .spektacular/knowledge/ as this repo's own store, addressed by the name the project registered the repo under; the project can declare additional shared stores — for example a team directory or a machine-wide global one — under knowledge.sources (see Configuration). See Knowledge for how it is organised and consumed.
Knowledge
Knowledge is the accumulated know-how a project draws on when planning — conventions, glossary terms, architecture notes, gotchas, learnings, and decisions. It is strictly a planning-time input: the planning agent reads it while producing a plan, and the relevant parts are written into the plan itself. The implement workflow then consumes only the plan documents — the plan is the contract.
Six categories, two retrieval tiers
Every entry belongs to exactly one of six categories, fixed by the first segment of its path. Each category has a retrieval tier that decides when its entries are loaded:
- Always-applied —
conventions (standing rules to follow) and glossary (shared domain and project terms). Loaded in full on every planning task, and deliberately excluded from search results so they are never surfaced twice.
- Looked-up —
architecture, gotchas, learnings, and decisions. The larger reference body, fetched only when a search matches, so it can grow without weighing down every task.
The category model — names, retrieval tiers, and per-category boundaries — is declared once in code, so it stays consistent across directory scaffolding, search labelling, and retrieval. A category's retrieval tier says when its entries are loaded; the addressing tier below says which knowledge a store holds. The two are different axes.
Tiers, search, and de-duplication
Knowledge lives in one of two tiers. Every registered repo contributes exactly one store, addressed by the name the project registered it under, holding knowledge about that repo's own code. The project declares any number of shared stores under knowledge.sources, each with its own name, for knowledge that belongs to no single repo (see Configuration). Every read, search, and always-applied load states a tier and, optionally, the store names to narrow to; every result reports the tier and store it came from.
Reading and writing name exactly one store, so they take a tier and a name alongside the path; a request that leaves either out is refused, and the refusal lists the names available in that tier. Searching, listing, conventions, and the always-applied load take --tier <project|repo|all> and a repeatable --filter <name>; omitting the narrowing covers every store the tier reaches, and no store is ever included or excluded implicitly.
Lookups are consolidated and de-duplicated across stores: each entry carries a SHA-256 checksum over its exact bytes, and byte-identical entries appearing in more than one store collapse to a single result. A search result looks like:
Hit {
tier // addressing tier of the originating store (project or repo)
name // name of the originating store (e.g. docs)
path // locator relative to the store root (e.g. gotchas/db-timeouts.md)
title // the document's first heading, or the locator when it has none
excerpts // compact matched excerpts
score // sum of query-term occurrences (ranking)
category // category derived from the path (e.g. gotchas, architecture)
checksum // SHA-256 over the entry's raw bytes; the byte-identity de-dup key
}
For the full model — every category definition, the retrieval tiers, the addressing tiers, and the de-duplication rationale — see the knowledge-base documentation.
CLI
Agents (and you) reach knowledge through the spektacular knowledge commands rather than reading the files directly, so access stays consistent across stores. The main subcommands:
knowledge search <query> — keyword-search the stores the request covers (excluding the always-applied categories), returning ranked, tier- and category-tagged hits, each carrying the entry's tags. A document need not contain every query word: it is returned if it carries evidence for any of them. Narrow with --tier, --filter, and a repeatable --tag
knowledge tags — list the tag vocabulary already in use, with an entry count for each, most-used first; takes --tier and --filter
knowledge conventions / knowledge always-applied — read the always-applied entries in full; both take --tier and --filter
knowledge categories — list the categories and their retrieval tiers
knowledge read / knowledge write — read and write one addressed entry, via --data '{"tier":"…","name":"…","path":"…"}'
knowledge list — list entries across the stores the request covers; takes --tier and --filter
knowledge sources — list the configured stores by tier and name, with their locations
Every subcommand accepts --schema to print its input/output JSON schema and exit.
Capturing knowledge
When research surfaces a durable learning, gotcha, or convention worth keeping, the agent proposes the destination — the tier, the store name, and the path — along with the exact content, and waits for your explicit confirmation before writing; it never persists to a knowledge store unprompted. In a Spektacular-initialised repo, the spek-knowledge skill is the entry point for reading, contributing to, and updating the knowledge base in any session, and coding agents route what they would otherwise save to their own per-user memory into the project knowledge base instead, so captured knowledge lands in git and travels with the project.
Configuration
Configuration is split across two files, and a colocated single-repo project simply holds both in the same .spektacular/ directory. A repo's Spektacular files can also live apart from its code, in a folder that points at the code (see Repo configuration below).
.spektacular/config.yaml (project configuration). The project's identity, the coding agent Spektacular drives, the registry of member repos with the location of each repo's Spektacular files, and the central spec, plan, and changelog stores. Spektacular always runs against a project: running it in a directory with no config.yaml produces an explicit error pointing at init (there is no parent-directory search).
.spektacular/repo.yaml (repo configuration). A repo's own concerns only: what it is, where its code lives, its knowledge sources, and its changelog provider. It carries no pointer to any project, so one repo can belong to several projects at once.
Breaking change: earlier releases used a single config.yaml without a project name. Existing setups re-initialize with spektacular init <agent>: init backfills the name (from the directory basename, or --name), seeds the colocated repo's repo.yaml, and registers it in the new repos list.
Project configuration (config.yaml)
name: my-project # required, slug-safe; namespaces changelog entries
source: git@example.com:org/my-project.git # optional; the project's git address, recorded in derived changelog entries only
command: spektacular
agent: claude
debug:
enabled: false
spec:
provider: file
id_method: timestamp # how new spec identifiers are generated
config:
directory: .spektacular/specs # project-root-relative directory for spec files
plan:
provider: file
config:
directory: .spektacular/plans # project-root-relative directory for plan files
changelog:
provider: file
config:
directory: .spektacular/changelog # central changelog; entries land under <directory>/<name>/
repos:
- name: my-project # the colocated repo, registered by init: this .spektacular/ folder
location: .
- name: docs # a repo checked out beside this one, with its own .spektacular/
location: ../../docs/.spektacular
- name: lib # a repo folder in this project; its code is cloned from a git source
location: ../repos/lib
knowledge:
sources: # optional, the project's shared stores only (e.g. a team share);
- name: team # each repo declares its own store in its repo.yaml
provider: file
config:
location: ../team-kb # relative to the folder holding config.yaml, as repos are
Each repo entry needs a slug-safe unique name and a location: the folder holding that repo's repo.yaml (local is still accepted and means the same thing). A relative location is resolved from the folder holding config.yaml, and nothing is appended to it, so the project's own footprint is . and a repo folder in the project is ../repos/<name>. A repo is normally added through a guided flow: you are asked which repo to add, and its name, description, role and tags are each proposed for you from what the repo says about itself, one question at a time, with a plain-language confirmation before anything is written. Spektacular's files go inside the repo being added unless it cannot take them or you say otherwise, in which case they live in a folder under the project and the repo is left with only its code. An add can be started and finished while a spec or plan is already in progress. A caller that already knows every detail can still register a repo in a single command with repo add. Where the code lives is declared in the repo's own repo.yaml as source; the old address key is no longer read, and a config that still carries it fails to load with an error saying where the value now goes. description, role, and tags are optional metadata, also in repo.yaml, that cross-repo planning uses to attribute requirements to the right repo. Add to the registry with spektacular repo new, or spektacular repo add when every detail is already known, and inspect it with spektacular repo list; removal is a manual config edit. Cloned repos are never fetched or pulled automatically; a stale clone produces a warning only.
Relative locations everywhere in config.yaml share one base: the folder holding config.yaml. That covers both a repos entry's location and a knowledge.sources entry's config.location, so .. is the project's own root and ../team-kb a folder beside it. An absolute location is used as written. A knowledge source that does not resolve to a directory fails fast, naming the store, the path it resolved to, and the base it resolved from; when the store is found where the pre-1.0 rule would have put it, the error also names the exact corrected value to write.
Repo configuration (repo.yaml)
description: the documentation repo
role: documentation
tags: [docs]
source: # where the code is; omit when it is this folder
provider: file # file or git
config:
location: .. # a path relative to this file, or a git URL for the git provider
knowledge: # the repo's single store; synthesised if the file is absent
provider: file # addressed by the name the project registered this repo under
config:
location: knowledge
changelog:
provider: file
config:
directory: changelog # where this repo's derived entries land
A repo's Spektacular files can sit inside its code, in a .spektacular/ folder holding repo.yaml with a file source pointing at .., or in a folder of their own, for example one folder per repo under a project, with source pointing at a checkout on disk (absolute, relative to the folder holding repo.yaml, or using ${VAR}) or at a git repository that Spektacular clones into .spektacular/repos/<name>/ on first use. In the separate layout the code repository receives only code changes; knowledge and changelog entries land under the folder holding repo.yaml. spektacular repo list reports the resolved source as each repo's root. See Multi-Repo Projects for the layouts.
Knowledge aggregates across every registered repo's declared sources (in registry order) followed by the project-owned sources, so a repo's knowledge travels with it into every project that registers it. Changelog entries, central and derived per-repo, are namespaced under a folder named after the project (<directory>/<project-name>/<id>_<slug>.md), so multiple projects writing into one repo can never collide.
Excluding paths (.spektacular_ignore)
Any source root (a repo, or the project's own storage locations) may carry a .spektacular_ignore file using gitignore pattern syntax. Matching paths are excluded from Spektacular's own listing and search results, keeping build artifacts and dependency directories out of planning research, but a directly named path is never blocked, and agents' native file tools are unaffected.
For the full reference (every key, the id-method semantics, name-normalisation rules, and ${VAR} expansion) see the configuration documentation. For the concept of multi-repo projects, why the configuration is split this way, and how work is attributed across repos, see Multi-Repo Projects.
Testing
Spektacular has two layers of tests: a fast Go unit suite, and an end-to-end Harbor harness that runs the workflows against real AI coding agents inside sandboxed Docker containers.
Unit tests
go test ./... # or: make test
End-to-end (Harbor)
Prerequisites
- Docker
- uv (Python package manager)
Install Harbor
uv tool install harbor
Run the oracle (scripted) tests
The oracle agent runs a scripted solution to validate the test harness itself —
no AI tokens required:
harbor run -p tests/harbor/spec-workflow -a oracle -o tests/harbor/jobs
Run with a real agent
Harbor needs an auth token to run Claude Code inside the container. If you use
Claude Max (OAuth), export the token from your local credentials:
export ANTHROPIC_AUTH_TOKEN=$(python3 -c "import json; print(json.load(open('$HOME/.claude/.credentials.json'))['claudeAiOauth']['accessToken'])")
If you use an API key instead, export that:
export ANTHROPIC_API_KEY=sk-ant-...
Then run:
harbor run -p tests/harbor/spec-workflow -a claude-code -m claude-sonnet-4-6 -o tests/harbor/jobs
Makefile wrappers run the suites for you, building the binary and wiring up the agent-specific placeholders:
make harbor-test-spec # spec workflow (claude)
make harbor-test-spec-codex # spec workflow (codex)
make harbor-test-plan # plan workflow (claude)
make harbor-test-repo # guided repo add, answering each question (claude)
make harbor-test-repo-delegated # guided repo add, handing the whole set over (claude)
Test results
Results are written to tests/harbor/jobs/ (gitignored). Each run produces:
tests/harbor/jobs/<timestamp>/
├── result.json # Overall pass/fail and metrics
└── spec-workflow__<id>/
├── agent/ # Agent output log
├── verifier/
│ ├── test-stdout.txt # pytest output
│ └── reward.txt # 1 = pass, 0 = fail
└── trial.log # Full trial log
Available test tasks
| Task |
Description |
tests/harbor/spec-workflow |
Full spec creation workflow, end to end |
tests/harbor/plan-workflow |
Full plan generation workflow, end to end |
tests/harbor/repo-workflow |
Guided repo add, checked against the agent's own transcript |
The repo-workflow suite is the one whose assertions are about the conversation rather than the
files: that only the repo itself is asked for cold, that every later question carries a proposed
value, that the questions arrive one per exchange, and that none of Spektacular's internal
vocabulary reaches the user. Its rules are themselves checked by
tests/harbor/repo-workflow/tests/test_verifier_selfcheck.py, which runs locally under plain
pytest with no container and proves each rule fails on a transcript that breaks it:
python3 -m pytest tests/harbor/repo-workflow/tests/test_verifier_selfcheck.py
Building from Source
# build binary
make build
# run tests
make test
# cross-compile for all platforms
make cross
make build produces the binary at ./bin/spektacular. The Makefile targets:
| Target |
Description |
make build |
Build the ./bin/spektacular binary |
make test |
Run go test ./... |
make lint |
Run go vet ./... |
make clean |
Remove build artefacts |
make install-local |
Build and copy the binary to /usr/local/bin |
make cross |
Cross-compile for darwin/linux/windows (amd64 + arm64) |
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b my-feature)
- Make your changes
- Run the tests and vet checks (
make test, make lint)
- Submit a pull request
License
Apache 2.0