kb-recall 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kb_recall-0.1.0/.github/workflows/ci.yml +48 -0
- kb_recall-0.1.0/.github/workflows/publish.yml +33 -0
- kb_recall-0.1.0/.gitignore +21 -0
- kb_recall-0.1.0/CLAUDE.md +193 -0
- kb_recall-0.1.0/GUIDE.md +561 -0
- kb_recall-0.1.0/LICENSE +21 -0
- kb_recall-0.1.0/PKG-INFO +401 -0
- kb_recall-0.1.0/README.md +376 -0
- kb_recall-0.1.0/RELEASING.md +101 -0
- kb_recall-0.1.0/docs/toolsearch-truncation.md +76 -0
- kb_recall-0.1.0/kb_recall/__init__.py +0 -0
- kb_recall-0.1.0/kb_recall/cli.py +371 -0
- kb_recall-0.1.0/kb_recall/commands/compact.md +107 -0
- kb_recall-0.1.0/kb_recall/commands/init.md +88 -0
- kb_recall-0.1.0/kb_recall/commands/link-feature.md +37 -0
- kb_recall-0.1.0/kb_recall/commands/list.md +28 -0
- kb_recall-0.1.0/kb_recall/commands/load.md +27 -0
- kb_recall-0.1.0/kb_recall/commands/miss.md +52 -0
- kb_recall-0.1.0/kb_recall/commands/save.md +161 -0
- kb_recall-0.1.0/kb_recall/commands/tidy.md +107 -0
- kb_recall-0.1.0/kb_recall/hooks/__init__.py +0 -0
- kb_recall-0.1.0/kb_recall/hooks/hook_helpers.py +324 -0
- kb_recall-0.1.0/kb_recall/hooks/prompt_submit.py +535 -0
- kb_recall-0.1.0/kb_recall/server.py +2001 -0
- kb_recall-0.1.0/kb_recall/templates/claude-command.md +41 -0
- kb_recall-0.1.0/kb_recall/templates/claude-md-snippet.md +109 -0
- kb_recall-0.1.0/kb_recall/templates/claude-settings.json +15 -0
- kb_recall-0.1.0/kb_recall/templates/feature-README.md +80 -0
- kb_recall-0.1.0/kb_recall/templates/feature-memories.md +10 -0
- kb_recall-0.1.0/kb_recall/templates/features-index.md +11 -0
- kb_recall-0.1.0/pyproject.toml +79 -0
- kb_recall-0.1.0/scripts/analyze_effectiveness.py +282 -0
- kb_recall-0.1.0/scripts/lint_memories.py +213 -0
- kb_recall-0.1.0/tests/__init__.py +0 -0
- kb_recall-0.1.0/tests/test_cli.py +248 -0
- kb_recall-0.1.0/tests/test_hook_helpers.py +300 -0
- kb_recall-0.1.0/tests/test_prompt_submit.py +681 -0
- kb_recall-0.1.0/tests/test_server.py +897 -0
- kb_recall-0.1.0/uv.lock +1328 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
# Called by publish.yml so a tag push runs the same tests as the PR did,
|
|
8
|
+
# instead of a second copy of these steps that can drift out of sync.
|
|
9
|
+
workflow_call:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
# 3.10 is the floor declared in requires-python; the rest guard against
|
|
18
|
+
# a dependency or syntax change that only breaks on a newer interpreter.
|
|
19
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v3
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
|
|
28
|
+
- name: Install Python ${{ matrix.python-version }}
|
|
29
|
+
run: uv python install ${{ matrix.python-version }}
|
|
30
|
+
|
|
31
|
+
# --locked, not a bare `uv sync`: the lock file is committed, so CI should
|
|
32
|
+
# fail loudly on a stale lock rather than silently re-resolving and testing
|
|
33
|
+
# a different dependency set than the one developers actually run.
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: uv sync --locked --python ${{ matrix.python-version }}
|
|
36
|
+
|
|
37
|
+
- name: Lint
|
|
38
|
+
run: uv run ruff check kb_recall tests scripts
|
|
39
|
+
|
|
40
|
+
# Checks kb_recall only, not tests/scripts: the shipped package is what
|
|
41
|
+
# the type declarations have to be true for. Nothing ran mypy before this
|
|
42
|
+
# step existed, so it sat declared-but-unrun in `[dependency-groups] dev`
|
|
43
|
+
# and drifted to red while CI stayed green.
|
|
44
|
+
- name: Type check
|
|
45
|
+
run: uv run mypy kb_recall
|
|
46
|
+
|
|
47
|
+
- name: Test
|
|
48
|
+
run: uv run pytest -q
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
# Reuse the workflow that gates every PR rather than keeping a second copy of
|
|
10
|
+
# the test steps here. A tag push is the one event that cannot be cheaply
|
|
11
|
+
# redone — once a version is on PyPI it can never be re-uploaded under the same
|
|
12
|
+
# number — so the tests get re-run on exactly the tagged commit, not assumed
|
|
13
|
+
# from whatever the branch looked like at review time.
|
|
14
|
+
ci:
|
|
15
|
+
uses: ./.github/workflows/ci.yml
|
|
16
|
+
|
|
17
|
+
publish:
|
|
18
|
+
needs: ci
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
environment: pypi
|
|
21
|
+
permissions:
|
|
22
|
+
id-token: write # required for PyPI Trusted Publishing (OIDC), no API token needed
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v3
|
|
28
|
+
|
|
29
|
+
- name: Build package
|
|
30
|
+
run: uv build
|
|
31
|
+
|
|
32
|
+
- name: Publish to PyPI
|
|
33
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.vscode
|
|
2
|
+
.claude
|
|
3
|
+
CLAUDE.local.md
|
|
4
|
+
__pycache__/
|
|
5
|
+
|
|
6
|
+
# Build outputs. These are the entries that actually matter: `dist/`, `build/`
|
|
7
|
+
# and `*.egg-info` are what show up as untracked the moment anyone runs
|
|
8
|
+
# `uv build`.
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
|
|
13
|
+
# Tool caches. uv, pytest and ruff each drop a `.gitignore` containing `*` inside
|
|
14
|
+
# their own directory, so these are already ignored in practice — listed anyway
|
|
15
|
+
# so a contributor reading this file does not need to know that, and so the
|
|
16
|
+
# reasoning is not re-derived from a confusing `git check-ignore` result (which
|
|
17
|
+
# reports the *directories* as not ignored, because only their contents are).
|
|
18
|
+
.venv/
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
.mypy_cache/
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Recall MCP
|
|
2
|
+
|
|
3
|
+
MCP server that provides a Feature Knowledge Base for Claude Code. See README.md for architecture, tools, and setup.
|
|
4
|
+
|
|
5
|
+
## Working on this project
|
|
6
|
+
|
|
7
|
+
**Language:** Code and identifiers in English.
|
|
8
|
+
|
|
9
|
+
**Initiative:** Before implementing, proactively raise trade-offs and confirm approach with the user. Do not implement first and explain later.
|
|
10
|
+
|
|
11
|
+
**After changes:** Run `uv run recall-server` to verify the server starts without errors. Run `uv run pytest` for the hook helpers test suite.
|
|
12
|
+
If you edited a tool docstring, verify its length: `uv run python -c "import inspect; from kb_recall import server; print(len(inspect.getdoc(server.TOOL_NAME)))"` — see "Docstring length" below.
|
|
13
|
+
Editing `kb_recall/server.py` does not hot-reload the running MCP server — this session's connection may pick up the change (e.g. after an `mcp add`/`remove` or config edit forces a reconnect), but any *other* open Claude Code window has its own separate server process and needs its own reload to see the update.
|
|
14
|
+
|
|
15
|
+
## Development
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv sync # install deps
|
|
19
|
+
uv run recall-server # run locally (stdio mode)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Hooks API reference:** https://code.claude.com/docs/en/hooks.md — fetch the live page when working on `kb_recall/hooks/prompt_submit.py`. Do not vendor a copy into the repo: a snapshot of that page was kept here until 2026-09-16 and had silently fallen 12 events behind (21 of 33) within three months of being written, because a copied reference carries no signal that it is incomplete.
|
|
23
|
+
|
|
24
|
+
## Slash Command Standard
|
|
25
|
+
|
|
26
|
+
Every `commands/*.md` file must follow `templates/claude-command.md`. Key rules:
|
|
27
|
+
- First line ends with `Argument (optional): **$ARGUMENTS**`
|
|
28
|
+
- `## When to use` with at least one `Never...` boundary
|
|
29
|
+
- Step 1 (slug detection) is boilerplate — copy verbatim from the template
|
|
30
|
+
- Report step required for write commands; omit for read-only
|
|
31
|
+
|
|
32
|
+
## MCP Tool Docstring Standard
|
|
33
|
+
|
|
34
|
+
Every tool function in `kb_recall/server.py` must follow this 5-part structure in order:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
1. WHAT — one-line description of what the tool does
|
|
38
|
+
2. WHEN — when to call it / when NOT to call it
|
|
39
|
+
3. FORMAT — how to fill arguments correctly; include "Never..." anti-patterns
|
|
40
|
+
Args: — per-param, one line each. Place right after FORMAT, not last —
|
|
41
|
+
see "Docstring length" below.
|
|
42
|
+
4. OUTPUT — what to do after the tool returns: act on hints, error recovery, next call to make
|
|
43
|
+
5. EXAMPLES — few-shot examples for complex patterns (omit if trivial)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**WHEN** guides Claude's decision to call.
|
|
47
|
+
**FORMAT** prevents wrong inputs — negative "Never..." statements create hard boundaries.
|
|
48
|
+
**OUTPUT** closes the loop — without it, Claude reads a response and often doesn't act on it.
|
|
49
|
+
**EXAMPLES** (What/Why/Apply format) teach edge cases that prose alone can't convey.
|
|
50
|
+
|
|
51
|
+
Never delete the Examples block during a refactor — update the content, keep the block.
|
|
52
|
+
|
|
53
|
+
### Docstring length: ToolSearch truncates at ~2000-2150 chars
|
|
54
|
+
|
|
55
|
+
Claude Code's ToolSearch renders a deferred MCP tool's description with a hard cut around
|
|
56
|
+
~2000-2150 characters (measured independently twice on this server: 2011-2154 and 2100-2154)
|
|
57
|
+
— regardless of the tool's real docstring length, and with no error signal. Longer docstrings
|
|
58
|
+
lose a *larger fraction*, not a fixed amount: `load_feature_context` at 3933 chars kept ~51%;
|
|
59
|
+
`save_memory` at 7989 chars kept only ~27%. `Args:` is especially at risk — the JSON schema
|
|
60
|
+
carries no per-param description outside the docstring text (`parameters.properties` only has
|
|
61
|
+
type/title/default), so a cut before `Args:` means the model never learns what a parameter means.
|
|
62
|
+
|
|
63
|
+
For any docstring approaching or exceeding ~1800 chars:
|
|
64
|
+
- **Place `Args:` right after FORMAT**, before `OUTPUT`/`EXAMPLES`. `OUTPUT`/`EXAMPLES` may
|
|
65
|
+
safely sit last — they're either duplicated elsewhere or purely illustrative (see below).
|
|
66
|
+
- **Re-measure after every edit**: `uv run python -c "import inspect; from kb_recall import server; print(len(inspect.getdoc(server.TOOL_NAME)))"`.
|
|
67
|
+
Target comfortably under ~1900 chars through `Args:`. Small restorations drift back into the
|
|
68
|
+
danger zone one edit at a time — check every time, not just once at the end.
|
|
69
|
+
- **Cut what's duplicated in `templates/claude-md-snippet.md`** (copied into every consumer
|
|
70
|
+
project's CLAUDE.md by `recall setup`) before cutting anything that exists only in the
|
|
71
|
+
docstring — `grep` the template first.
|
|
72
|
+
- **Non-redundant content that still doesn't fit** → move it to a **response footer** (text
|
|
73
|
+
appended to the tool's return value on success) instead of deleting it — this channel isn't
|
|
74
|
+
subject to ToolSearch's cap at all. See `load_feature_context`/`save_memory`'s footers for the
|
|
75
|
+
pattern. This only covers *post-call* guidance (what to do after success) — it cannot help
|
|
76
|
+
decide whether/how to call the tool, so quality gates, FORMAT, and Args must stay in the safe
|
|
77
|
+
part of the docstring itself. Test: can this rule only be acted on using the tool's actual
|
|
78
|
+
return data (e.g. checking a loaded memory's tag against README text)? If yes, it's
|
|
79
|
+
footer-safe even if it looks redundant; if it's pre-call decision guidance (WHEN/FORMAT/
|
|
80
|
+
Args-shaped), it isn't — no matter how duplicated it looks elsewhere.
|
|
81
|
+
- **Cut Examples first** if space runs out — the rule an example illustrates should already be
|
|
82
|
+
stated in prose elsewhere (WHEN/FORMAT/Never-save lists). One example per distinct category is
|
|
83
|
+
usually enough; verify no two tools' remaining examples land on the same category by accident.
|
|
84
|
+
|
|
85
|
+
## Feature Knowledge Bases (recall-mcp)
|
|
86
|
+
|
|
87
|
+
Before anything else this session: check whether tools like `save_memory`,
|
|
88
|
+
`load_feature_context`, `list_features` are available to you.
|
|
89
|
+
|
|
90
|
+
- **Not available** → tell the user this project uses recall-mcp for persistent
|
|
91
|
+
feature knowledge across sessions, and offer to set it up for them right now
|
|
92
|
+
instead of asking them to open a terminal:
|
|
93
|
+
- If `recall` is on PATH, offer to run it yourself:
|
|
94
|
+
`cd <this project's directory> && recall setup`.
|
|
95
|
+
Only run it after the user agrees. Afterward, tell them to reload Claude Code —
|
|
96
|
+
you cannot trigger that yourself.
|
|
97
|
+
- If `recall` isn't found, tell the user kb-recall isn't installed here and give
|
|
98
|
+
them the one-line install: `uv tool install kb-recall`, then `recall setup` from
|
|
99
|
+
their project directory. Do not attempt to install it yourself.
|
|
100
|
+
Then continue with the user's original request normally.
|
|
101
|
+
- **Available** → follow the rules below.
|
|
102
|
+
|
|
103
|
+
Feature-specific context is stored in `~/.recall-mcp/` and accessed via MCP tools.
|
|
104
|
+
The hook handles loading automatically — KB index is injected at session start and the
|
|
105
|
+
active KB is loaded based on the current branch. You do not need to call `list_features`
|
|
106
|
+
or `load_feature_context` manually.
|
|
107
|
+
|
|
108
|
+
### Cross-feature search exists — narrow trigger
|
|
109
|
+
|
|
110
|
+
`search_features(query="...")` verifies a fact that might live in another feature —
|
|
111
|
+
see its own docstring for exact WHEN/FORMAT. Never use it for slug discovery; the
|
|
112
|
+
hook-injected feature index already covers that. Default scope is always the
|
|
113
|
+
current project — never pass `project="all"` or a project subset on your own
|
|
114
|
+
initiative.
|
|
115
|
+
|
|
116
|
+
**The moment a user asks whether something already exists, was done, was hit as a
|
|
117
|
+
bug, or has a rule/constraint elsewhere — or whether the current change affects
|
|
118
|
+
another feature — call `search_features` before answering.** This is a detectable
|
|
119
|
+
trigger tied to the user's own words, not a judgment call on your part; don't wait
|
|
120
|
+
for the save-before-write gate below to be the only path that reaches this tool.
|
|
121
|
+
|
|
122
|
+
**When the user asks to search other projects too — don't decide the scope
|
|
123
|
+
yourself.** Present the actual configured project names (e.g. via
|
|
124
|
+
`list_features()`) as choices and let them pick the relevant subset — never
|
|
125
|
+
assume the user already knows or will type exact names. In practice a bounded
|
|
126
|
+
subset is almost always the right scope (users rarely work across more than a
|
|
127
|
+
handful of projects at once); reserve `"all"` for when the user explicitly
|
|
128
|
+
wants literally everything — don't front it as an equally-weighted default,
|
|
129
|
+
since it tends to pull in noise from unrelated projects.
|
|
130
|
+
|
|
131
|
+
**Before saving a `[decision]`/`[rule]`/`[constraint]`/`[gotcha]` memory, or before
|
|
132
|
+
`report_miss`** — run this search first. If a match turns up:
|
|
133
|
+
- **Relevant here too, even if it lives elsewhere**: duplicate into the current KB
|
|
134
|
+
anyway — yes, even though it looks redundant — with a source note ("originally
|
|
135
|
+
documented in KB `<slug>`, dated `<date>`"); don't just add a `related_tickets`
|
|
136
|
+
pointer instead.
|
|
137
|
+
- **Current task appears to belong entirely to another KB**: don't conclude from a
|
|
138
|
+
snippet alone — confirm via an explicit self-declared signal (e.g. "Wrong-KB
|
|
139
|
+
duplicate, authoritative KB is X") or `load_feature_context(candidate_slug)`. Even
|
|
140
|
+
then, ask the user before skipping the current KB.
|
|
141
|
+
- **Searched only to answer a question, not to save**: just answer, no forced write.
|
|
142
|
+
|
|
143
|
+
### Save — in the same turn, not at the end
|
|
144
|
+
|
|
145
|
+
Call `save_memory(slug="<slug>", content="<insight>")` the moment you observe any of:
|
|
146
|
+
- A root cause or "turns out the real issue is..."
|
|
147
|
+
- A constraint, invariant, or rule not obvious from the code
|
|
148
|
+
- An approach you tried and rejected (and why)
|
|
149
|
+
- An architectural decision with non-obvious reasoning
|
|
150
|
+
- A promising direction or improvement idea raised but not yet decided — tag `[idea]`, note where discussion left off
|
|
151
|
+
|
|
152
|
+
Entries can be multi-sentence. Skip routine implementation details.
|
|
153
|
+
Write in English — KB content (memories and README sections) must be in English regardless of conversation language.
|
|
154
|
+
Any entry claiming something "doesn't exist / hasn't been built / isn't implemented" must include a `Verify: <grep/rg command you actually ran>` line — absence claims are the easiest to get wrong and the hardest to self-correct once trusted as source of truth.
|
|
155
|
+
|
|
156
|
+
After saving, if the tag qualifies for promotion, classify it by risk BEFORE answering the user:
|
|
157
|
+
- `[gotcha]` / `[constraint]` → section `critical_warnings`
|
|
158
|
+
- `[decision]` → section `architecture`
|
|
159
|
+
- `[rule]` → section `business_rules`
|
|
160
|
+
- `[bug]`, `[idea]`, `[pattern]`, `[resolved]` → skip by default — unless the content clearly matches another section's shape (e.g. a priority/triage synthesis matches `open_items`'s table, a step sequence matches `checklist`); route it to that section directly instead of skipping
|
|
161
|
+
|
|
162
|
+
**Pure append** (nothing existing needs removing, rewriting, or marking stale/superseded/resolved):
|
|
163
|
+
1. Output one line: `[recall-mcp] Promoted '{tag}' → {section} (auto): {title}.`
|
|
164
|
+
2. Call `update_readme(section="...", content="<new entry block only>", mode="append")` immediately — no approval needed.
|
|
165
|
+
|
|
166
|
+
**Removal or consolidation involved** (any existing entry needs removing, merging, or marking superseded/resolved):
|
|
167
|
+
1. Output one line: `[recall-mcp] Promoting '{tag}' → {section}.`
|
|
168
|
+
2. Read the current section content from the loaded KB (already in context).
|
|
169
|
+
3. Synthesize: remove stale/superseded entries, integrate the new memory alongside still-valid entries.
|
|
170
|
+
4. Call `update_readme(section="...", content="<synthesized>", mode="replace")` — `confirm` defaults to False, so this writes nothing and returns a real diff instead.
|
|
171
|
+
5. Show that diff verbatim — wrapped in a ```diff fenced code block for red/green coloring, never your own paraphrase — then ask "Apply to README `{section}`?"
|
|
172
|
+
6. Only after approval, call `update_readme(...)` again with `confirm=True` (same args) to actually write.
|
|
173
|
+
|
|
174
|
+
After promoting a `[decision]` — also scan `open_items`: if any row is resolved or rejected by this decision, show the updated table and ask "Apply to README `open_items`?" before calling `update_readme` — marking a row resolved always needs approval, never auto-write it.
|
|
175
|
+
|
|
176
|
+
After either promotion path succeeds, check whether the README content now fully captures the source memory's What/Why/Apply:
|
|
177
|
+
- **Fully captured**: ask "Mark the source memory (`id:XXXX`) as resolved now that it's promoted to README `{section}`?" — show the one-line closure note you'd write. Only on approval, call `save_memory(slug="<slug>", content="[resolved:XXXX] Promoted to README {section}: <one-line>")`. This matters because `[gotcha]`/`[decision]`/`[rule]` memories are always full-loaded (never index-only, see `load_feature_context`'s tag-tier pagination) — an unresolved duplicate of already-promoted content is pure wasted context on every future load.
|
|
178
|
+
- **README trimmed or paraphrased real reasoning out** (Why/Apply detail didn't make it in): don't ask to resolve — say so explicitly, since resolving would lose that detail (only the short closure line survives in the merged view; the raw memories-*.md file still has it, but recall-mcp's own guidance is not to read those directly).
|
|
179
|
+
|
|
180
|
+
Then answer the user's question normally.
|
|
181
|
+
|
|
182
|
+
### Update README when knowledge changes
|
|
183
|
+
|
|
184
|
+
Call `update_readme(slug="<slug>", section="<section>", content="<updated content>")` when:
|
|
185
|
+
- The moment you make or learn an architectural/business-rule decision — that's already covered by the Save flow above (save_memory → classify → promote); don't call update_readme directly for this
|
|
186
|
+
- You had to open files to answer a question about business logic or constraints (the KB is missing that knowledge — add it now)
|
|
187
|
+
- After `init_feature`, to fill in sections from scratch
|
|
188
|
+
|
|
189
|
+
### When you make a mistake the KB should have prevented
|
|
190
|
+
|
|
191
|
+
Only when the user explicitly flags it — never call this proactively for a mistake you caught yourself before it reached the user (that's a `save_memory` entry instead, e.g. tag `[gotcha]`).
|
|
192
|
+
- Call `report_miss(slug="<slug>", description="<what went wrong and what the KB should have said>")`.
|
|
193
|
+
- Immediately after, call `update_readme` on the section the miss revealed as missing.
|