Commit all in-flight work — ckb-module and ckb-reset skills, the .agents/modules/ scaffold, OPENSPEC docs, decision records D-0001 and D-0002, graph edges and workload summaries — so the reset that follows is fully recoverable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9 KiB
name: ckb-spec
description: Own the KB-root openspec/ layer (cross-cutting capability specs spanning several src/ repos), delegate per-repo specs to OpenSpec's own installed workflow inside src//openspec/, and bridge both into wiki/ — archived change proposals become numbered decision records, current specs become type: spec entity pages, and the two levels link via implements/implemented_by. Use when the user says "write a spec", "propose a change", "what specs do we have", "archive this change", "sync the specs", "which spec covers X", or "wire OpenSpec into the wiki". Part of the opt-in software module. Distinct from ckb-decide (records decisions directly; this skill feeds it from archived specs) and ckb-code-map (records what the code IS; this skill records what it SHOULD DO).
Spec skill
Purpose
A code map says what the software is. A spec says what it is supposed to do — and a change proposal says what should become true next. This skill owns that axis, across two levels that deliberately differ:
| Level | Holds | Owned by |
|---|---|---|
openspec/ at the KB root |
Cross-cutting capabilities and contracts spanning several src/ repos. The what and why. |
This skill. A root openspec/ has no code beneath it and no single repo governing it, so OpenSpec's own repo-shaped workflow doesn't serve it. |
src/<repo>/openspec/ |
How one repo implements those capabilities. The how. | OpenSpec itself. This is its native case: specs travel with the code and ride along in the repo's own PRs. |
The split is the point, and so is the risk it creates: two places a statement can
live is two places it can diverge. The implements / implemented_by links
below exist so divergence becomes a lint finding rather than a silent second
source of truth. Maintaining those links is not optional bookkeeping — it is the
reason the two-level arrangement is safe.
Trigger phrases
- "write a spec" / "add a capability" / "what specs do we have" / "which spec covers X"
- "propose a change" / "draft a change proposal" / "archive this change"
- "sync the specs" / "wire OpenSpec into the wiki" / "are the specs in sync"
Scope rule
Writes openspec/ at the KB root, and wiki/. Inside src/<repo>/ it reads
freely and writes only through OpenSpec's own workflow, on the user's
explicit instruction. It never commits or pushes a src/ repo.
KB-root specs
Layout
openspec/
├── README.md # what this layer is for
├── specs/
│ └── <capability>.md # one capability, current truth
└── changes/
├── <change-id>/ # proposed delta, not yet true
└── archive/ # applied changes, immutable
A KB-root capability spec
---
type: spec
spec_id: billing-invoicing
tldr: One sentence stating the capability, in the present tense, as a contract.
status: active
implemented_by: /src/billing/openspec/specs/invoicing.md, /src/portal/openspec/specs/invoice-view.md
affects: /wiki/entities/billing.md
confidence: 0.9
quality: 0.8
last_updated: YYYY-MM-DD
freshness_window_days: 180
retention: high
---
Body: Purpose (why this capability exists), Requirements (each one testable — "the system SHALL ..." beats "the system should be fast"), Out of scope, Implementations (which repos, dual-linked), Sources.
Write requirements a reader can disprove. A requirement nobody can fail is not a requirement, and a spec full of them reads as thorough while constraining nothing.
Writing one
- Check
openspec/specs/for an existing capability on the subject. Extend it rather than creating a near-duplicate — two overlapping specs is the failure this layer exists to prevent. - Check the cascade (
wiki/→linked/→libs/) for what the KB already knows about the subject, and link to it rather than restating it. - Write the spec, then a
type: specmirror page underwiki/entities/(or link the spec file directly from the relevant entity page — prefer one home plus links over two copies of the text). - Log it per Rule B and update
wiki/entities/index.md.
Per-repo specs
Delegate. If src/<repo>/openspec/ exists, follow the instructions OpenSpec
installed there — do not substitute a workflow of your own, and do not "improve"
its file format to match this KB's conventions. That repo's specs are governed by
OpenSpec upstream; divergence there breaks its tooling and its PR flow.
Never run openspec init at the KB root
openspec init writes tool-integration files into .claude/skills/ and adds
marker blocks to AGENTS.md / CLAUDE.md. At the KB root both are load-bearing:
.claude/skills is a symlink to .agents/skills (so OpenSpec's files would join
this KB's skill set), and CLAUDE.md is a symlink to AGENTS.md, the KB's own
system prompt.
Run it only inside src/<repo>/, where the repo gets its own .claude/ and
its own AGENTS.md. The KB-root openspec/ is not an OpenSpec install and never
needs init. If the user asks for init at the root, explain this and offer the
KB-root spec layer instead — don't run it and don't clean up afterwards.
Install is npm install -g @fission-ai/openspec@latest (Node 20.19.0+), then
openspec init in the repo; openspec update after a CLI upgrade. See
OPENSPEC.md for the user-facing guide.
If OpenSpec is not installed (command -v openspec fails, or the repo has no
openspec/), say so plainly and stop rather than improvising:
"This repo has no OpenSpec setup, and the
openspecCLI isn't on PATH. Per-repo specs are OpenSpec's own workflow — install it and runopenspec initinsrc/<repo>, and I'll take it from there. I can write this as a KB-root cross-cutting spec instead if it isn't repo-specific."
That offer is genuine, not a consolation: a requirement that spans repos belongs at the root anyway.
The bridge
Three directions, all owned here.
1. Archived change → decision record
An applied-and-archived OpenSpec change is a decision that was made and acted on.
When the user archives a change (either level), offer to record it via
ckb-decide: the change's why becomes Context and Rationale, the delta
becomes Decision, and rejected options in the proposal become
Alternatives considered.
Cite the change id and its path in the decision's ## Sources. Do not
auto-record without asking — not every archived change is a decision worth a
permanent numbered record, and a decisions log padded with routine changes loses
the property that makes it worth reading.
Both artifacts are append-only, which makes them a natural pair: neither is ever rewritten when the thinking changes later.
2. Spec → entity page
Current KB-root specs surface in wiki/entities/ as type: spec pages so
ckb-retrieve can answer "what is this supposed to do" without opening the spec
tree. Keep these thin and pointer-style: tldr, spec_id, status, links. The
spec file stays the source of truth — a full copy in wiki/ is a second thing
to keep in sync, and it will lose.
3. Level linking — the drift rule
- A KB-root spec lists every repo-level spec implementing it in
implemented_by. - A repo-level spec names its parent in
implements. - Both become graph edges (
implements) inwiki/graph/edges.json.
Set both sides, every time. A one-sided link is a lint finding, exactly as a one-sided decision supersession is.
Sync check
On "sync the specs" / "are the specs in sync", report:
- KB-root specs with empty
implemented_by— specified but nobody builds it. - Repo-level specs with
implementspointing at a missing or archived root spec — building against something no longer true. - One-sided links, either direction.
- Root specs whose
last_updatedis older than the mapped commit of every repo implementing them — possible drift; verify, don't auto-fix.
Report all four. Fix none of them automatically — each is a statement about
intent, and only the user knows which side is right. ckb-lint runs the same
four checks as part of its sweep when this module is installed.
Edge cases
src/is empty — the KB-root layer still works. Cross-cutting specs can precede any code; that's often the point.- A spec that's really a decision ("we'll use Postgres") — that's
ckb-decide. A spec states a standing contract; a decision records a choice at a point in time. If it doesn't constrain future behaviour, it isn't a spec. - A capability implemented by exactly one repo, forever — it probably belongs in that repo, not at the root. Say so; don't silently promote it.
- OpenSpec's format changes upstream — for per-repo specs, follow upstream. This skill's formats govern the KB-root layer only.
- A change proposal that was rejected — it still archives, and it is still
worth a decision record with
status: rejected. The reasoning is the value.
Licensed under the Apache License, Version 2.0 — see LICENSE at the repository root, or http://www.apache.org/licenses/LICENSE-2.0.