Maintain docs/RISK_REGISTER.md, the living, append-only list of every deferred item, interim shortcut, and open architecture decision for this project. Use when a shortcut is taken, a decision is postponed, a follow-up is filed, or an existing entry is actually resolved. DO NOT use for accepted, finalized architecture decisions (use adr-capture instead), for session-local working notes that don't need to survive across the team (use context-file / the local scratch-notes directory instead), or for anything that must stay confidential (the register is a committed, team-visible doc). Triggers: 'add to risk register', 'log this as a risk', 'track this shortcut', 'this is a known gap', 'mark risk #N resolved', 'what's on the risk register', 'what's still open'.
75
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
A single committed table (docs/RISK_REGISTER.md) of every deferred item, interim shortcut, and open architecture
decision that could otherwise get forgotten between sessions or team members. Unlike gitignored, session-local
scratch notes, this is a durable, shared, GitHub-visible record -- the register is the index; a scratch note or a PR
is where the extra detail lives, if there is more to say than fits in a row.
docs/RISK_REGISTER.md exists. If this is the first entry in a project and the file doesn't exist yet, create it
with a # Risk Register title, an intro paragraph (paraphrase the one above), and an empty table using the columns
in assets/templates/risk-register-row-template.yaml.assets/templates/risk-register-row-template.yaml -- the shape of a single row, illustratedassets/schemas/risk-register-row.schema.json -- the same shape, as enforceable constraintsscripts/validate-risk-register-append-only.sh and scripts/validate-risk-register-schema.sh, both wired into a
local pre-commit hook so a row can never silently vanish, and a malformed row can never be committedadr-capture), not a risk.| Column | Meaning |
|---|---|
# | Permanent id, assigned once, never reused or renumbered. Next id = current max + 1. |
| Item | Short name, not a sentence. |
| Type | Deferred (will do later) / Shortcut (interim workaround in place now) / Accepted Risk (known, decided to live with it) / Open Decision (a choice not yet made, blocking further work) |
| Description | Self-contained: what it is and why it exists. A reader with no other context should understand it. |
| Risk if unaddressed | One sentence: the concrete failure mode, not a vague "could be a problem." |
| Added | YYYY-MM-DD, the date the row was created. Never changes. |
| Status | Open until resolved, then Resolved. Lifecycle state only -- no date or rationale appended here. |
| Date | -- while Status is Open; the resolution date (YYYY-MM-DD) once Status flips to Resolved. Set together with Decision. |
| Decision | -- while Status is Open; a self-contained sentence on how it was resolved once Status flips to Resolved. Set together with Date. |
# in docs/RISK_REGISTER.md; the new row's # is that value + 1.Description -- don't assume the reader has whatever prompted this in front of them.
Never cite a .context/ file as where the extra detail lives -- .context/ is gitignored local scratch (see
row 11), so a reference to it may be dead for every reader but the one who filed it. If there is more detail than
fits in the row, inline the essential facts directly, or point at a committed doc (an ADR, an MR description,
another docs/*.md page) instead.Type from its enum, Added as YYYY-MM-DD, Status
starting Open with Date/Decision both --). Never insert out of # order, never renumber existing rows.0 before committing -- the
pre-commit hook runs both too, but checking early avoids a bounced commit.# or Item name.Status to Resolved, Date to the resolution date (YYYY-MM-DD), and Decision to a self-contained
sentence on how it was resolved -- all three in the same edit. The schema script rejects a row where Status is
Resolved but Date/Decision are still --, or vice versa.Status/Date/Decision. If the original Description is now
misleading given how it was actually resolved, add a short clarifying clause rather than rewriting it wholesale --
the row is a history, not just a current-state snapshot.scripts/validate-risk-register-schema.sh and confirm it passes now that all three cells moved together.Before treating an add-or-resolve edit as finished, confirm all of the following, not just that the edit was typed:
scripts/validate-risk-register-schema.sh passes with no output (content is well-formed).scripts/validate-risk-register-append-only.sh passes with no output (no previously-committed # went missing).Description and Risk if unaddressed stand alone without needing outside context.Status, Date, and Decision all changed together, and nothing else did.Status/Date/Decision change after commit) is convention, not a database
constraint. Nothing technically stops editing a row's other columns beyond that point; the point is that
everyone treats it as if something did. An unenforced rule that's actually followed is worth more here than an
enforced one that invites working around it -- the goal is a trustworthy history, not a tamper-proof one.NEVER delete a row or renumber an existing #.
WHY: The # is the permanent identity other docs and conversations reference; deleting it destroys the history
the register exists to preserve. Consequence: anything that pointed at that # now references a gap, and nobody
can tell whether the risk was resolved or simply erased.
BAD: Removing row 5 because the risk "isn't relevant anymore" instead of marking it Resolved.
GOOD: Status: Resolved, Date: 2026-08-01, Decision: No longer applicable because X.
NEVER edit a row's Item, Type, Description, Risk if unaddressed, or Added after it's committed.
WHY: Nothing technically blocks this, which is exactly why it matters to hold the line by hand -- rewriting a
row's past turns the register into a current-state snapshot instead of a history. Consequence: a reader comparing
today's register against an old MR discussion finds the row described differently than what was true at the time.
BAD: Editing row 3's Description to reflect what you now know, erasing what was true when it was filed.
GOOD: Leave the original Description as-is; add a short clarifying clause if truly needed, and put the update
in Decision where it belongs.
NEVER file a vague row ("investigate later", "might be an issue").
WHY: An unactionable row is noise. Consequence: once a few vague rows accumulate, nobody trusts any row
enough to act on it without re-investigating from scratch, defeating the point of keeping a register at all.
BAD: Description: Route fingerprinting stuff might need cleanup at some point.
GOOD: A concrete description plus a concrete, one-sentence risk if it stays unaddressed.
NEVER cite a .context/ file as where a row's extra detail lives.
WHY: .context/ is gitignored local scratch, not a committed, durable record -- a reference to it may point at
something no other reader, machine, or fresh clone can ever open, the moment the row is committed.
Consequence: the register's own self-containment requirement is broken by the row that's supposed to enforce it.
BAD: Description: ... see .context/findings/2026-07-31-foo.md for the exact detail.
GOOD: Inline the essential fact in the row itself, or reference a committed doc (ADR, MR, another docs/*.md
page) if it doesn't fit.
NEVER put confidential or personal data in a register row. WHY: This file is committed to the shared, GitHub-visible repo, unlike gitignored scratch notes. Consequence: confidential detail leaks to every teammate and CI job with repo read access, with no way to retroactively scrub it from git history. GOOD: Reference a scratch-note file or an internal ticket for anything sensitive, and keep the row itself generic.
NEVER mention a shortcut, deferred item, or open decision in a reply without filing the row in the same turn. WHY: No pre-commit hook can catch this -- the validation scripts only check a row that already exists, not whether one should exist because something register-worthy got described in prose and then dropped. Consequence: the register silently under-represents what's actually deferred, creating false confidence that everything open is tracked when it isn't. BAD: "One side note, not part of this task but worth flagging: ..." -- followed by moving on to something else. GOOD: Stop, add the row right then (even mid-task), then proceed -- or say explicitly it isn't actionable yet and confirm the row was filed rather than merely described.
Two independent checks; neither subsumes the other -- run both before committing (both are also wired into the pre-commit hook):
scripts/validate-risk-register-append-only.sh # History: did a previously-committed row id disappear vs. HEAD?
scripts/validate-risk-register-schema.sh # Content: does every row match risk-register-row.schema.json?# Expected output on success (both scripts): nothing printed, exit code 0.
# Expected output on failure: a one-line reason on stderr, exit code 1 -- fix and re-run before committing.validate-risk-register-append-only.sh is git-diff-based and would happily pass a row with a typo'd Type, since
nothing was deleted. validate-risk-register-schema.sh is point-in-time and has no git awareness, so it would
happily pass a file where an entire row vanished, as long as what remains is individually well-formed. Together they
cover "nothing was lost, and what's there is valid"; either alone does not.
Problem | Solution
Pre-commit blocks: row id missing vs. HEAD | validate-risk-register-append-only.sh caught a deletion --
| restore the row (mark Resolved instead) and re-stage.
Pre-commit blocks: type/added/status doesn't match | validate-risk-register-schema.sh caught a malformed field --
| fix it to match risk-register-row.schema.json's constraints.
Pre-commit blocks: schema required fields mismatch | The script's column order is out of sync with a schema edit --
| update validate-risk-register-schema.sh's expected_fields.
Not sure what the next # should be | Read the last row of the table; next # = its # + 1.
Unsure whether something belongs here or in an ADR | Is it a decision that's actually been made and is final? -> ADR.
| Is it open, deferred, or a known accepted risk? -> register row.| Topic | Reference | When to Use |
|---|---|---|
How this register differs from tech-debt and from session-local scratch notes | Relationship to Sibling Conventions | Deciding which of the three a given item belongs in |
| A real filing mistake and the check that would have caught it | Case Study: Verify "Unresolved" Before Filing | Before filing a new "Open Decision" row that feels surprising or unfamiliar |
a1083f4
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.