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>
192 lines
9 KiB
Markdown
192 lines
9 KiB
Markdown
---
|
|
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/<repo>/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
|
|
|
|
```markdown
|
|
---
|
|
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
|
|
|
|
1. 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.
|
|
2. Check the cascade (`wiki/` → `linked/` → `libs/`) for what the KB already
|
|
knows about the subject, and link to it rather than restating it.
|
|
3. Write the spec, then a `type: spec` mirror page under `wiki/entities/` (or
|
|
link the spec file directly from the relevant entity page — prefer one home
|
|
plus links over two copies of the text).
|
|
4. 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](../../../../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 `openspec` CLI isn't on PATH. Per-repo
|
|
> specs are OpenSpec's own workflow — install it and run `openspec init` in
|
|
> `src/<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`) in `wiki/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:
|
|
|
|
1. KB-root specs with empty `implemented_by` — *specified but nobody builds it.*
|
|
2. Repo-level specs with `implements` pointing at a missing or archived root spec
|
|
— *building against something no longer true.*
|
|
3. One-sided links, either direction.
|
|
4. Root specs whose `last_updated` is 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](../../../../LICENSE)
|
|
at the repository root, or <http://www.apache.org/licenses/LICENSE-2.0>.*
|