ckb/.agents/modules/software/skills/ckb-spec/SKILL.md
Michał Kopeć 0c06cb64ab Restore point before wiki reset
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>
2026-09-20 17:56:41 +02:00

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>.*