feat(kb): skill i skrypt do pisania dokumentow kb/ (OKF authoring)
kb-authoring: sciagawka frontmattera OKF v0.1, taksonomia typow z rozstrzygnieciami decision/incident/runbook/audit wyciagnietymi z historii migracji (SPLIT commity), domyslne visibility: private, przypomnienie o check_okf.py po kazdej zmianie. Odrebne od kb-publish (to dotyczy pisania zrodel, nie wystawki public). new-doc.sh: scaffolduje kb/<type>s/<slug>.md z poprawnym frontmatterem, waliduje type, odmawia nadpisania, dokłada as_of dla type: audit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXe8RXn1YDY2HAnX3RVwVS
This commit is contained in:
parent
003f83d453
commit
ba94be1599
116
.claude/skills/kb-authoring/SKILL.md
Normal file
116
.claude/skills/kb-authoring/SKILL.md
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
---
|
||||||
|
name: kb-authoring
|
||||||
|
description: Conventions for writing/editing source knowledge-base documents under kb/**/*.md (frontmatter schema, type taxonomy, visibility default, validation). Trigger whenever creating or editing a file under kb/ — this is about authoring the sources, not publishing kb-site (see kb-publish for that).
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This skill governs **writing kb/\*\*/\*.md documents themselves** — the OKF-format
|
||||||
|
knowledge base sources. It is not about publishing them: for "should I run the
|
||||||
|
publish script", see the `kb-publish` skill, referenced again at the bottom
|
||||||
|
here.
|
||||||
|
|
||||||
|
`docs/sessions/*.md` files use `type: session-log` frontmatter and are validated
|
||||||
|
by the same script, but they are **not** kb/ documents — they are a running
|
||||||
|
diary of work sessions. **Never convert a session log into a kb/ doc** and
|
||||||
|
never move/copy a kb/ doc's content into docs/sessions/. If material in a
|
||||||
|
session log deserves a permanent home (a decision, an incident writeup, a
|
||||||
|
runbook), write a **new** file under kb/ that captures it properly — don't
|
||||||
|
relocate the diary entry.
|
||||||
|
|
||||||
|
## Frontmatter schema (OKF v0.1)
|
||||||
|
|
||||||
|
Every kb/ document opens with a YAML frontmatter block (`---` ... `---`) as
|
||||||
|
the first thing in the file. Validated by `scripts/kb/check_okf.py` — read
|
||||||
|
that file if you need the exact rules; summary:
|
||||||
|
|
||||||
|
| Field | Required | Values | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `okf` | yes | `"0.1"` | Pinned. Always this literal string, quoted. |
|
||||||
|
| `type` | yes | one of the taxonomy below | Determines which `kb/<type>s/` directory the file lives in. |
|
||||||
|
| `visibility` | yes | `private` \| `public` | **Default is `private`.** See below — never default to `public`. |
|
||||||
|
| `status` | yes | `active` \| `deprecated` \| `planned` | `planned` = decisions not yet made / work not yet started. `deprecated` requires `superseded_by`. |
|
||||||
|
| `updated` | yes | `YYYY-MM-DD` | Bump every time you edit the file's content. |
|
||||||
|
| `links` | yes (may be `[]`) | list of paths | Relative to **this file's own directory**, e.g. `../phases/backlog.md` or `sibling-doc.md`. Every entry must resolve to an existing file — check_okf verifies this. |
|
||||||
|
| `as_of` | only for `type: audit` | `YYYY-MM-DD` | Required exactly when `type: audit`, forbidden otherwise. See "audit" below for why this is a separate field from `updated`. |
|
||||||
|
| `superseded_by` | only when `status: deprecated` | free text or path | Required exactly when `status: deprecated`, forbidden otherwise. Points to whatever replaced this doc (a doc path, or prose describing the replacement if there's no single successor file). |
|
||||||
|
| `contradicts` | optional | list | Free-text notes about known conflicts with other sources (e.g. "CLAUDE.md says X, but the directory doesn't exist"). Entries that look like a `.md` path (contain `/` and end in `.md`) are checked for existence like `links`; plain-text entries are not. |
|
||||||
|
| `stub` | optional | bool | Marks a doc as a placeholder/incomplete. Must be a real YAML bool (`true`/`false`), not a string. |
|
||||||
|
|
||||||
|
## Type taxonomy — kb/<type>s/
|
||||||
|
|
||||||
|
| `type` | Directory | What goes here |
|
||||||
|
|---|---|---|
|
||||||
|
| `node` | `kb/nodes/` | One doc per physical/virtual host — role, configured services, runtime data paths. Mirrors `hosts/<node>/`. |
|
||||||
|
| `service` | `kb/services/` | One doc per deployed service — what it is, how it's used/configured. Mirrors `services/<svc>/`. |
|
||||||
|
| `subsystem` | `kb/subsystems/` | Cross-cutting architecture/design docs describing how something works *in general* (access model, agent system, deployment conventions) — not tied to a single node or service. Kept in sync with current reality (unlike `audit`, see below). |
|
||||||
|
| `decision` | `kb/decisions/` | A choice that was made (or is still open, `status: planned`) plus its rationale — forward-looking, governs future behavior. Gets edited in place and re-dated as the decision evolves; it is not a historical log of what happened. |
|
||||||
|
| `incident` | `kb/incidents/` | A factual account of something that broke: symptom, root cause, fix/status, at a point in time. Slug conventionally date-prefixed (`YYYY-MM-DD-short-description.md`) since incidents are anchored to when they happened. Content generally stays close to the as-happened account rather than being rewritten into "current state" prose. |
|
||||||
|
| `runbook` | `kb/runbooks/` | Step-by-step operational procedure — deploy, install, recover, troubleshoot. Imperative, command-heavy, meant to be followed live. |
|
||||||
|
| `phase` | `kb/phases/` | A project/milestone plan broken into steps, tracking progress (including backlog/roll-up index docs). |
|
||||||
|
| `audit` | `kb/audits/` | A **point-in-time snapshot** of actual/verified state (ground truth recon), never an ongoing description. Requires `as_of` — the date the finding was true — kept distinct from `updated` (the date the doc text was last edited) precisely because an audit's findings can go stale even when nobody touches the file. Slug conventionally date-suffixed (`topic-YYYY-MM-DD.md`). |
|
||||||
|
|
||||||
|
`session-log` also exists as a `type` value (for `docs/sessions/`) but is **out
|
||||||
|
of scope for `kb/`** — see Scope above.
|
||||||
|
|
||||||
|
### Decision vs incident vs runbook vs audit — how ambiguous cases got resolved
|
||||||
|
|
||||||
|
This came up repeatedly during the kb/ migration (docs that mixed genres got
|
||||||
|
split, not force-fit into one type):
|
||||||
|
|
||||||
|
- **decision vs incident**: a doc that both narrates "here's what broke" *and*
|
||||||
|
states "here's the guardrail we adopted because of it" is two documents.
|
||||||
|
Split the incident account into `kb/incidents/`, keep (or extract) the
|
||||||
|
resulting decision/guardrail into `kb/decisions/`. Example:
|
||||||
|
`home-assistant/DESIGN.md` → `kb/decisions/ha-configs-as-code.md` +
|
||||||
|
`kb/incidents/2026-07-22-ha-dwie-instancje.md`.
|
||||||
|
- **decision vs runbook**: if a decision doc contains a reusable operational
|
||||||
|
recipe (install steps, recovery procedure), that section is a runbook, not
|
||||||
|
part of the decision's rationale. Split it out. Example:
|
||||||
|
`deploy-runner` → `kb/services/job-deploy-runner.md` (how it works) +
|
||||||
|
`kb/decisions/deploy-runner-uzasadnienie.md` (why) +
|
||||||
|
`kb/runbooks/deploy-runner-install.md` (how to install/operate it).
|
||||||
|
- **subsystem vs audit**: a `subsystem` doc is the *maintained* description of
|
||||||
|
how something is designed/intended to work — you keep it in sync. An
|
||||||
|
`audit` is a *frozen* investigation result ("I checked X on this date and
|
||||||
|
found Y") — you don't rewrite it as things change, you write a new audit
|
||||||
|
or a decision/incident instead. This is why `audit` got its own `as_of`
|
||||||
|
field distinct from `updated`.
|
||||||
|
- When in doubt, prefer splitting a doc across two types over stretching one
|
||||||
|
type's frontmatter to cover mixed content — that's the pattern the
|
||||||
|
migration itself followed (see git log `feat(kb): SPLIT ...` commits for
|
||||||
|
worked examples).
|
||||||
|
|
||||||
|
## Visibility default: private
|
||||||
|
|
||||||
|
**`visibility` defaults to `private`.** Every new document must be written
|
||||||
|
`private` unless the operator has explicitly and consciously decided it
|
||||||
|
should be `public` — never infer or default to `public` on your own, even if
|
||||||
|
the content looks harmless. `visibility: public` documents are the only ones
|
||||||
|
`scripts/kb/gen_pages.py` will ever emit to the public kb-site (fail-closed:
|
||||||
|
missing/unparseable/unrecognized `visibility` is treated as private).
|
||||||
|
|
||||||
|
## After every change to kb/**/*.md
|
||||||
|
|
||||||
|
Run the validator and fix anything it flags before considering the edit done:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/kb/check_okf.py
|
||||||
|
```
|
||||||
|
|
||||||
|
It checks frontmatter parses, `okf` is pinned, `type`/`visibility`/`status`
|
||||||
|
are from their closed lists, dates are well-formed, `as_of`/`superseded_by`
|
||||||
|
are present exactly when required, and every `links`/path-like `contradicts`
|
||||||
|
entry resolves to a real file. A red run means something is broken — fix it,
|
||||||
|
don't skip it.
|
||||||
|
|
||||||
|
## Creating a new document
|
||||||
|
|
||||||
|
Use `scripts/kb/new-doc.sh <type> <slug> [--public]` to scaffold a
|
||||||
|
correctly-placed file with valid frontmatter, then fill in the content.
|
||||||
|
|
||||||
|
## If you just made a public change
|
||||||
|
|
||||||
|
If a file you created or edited carries `visibility: public`, don't forget
|
||||||
|
the KB site itself doesn't update on its own — see the `kb-publish` skill for
|
||||||
|
the one-line reminder to run `scripts/kb/publish.sh`.
|
||||||
78
scripts/kb/new-doc.sh
Executable file
78
scripts/kb/new-doc.sh
Executable file
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/kb/new-doc.sh — scaffold a new kb/ document with valid OKF v0.1 frontmatter.
|
||||||
|
#
|
||||||
|
# Usage: scripts/kb/new-doc.sh <type> <slug> [--public]
|
||||||
|
#
|
||||||
|
# <type> one of the kb/ document types (see TYPES below) — plural directory
|
||||||
|
# kb/<type>s/ must already exist.
|
||||||
|
# <slug> filename stem (no .md); becomes kb/<type>s/<slug>.md.
|
||||||
|
# --public sets visibility: public instead of the default private. Only pass
|
||||||
|
# this when the operator has explicitly decided the doc is public —
|
||||||
|
# see .claude/skills/kb-authoring/SKILL.md "Visibility default".
|
||||||
|
#
|
||||||
|
# Refuses to overwrite an existing file. Does not touch docs/sessions/ —
|
||||||
|
# session logs are not kb/ documents (type: session-log is out of scope here).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
TYPES=(node service subsystem decision incident runbook phase audit)
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "[new-doc] BLAD: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $# -ge 2 ]] || fail "usage: scripts/kb/new-doc.sh <type> <slug> [--public]"
|
||||||
|
|
||||||
|
DOC_TYPE="$1"
|
||||||
|
SLUG="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
VISIBILITY="private"
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--public) VISIBILITY="public" ;;
|
||||||
|
*) fail "opcja nieznana: $arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
is_valid_type=false
|
||||||
|
for t in "${TYPES[@]}"; do
|
||||||
|
[[ "$t" == "$DOC_TYPE" ]] && is_valid_type=true
|
||||||
|
done
|
||||||
|
"$is_valid_type" || fail "type '$DOC_TYPE' spoza listy (${TYPES[*]})"
|
||||||
|
|
||||||
|
[[ "$SLUG" =~ ^[a-z0-9][a-z0-9._-]*$ ]] \
|
||||||
|
|| fail "slug '$SLUG' — dozwolone tylko [a-z0-9._-], zaczynajac od [a-z0-9]"
|
||||||
|
|
||||||
|
TARGET_DIR="$REPO_ROOT/kb/${DOC_TYPE}s"
|
||||||
|
[[ -d "$TARGET_DIR" ]] || fail "katalog $TARGET_DIR nie istnieje"
|
||||||
|
|
||||||
|
TARGET_FILE="$TARGET_DIR/$SLUG.md"
|
||||||
|
[[ -e "$TARGET_FILE" ]] && fail "plik juz istnieje: $TARGET_FILE (nie nadpisuje)"
|
||||||
|
|
||||||
|
TODAY="$(date +%F)"
|
||||||
|
|
||||||
|
TITLE="$(echo "$SLUG" | tr '-' ' ' | sed 's/\b\(.\)/\u\1/g')"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "---"
|
||||||
|
echo 'okf: "0.1"'
|
||||||
|
echo "type: $DOC_TYPE"
|
||||||
|
echo "visibility: $VISIBILITY"
|
||||||
|
echo "status: active"
|
||||||
|
echo "updated: $TODAY"
|
||||||
|
if [[ "$DOC_TYPE" == "audit" ]]; then
|
||||||
|
echo "as_of: $TODAY"
|
||||||
|
fi
|
||||||
|
echo "links: []"
|
||||||
|
echo "---"
|
||||||
|
echo
|
||||||
|
echo "# $TITLE"
|
||||||
|
echo
|
||||||
|
} > "$TARGET_FILE"
|
||||||
|
|
||||||
|
echo "[new-doc] utworzono: kb/${DOC_TYPE}s/$SLUG.md (visibility: $VISIBILITY)"
|
||||||
|
echo "[new-doc] Uzupelnij tresc, potem scripts/kb/check_okf.py"
|
||||||
Loading…
Reference in a new issue