ckb/.agents/skills/ckb-decide/SKILL.md
Michał Kopeć 65b1e422b3 Add decision log, scriptify OKF export and lint detection
Decision log (VERSION 1.6.0, kb_schema_version 1.4):
- wiki/decisions/ scaffold — numbered NNNN-slug.md records, own index
  (with status vocabulary) and log
- type: decision adds status/decided_on/decided_by/affects/review_on;
  supersedes/superseded_by carry history and must be set on both sides
- New ckb-decide skill: records decisions and answers what/why/who/when,
  what superseded what, and what is still open. Decision pages are
  append-only — a changed mind is a new superseding decision
- Graph gains decided_by and affects edge types
- ckb-ingest routes decisions found in raw material to this format;
  ckb-retrieve gains the decisions index as a cascade step

Scriptified the mechanical skills:
- ckb-export-okf/scripts/export_okf.py does the whole OKF transform
  (frontmatter remap, link rewriting, index/log regeneration, conformance
  validation); --check validates without writing
- ckb-lint/scripts/lint_report.py does the read-only detection half
  (conformance, freshness, confidence, retention, decisions, orphans,
  graph, index/log, source.yaml); judgment calls stay with the model

Also: removed the duplicate personal quiz skill, fixed stale cbk-quiz
doc paths, gitignored __pycache__.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 20:50:36 +02:00

14 KiB

name description
ckb-decide Record a decision into wiki/decisions/ as a numbered decision record — what was decided, by whom, when, why, what it affects, and which earlier decision it supersedes or reverses — and answer questions about decisions already recorded ("what did we decide about X", "show decision 7", "which decisions are still open", "what changed the database choice"). Use when the user says "record a decision", "log a decision", "we decided ...", "ADR", "decision record", or asks what/when/why/by whom something was decided. Distinct from `ckb-ingest` (which turns raw source material into entity pages) and from `ckb-retrieve` (general KB questions — this skill is the decisions-scoped path, and hands off to it for anything wider).

Decision log skill

Purpose

A decision is a different kind of knowledge from an entity page. An entity page describes what something is, and gets rewritten as understanding improves. A decision record describes what was chosen at a point in time, by whom, and why — and is never rewritten to reflect a later change of mind. When the choice changes, a new decision supersedes the old one and both stay on the record. That's the whole point: the value is in being able to ask "why is it like this?" and get the reasoning, the people, and the date, not just the current state.

This skill owns both halves of that:

  • Recording — turning "we decided X" into a numbered, linked, logged decision record under wiki/decisions/, with the supersession links wired in both directions.
  • Looking up — answering questions about decisions already recorded, scoped to wiki/decisions/ rather than searching the whole wiki.

It writes only inside wiki/ (wiki/decisions/, wiki/decisions/index.md, wiki/decisions/log.md, wiki/graph/edges.json, and a pointer line in wiki/log.md). It never edits an existing decision's substance — see Rule: decisions are append-only, below.

Trigger phrases

Recording:

  • "record a decision" / "log a decision" / "add a decision record" / "write an ADR"
  • "we decided ..." / "we've agreed to ..." / "the call was ..."
  • "we're reversing ..." / "that supersedes decision N"

Looking up:

  • "what did we decide about X" / "why do we ... ?" (when the answer is a decision)
  • "show decision 7" / "show me D-0007"
  • "which decisions are still open" / "what's proposed but not accepted"
  • "who decided X" / "when did we decide X"
  • "what decisions affect <project/system/person>"
  • "what superseded decision N" / "what changed the X decision"

For a question that only touches decisions as part of a wider answer, use ckb-retrieve instead and let it pull decision pages in as one source among many. This skill is for when decisions themselves are the subject.

The decision record format

One page per decision at wiki/decisions/NNNN-short-slug.md, numbered sequentially from 0001. Numbers are never reused and never renumbered — a superseded or reversed decision keeps its number and its page.

---
type: decision
tldr: One sentence stating the decision itself, not the topic.
status: accepted
decided_on: 2026-09-01
decided_by: Alice Smith, Bob Jones
affects: /wiki/entities/billing-service.md, /wiki/projects/index.md
review_on: 2027-03-01
supersedes: /wiki/decisions/0003-use-mysql.md
confidence: 0.9
quality: 0.8
last_updated: 2026-09-01
freshness_window_days: 365
retention: high
---

# D-0007 — Use Postgres for the billing store

**Status:** Accepted · **Decided:** 2026-09-01 · **Deciders:** [[Alice Smith]] / [Alice Smith](/wiki/entities/alice-smith.md), [[Bob Jones]] / [Bob Jones](/wiki/entities/bob-jones.md)

## Context

What forced a choice. The problem, the constraints, what was true at the
time. Written so it still makes sense to someone reading it in two years
with none of the surrounding conversation.

## Decision

What was actually decided, stated plainly and in the active voice.

## Rationale

Why this option won. The reasoning that would have to change for the
decision to be worth revisiting.

## Consequences

What follows — what this commits us to, what it rules out, what work it
creates. Both the good and the costly.

## Alternatives considered

What else was on the table and why each was not chosen. A decision record
without this is much less useful on re-reading: it's the part that stops
the same option being re-proposed every six months.

## Supersession

Supersedes [[D-0003]] / [D-0003](/wiki/decisions/0003-use-mysql.md) — MySQL
was chosen before the reporting requirements landed.

## Sources

Where this came from — a meeting, a thread, a `raw/archive/` file, a ticket.

Field reference

Field Required Notes
type yes always decision
tldr yes the decision itself in one sentence ("Billing uses Postgres"), not the topic ("database choice") — this is what shows in the index and in search results
status yes proposed / accepted / rejected / superseded / reversed — vocabulary defined in wiki/decisions/index.md
decided_on yes for accepted/rejected/reversed YYYY-MM-DD, the date the call was made. Distinct from last_updated, which is when the page last changed. A proposed decision may have no decided_on yet.
decided_by yes when known comma-separated names, kept flat and plain so tooling can read it. The body's Deciders line carries the dual-links to entity pages. If genuinely unknown, write unknown rather than omitting the field — "we don't know who decided this" is itself worth recording.
affects no comma-separated project-root-absolute wiki paths this decision constrains. Cheap way to answer "what decisions touch X" without walking the graph.
review_on no YYYY-MM-DD to revisit. ckb-lint reports these once the date passes.
supersedes / superseded_by when relevant project-root-absolute path to the other decision. Always set both sides (see below).
confidence, quality, last_updated, freshness_window_days, retention as usual standard page schema. Decisions default to freshness_window_days: 365 and retention: high — a decision record doesn't rot the way a status page does, and it should survive a retention sweep.

How to run this skill — recording

Step 1 — Collect the facts, ask only for what's missing

Take everything the user already said at face value; don't re-ask for it. Then check what's missing against this list, in priority order:

  1. The decision itself — what was chosen. Without this there's nothing to record.
  2. Who decided — names. This is the field users most often leave out and most often want later.
  3. When — a date. "Today" is fine; "last Tuesday's architecture review" is fine, resolve it to a date.
  4. Why — the rationale, and the alternatives that lost.
  5. Whether it changes an existing decision — see Step 2.

Ask for the missing ones in a single AskUserQuestion round rather than an interview — this is a recording task, not a discovery interview. If the user is clearly mid-flow and wants it written down now, record what you have, mark the gaps explicitly in the page body (## Rationale"Not captured at recording time."), and say which fields you left open so they can fill them in later. A decision recorded with gaps beats a decision not recorded.

Do not invent context, rationale, or consequences. If the user gave you one sentence, the record is one sentence plus the metadata — a fabricated ## Consequences section is worse than an absent one, because a later reader can't tell it wasn't real.

Step 2 — Check whether it supersedes anything

Before writing, read wiki/decisions/index.md and scan for a decision on the same subject. If one exists and is still accepted:

  • Confirm with the user that the new decision replaces it (don't assume — two decisions can coexist on the same subject at different scopes).
  • If it replaces it outright, set supersedes on the new page and, on the old page, set superseded_by and change its status to superseded.
  • If it undoes it and returns to the prior state, use the same two links but set the old page's status to reversed instead.
  • Never edit anything else on the old page. Its context, rationale, and consequences stay exactly as they were written — that's the historical record.

Both directions must be set. A one-sided supersession link is a lint finding, and it breaks the "what changed this?" lookup in the other direction.

Step 3 — Allocate the number and write the page

The next number is the highest existing NNNN in wiki/decisions/ plus one, zero-padded to four digits — never reuse a number, even if the highest-numbered decision was rejected or superseded. Slug from the decision itself, not the topic: 0007-use-postgres-for-billing.md, not 0007-database.md.

Write the page using the format above. Link deciders to their entity pages where those pages exist (dual-linked, per Rule C). Where a decider has no entity page, write the name as plain text and mention the gap in your report — creating person pages is ckb-ingest's job, not this skill's.

Step 4 — Wire it into the index and graph

  • wiki/decisions/index.md — add a bullet in number order: * **[Accepted]** [D-0007 — Use Postgres for the billing store](0007-use-postgres-for-billing.md) — <tldr> and remove the "no decisions recorded yet" placeholder once there's a first entry. When a decision's status changes (Step 2), update its index line too.
  • wiki/graph/edges.json — add the edges this decision creates: decided_by (decision → person page), affects (decision → each page named in affects), and supersedes (new decision → old decision) where applicable. Skip any edge whose target page doesn't exist rather than pointing at a page you'd have to invent.

Step 5 — Log it, then report

Log the change in wiki/decisions/log.md (not wiki/log.md) using Rule B's format, and add a single pointer line to wiki/log.md: - See wiki/decisions/log.md for decision-record changes on this date. Per the Recursive Index & Log Convention, each change gets exactly one home log — don't write the full entry in both.

Then tell the user: the number and title assigned, the fields you filled, any fields left open, what it superseded (and that the old page's status was updated), which deciders had no entity page, and the standard reminder that this is on disk but not committed — "say 'sync changes' when you want it pushed."

How to run this skill — looking up

Step 1 — Start at the decisions index

Read wiki/decisions/index.md first. It carries every decision's number, title, status, and one-line summary — enough to answer "which decisions are open", "what's been decided about X", and "which decision covers Y" without opening a single page.

Step 2 — Open the pages that actually matter, and read them fully

For a specific decision, read the whole page — a decision's tldr states the choice but not the reasoning, and "why" is usually the real question. For a subject-scoped question ("what have we decided about billing?"), open every decision whose affects or body mentions the subject, including superseded ones.

Superseded decisions are part of the answer, not noise. "We use Postgres, and before that MySQL, changed in September because of reporting" is the useful answer; "we use Postgres" is the impoverished one. Follow supersedes/superseded_by chains in both directions and present the history in order.

Step 3 — Verify against the source before answering

Apply ckb-retrieve's standing rule: a decision page's ## Sources section points at where the decision came from. When the answer hinges on detail beyond what the page states — exact wording, a number, who was actually in the room — follow the source rather than paraphrasing the paraphrase.

Step 4 — Answer with the metadata attached

A decision answer is incomplete without who and when — lead with the decision, then attribute it. State the status plainly, especially when it isn't accepted: an answer built on a proposed or superseded decision must say so in the same breath, or the reader will act on something that isn't in force.

If nothing is recorded on the subject, say so plainly and offer to record one now — don't reconstruct a decision from surrounding wiki content and present it as if it were on the record. If the question revealed a genuine gap, that's a wiki/query-gaps.md entry (ckb-retrieve owns that file).

Rule: decisions are append-only

The only edits this skill makes to an existing decision page are: status, superseded_by, last_updated, and — when the user is explicitly correcting a recording error rather than changing their mind — the factual metadata fields. Context, Decision, Rationale, Consequences, and Alternatives are never rewritten to match a later view.

When the user says "actually we changed our mind about D-0003", that is a new decision superseding D-0003, not an edit to D-0003. Say so and record it that way. The exception is a genuine transcription error ("I said Alice, it was actually Anna") — fix that in place and note the correction in the log entry.

Edge cases

  • No wiki/decisions/ directory yet — create it along with index.md and log.md (using the scaffold this template ships), then record the decision as 0001-….
  • The user describes a decision that was already recorded — don't create a duplicate. Point at the existing record and ask whether they want to supersede it, correct it, or leave it.
  • A decision with no clear decider ("we just kind of settled on it") — record decided_by: unknown rather than guessing or attributing it to whoever is in the room. Unattributed is a fact; misattributed is a defect.
  • A decision that was never actually made ("we should probably...") — record it as status: proposed with no decided_on, or don't record it at all. Ask which; a proposal filed as accepted is the most damaging failure mode this skill has.
  • A decision that reverses a reversal — normal chaining. Each link points one step back; the chain is the history.
  • Bulk import of historical decisions (a meeting-notes backlog) — that's ckb-ingest's job for the extraction, then this skill's format for the pages. Record them in chronological order so the numbers run in the same order as the decisions.

Licensed under the Apache License, Version 2.0 — see LICENSE at the repository root, or http://www.apache.org/licenses/LICENSE-2.0.