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.
Files changed (39) hide show
  1. kb_recall-0.1.0/.github/workflows/ci.yml +48 -0
  2. kb_recall-0.1.0/.github/workflows/publish.yml +33 -0
  3. kb_recall-0.1.0/.gitignore +21 -0
  4. kb_recall-0.1.0/CLAUDE.md +193 -0
  5. kb_recall-0.1.0/GUIDE.md +561 -0
  6. kb_recall-0.1.0/LICENSE +21 -0
  7. kb_recall-0.1.0/PKG-INFO +401 -0
  8. kb_recall-0.1.0/README.md +376 -0
  9. kb_recall-0.1.0/RELEASING.md +101 -0
  10. kb_recall-0.1.0/docs/toolsearch-truncation.md +76 -0
  11. kb_recall-0.1.0/kb_recall/__init__.py +0 -0
  12. kb_recall-0.1.0/kb_recall/cli.py +371 -0
  13. kb_recall-0.1.0/kb_recall/commands/compact.md +107 -0
  14. kb_recall-0.1.0/kb_recall/commands/init.md +88 -0
  15. kb_recall-0.1.0/kb_recall/commands/link-feature.md +37 -0
  16. kb_recall-0.1.0/kb_recall/commands/list.md +28 -0
  17. kb_recall-0.1.0/kb_recall/commands/load.md +27 -0
  18. kb_recall-0.1.0/kb_recall/commands/miss.md +52 -0
  19. kb_recall-0.1.0/kb_recall/commands/save.md +161 -0
  20. kb_recall-0.1.0/kb_recall/commands/tidy.md +107 -0
  21. kb_recall-0.1.0/kb_recall/hooks/__init__.py +0 -0
  22. kb_recall-0.1.0/kb_recall/hooks/hook_helpers.py +324 -0
  23. kb_recall-0.1.0/kb_recall/hooks/prompt_submit.py +535 -0
  24. kb_recall-0.1.0/kb_recall/server.py +2001 -0
  25. kb_recall-0.1.0/kb_recall/templates/claude-command.md +41 -0
  26. kb_recall-0.1.0/kb_recall/templates/claude-md-snippet.md +109 -0
  27. kb_recall-0.1.0/kb_recall/templates/claude-settings.json +15 -0
  28. kb_recall-0.1.0/kb_recall/templates/feature-README.md +80 -0
  29. kb_recall-0.1.0/kb_recall/templates/feature-memories.md +10 -0
  30. kb_recall-0.1.0/kb_recall/templates/features-index.md +11 -0
  31. kb_recall-0.1.0/pyproject.toml +79 -0
  32. kb_recall-0.1.0/scripts/analyze_effectiveness.py +282 -0
  33. kb_recall-0.1.0/scripts/lint_memories.py +213 -0
  34. kb_recall-0.1.0/tests/__init__.py +0 -0
  35. kb_recall-0.1.0/tests/test_cli.py +248 -0
  36. kb_recall-0.1.0/tests/test_hook_helpers.py +300 -0
  37. kb_recall-0.1.0/tests/test_prompt_submit.py +681 -0
  38. kb_recall-0.1.0/tests/test_server.py +897 -0
  39. 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.