--- 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//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//` 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/ │ └── .md # one capability, current truth └── changes/ ├── / # 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//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//`, 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/`, 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 .*