contexer 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.
@@ -0,0 +1,8 @@
1
+ Run a guided context setup for this repo.
2
+
3
+ 1. Call `bootstrap_context` with `repo_path=""` to get inferred facts and gap assumptions.
4
+ 2. Present each item one at a time — state what was detected or assumed, then ask "Correct? (yes / no / your correction)". Wait for the reply before moving on.
5
+ 3. After each reply, call `update_context` to store the confirmed fact with full reasoning.
6
+ 4. When all items are done, confirm how many were stored.
7
+
8
+ Keep it conversational — no upfront lists, one item per turn.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "mcp__claude_ai_Notion__notion-fetch",
5
+ "mcp__claude_ai_Notion__notion-search",
6
+ "mcp__claude_ai_Notion__notion-update-page"
7
+ ]
8
+ }
9
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "contexer",
3
+ "owner": { "name": "bhargavamin" },
4
+ "description": "Contexer — MCP context engine for Claude Code",
5
+ "plugins": [
6
+ {
7
+ "name": "contexer",
8
+ "source": "./",
9
+ "description": "MCP context engine — persists decisions and constraints across Claude Code sessions"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "contexer",
3
+ "version": "0.1.0",
4
+ "description": "MCP context engine — persists decisions and constraints across Claude Code sessions",
5
+ "author": { "name": "bhargavamin" },
6
+ "homepage": "https://github.com/bhargavamin/contexer",
7
+ "repository": "https://github.com/bhargavamin/contexer",
8
+ "license": "MIT",
9
+ "keywords": ["mcp", "context", "memory", "claude-code"]
10
+ }
@@ -0,0 +1,28 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ name: Build and publish
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+ permissions:
14
+ id-token: write # required for OIDC trusted publisher
15
+ contents: read # required to checkout private repo
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: astral-sh/setup-uv@v5
21
+ with:
22
+ enable-cache: true
23
+
24
+ - name: Build package
25
+ run: uv build
26
+
27
+ - name: Publish to PyPI
28
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,28 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Test / tool caches
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .mypy_cache/
16
+
17
+ # IDEs / editors
18
+ .idea/
19
+ .vscode/
20
+
21
+ # macOS
22
+ .DS_Store
23
+
24
+ # Claude Code local overrides (project .claude/settings.json is tracked on purpose)
25
+ .claude/settings.local.json
26
+
27
+ # Dev-only MCP override — plugin handles global registration
28
+ .mcp.json
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,65 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ # Install dependencies
9
+ uv sync
10
+
11
+ # Run the server (stdio transport — for manual testing)
12
+ uv run python server.py
13
+
14
+ # Smoke-test the server responds to MCP initialize
15
+ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}' | uv run python server.py
16
+ ```
17
+
18
+ ## Architecture
19
+
20
+ Three files, no more:
21
+
22
+ - **`server.py`** — MCP server entry point. Defines five tools (`capture_context`, `update_context`, `get_context`, `get_context_for_prompt`, `bootstrap_context`) using `FastMCP`. Generates a `SESSION_ID` (UUID) at process start shared across all tool calls in a session. Delegates all logic to `store.py`.
23
+ - **`store.py`** — All read/write and filtering logic. `_passes_filter` is the core gate: content is stored only if it is novel (token-overlap check — >70% overlap with existing decisions = duplicate). Storage is capped at `MAX_ENTRIES = 500` per repo. Display is separately capped: `_UNFILTERED_DISPLAY = 10` for overview calls, `_FILTERED_DISPLAY = 25` for query/type-filtered calls.
24
+ - **`requirements.txt`** — Kept for reference; `pyproject.toml` is the authoritative dependency spec managed by `uv`.
25
+
26
+ ## Storage
27
+
28
+ Context is stored at `~/.contexer/<repo_slug>.json` — one file per repo. The slug is the repo path with non-alphanumeric characters replaced by underscores. Each file holds a flat list of entries, each with `id`, `type` (`task` | `decision`), `subtype` (`architecture` | `constraint` | `pattern` | `convention` — decisions only), `content`, `session_id`, and `timestamp`.
29
+
30
+ ## MCP integration
31
+
32
+ The server is registered in `~/.claude.json` under `mcpServers`:
33
+
34
+ ```json
35
+ {
36
+ "contexer": {
37
+ "type": "stdio",
38
+ "command": "uv",
39
+ "args": ["run", "--directory", "/Users/bhargavamin/repos/personal/contexer", "python", "server.py"]
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Session behaviour (hooks)
45
+
46
+ `~/.claude/settings.json` wires up hooks globally (applies to every repo):
47
+
48
+ - **`SessionStart`** — injects all conventions and constraints directly as project rules; injects a count pointer for deferred architecture/pattern decisions (fetched JIT via `get_context`); injects the bootstrap STOP directive if no context exists.
49
+ - **`UserPromptSubmit` (anchor, command, every prompt)** — writes the git root to `~/.contexer/.current_repo` so MCP tools can resolve `repo_path=""` correctly even across concurrent sessions. Runs before all mcp_tool hooks.
50
+ - **`UserPromptSubmit` (bootstrap, mcp_tool, once)** — calls `get_bootstrap_context_prompt` on the first prompt; injects the bootstrap directive if no context exists (fallback for when SessionStart is skipped).
51
+ - **`UserPromptSubmit` (capture, mcp_tool, once)** — calls `capture_context` with the first user prompt as the task description. Stores as `type=task` only — never as a decision or constraint.
52
+ - **`UserPromptSubmit` (rationale, mcp_tool, every prompt)** — calls `get_context_for_prompt` with the prompt text; auto-injects matching decisions when the prompt contains rationale keywords (why, reason, rationale, decided, etc.). Silent no-op for all other prompts.
53
+ - **`PreCompact`** — injects a systemMessage reminding Claude to call `update_context` for any unsaved decisions before the context window is compacted.
54
+ - **`PostCompact`** — re-injects the full context via systemMessage so Claude resumes with full awareness after compaction.
55
+
56
+ **During a session**, call `update_context` whenever you make a significant decision, establish a pattern, or document a constraint. Pass the full reasoning, not just the conclusion. Optionally pass `subtype` (`architecture` | `constraint` | `pattern` | `convention`) to enable filtered retrieval later. The server filters — if content doesn't meet the novelty criteria it will be silently discarded, so err on the side of calling it.
57
+
58
+ **Retrieving context JIT**: call `get_context` **before reading files** for any question about architecture, design decisions, rationale, constraints, patterns, or conventions. Fall back to reading files only when context is missing or the question is about current code state (exact syntax, current values). Use `query` for keyword search or `entry_type` to retrieve a specific subtype: `get_context(entry_type="constraint")` returns only constraints (up to 25). Use `limit` to override the display cap. When results are truncated, the output includes a `"showing N of M"` note so you know more exist.
59
+
60
+ ## Design constraints
61
+
62
+ - **Silent operation is essential.** Tools must not produce noise — `update_context` silently discards filtered content without logging.
63
+ - **No abstraction beyond what exists.** The three-file structure is intentional. Do not add classes, config files, or layers unless the spec changes.
64
+ - **`update_context` is called by Claude Code, not the developer.** Claude Code nominates content; the server filters. The filtering criterion is novelty — >70% token overlap with any existing decision is rejected as a duplicate, not an LLM call.
65
+ - **Git hooks and CLI commits are out of scope.** The MCP tool call path is the only capture mechanism.
contexer-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bhargav Amin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: contexer
3
+ Version: 0.1.0
4
+ Summary: MCP server that captures and retrieves architectural decisions and context for Claude Code sessions
5
+ Project-URL: Homepage, https://github.com/bhargavamin/contexer
6
+ Project-URL: Repository, https://github.com/bhargavamin/contexer
7
+ Project-URL: Issues, https://github.com/bhargavamin/contexer/issues
8
+ Author-email: Bhargav Amin <devops.techpro@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,claude,context,decisions,llm,mcp
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: mcp>=1.9.4
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Contexer
23
+
24
+ Contexer is a lightweight MCP server for Claude Code that automatically captures decisions made during coding sessions and surfaces them at the start of every future session — so Claude never starts blind.
25
+
26
+ ## The problem
27
+
28
+ Every Claude Code session starts with no memory of the previous one. CLAUDE.md files require manual maintenance and go stale. When Claude works autonomously, the reasoning behind decisions disappears when the session ends. Teams end up re-explaining the same constraints, conventions, and architecture choices every time.
29
+
30
+ Contexer solves this by capturing decisions as they happen — silently, automatically, in the background — and replaying them as project rules at session start.
31
+
32
+ ---
33
+
34
+ ## Quick start
35
+
36
+ Install takes under two minutes. See **[docs/install.md](docs/install.md)** for full steps, verification, and uninstall.
37
+ For hooks, tool internals, filter logic, and storage layout see **[docs/architecture.md](docs/architecture.md)**.
38
+
39
+ **Plugin (recommended):**
40
+
41
+ ```
42
+ /plugin marketplace add bhargavamin/contexer
43
+ /plugin install contexer@contexer
44
+ /reload-plugins
45
+ ```
46
+
47
+ **Manual:**
48
+
49
+ ```bash
50
+ git clone git@github.com:bhargavamin/contexer.git ~/tools/contexer
51
+ bash ~/tools/contexer/scripts/install.sh
52
+ ```
53
+
54
+ After install, **open a new Claude Code session in any repo.** If no context exists, Claude will automatically run a bootstrap to capture your first decisions. If context exists, all constraints and conventions are injected at session start.
55
+
56
+ ---
57
+
58
+ ## How it works
59
+
60
+ You work normally. Contexer runs in the background via Claude Code hooks.
61
+
62
+ ```
63
+ Session opens
64
+ └─▶ All conventions + constraints injected as project rules
65
+ Architecture/pattern decisions available on demand (JIT)
66
+ First session with no context → bootstrap runs automatically
67
+
68
+ You type a prompt
69
+ └─▶ Your first message is stored as the current task description
70
+ └─▶ "Why/reason/rationale/decided" questions auto-fetch matching decisions
71
+
72
+ Claude works
73
+ └─▶ Calls get_context when it needs architecture or pattern context
74
+ └─▶ Calls update_context when it makes a significant decision
75
+
76
+ Context window nears limit
77
+ └─▶ Claude is reminded to save any unsaved decisions before compaction
78
+ └─▶ After compaction, full context is reloaded automatically
79
+ ```
80
+
81
+ **You never call any tool directly.** Claude handles all tool calls. If Claude misses something, say *"store that decision"* and it will call `update_context` immediately.
82
+
83
+ ---
84
+
85
+ ## Decision types
86
+
87
+ Every stored decision has a `subtype` that controls when and how it is surfaced.
88
+
89
+ | Subtype | What it captures | Injected at session start? |
90
+ |---|---|---|
91
+ | `constraint` | Rules that must always apply — "never commit untested code" | Yes — always |
92
+ | `convention` | Team or project standards — "use uv not pip", "conventional commits" | Yes — always |
93
+ | `architecture` | Structural decisions — "chose FastMCP over low-level mcp.Server" | No — fetched on demand |
94
+ | `pattern` | Recurring implementation approaches — "plain dicts as function boundaries" | No — fetched on demand |
95
+
96
+ Constraints and conventions are injected directly at every session start because they apply to every task. Architecture and pattern decisions are large and task-specific — Claude fetches them just-in-time when the task requires them.
97
+
98
+ ---
99
+
100
+ ## Managing decisions
101
+
102
+ All operations use natural language. Claude translates them into the right tool call.
103
+
104
+ ### Store a decision
105
+
106
+ ```
107
+ "store that as a constraint"
108
+ "save this as a convention: always use uv not pip"
109
+ "remember this architecture decision"
110
+ ```
111
+
112
+ Claude calls `update_context` with the content and the appropriate subtype. The server applies a novelty filter before storing — content with more than 70% token overlap with an existing decision is silently discarded as a duplicate.
113
+
114
+ > **Note:** Your first prompt each session is automatically stored as the *task description* — not as a decision or constraint. If you open a session with an instruction like *"always update docs before committing"*, it is captured as the task, not stored as a constraint. To store it as a constraint, either complete the turn (Claude will call `update_context` at the end) or explicitly say *"store that as a constraint"*.
115
+
116
+ ### Query decisions
117
+
118
+ ```
119
+ "show me all constraints"
120
+ "what decisions did we make about postgres?"
121
+ "show everything stored for this repo"
122
+ ```
123
+
124
+ | Example call | What it returns |
125
+ |---|---|
126
+ | `get_context()` | Latest 10 decisions — overview |
127
+ | `get_context(entry_type="constraint")` | Up to 25 constraints |
128
+ | `get_context(query="postgres")` | Up to 25 decisions matching "postgres" |
129
+ | `get_context(limit=50)` | Up to 50 decisions |
130
+
131
+ ### Update a decision
132
+
133
+ ```
134
+ "update the uv decision — we switched back to pip"
135
+ "correct the constraint about commit format"
136
+ ```
137
+
138
+ Claude calls `update_context` with the revised content. The old entry is not removed — a new entry is added alongside it. If the revised content is too similar to the original (>70% token overlap), it will be filtered as a duplicate. Rephrase it to include what changed.
139
+
140
+ ### Remove a decision
141
+
142
+ ```
143
+ "delete the postgres decision"
144
+ "remove all outdated constraints"
145
+ ```
146
+
147
+ Claude reads `~/.contexer/<repo_slug>.json` and removes the matching entry directly. The file is plain JSON — you can also edit or prune it manually at any time.
148
+
149
+ ---
150
+
151
+ ## Tools reference
152
+
153
+ | Tool | Triggered by | What it does |
154
+ |---|---|---|
155
+ | `capture_context` | `UserPromptSubmit` hook — once per session | Stores the first prompt as the task description |
156
+ | `update_context` | Claude, mid-task | Nominates a decision; server filters before storing |
157
+ | `get_context` | Claude, on demand | Returns stored decisions — filtered by keyword or subtype |
158
+ | `get_context_for_prompt` | `UserPromptSubmit` hook — every prompt | Detects rationale questions and auto-injects matching decisions |
159
+ | `bootstrap_context` | Claude, first session with no context | Scans repo stack for inferable decisions; surfaces gap questions |
160
+
161
+ ---
162
+
163
+ ## Storage
164
+
165
+ Decisions are stored locally at `~/.contexer/<repo_slug>.json` — one file per repo, capped at 500 entries. No cloud, no database, no accounts.
166
+
167
+ Each entry contains:
168
+
169
+ | Field | Values |
170
+ |---|---|
171
+ | `id` | UUID |
172
+ | `type` | `task` or `decision` |
173
+ | `subtype` | `architecture` \| `constraint` \| `pattern` \| `convention` |
174
+ | `content` | The full decision text |
175
+ | `session_id` | UUID for the session that created it |
176
+ | `timestamp` | ISO 8601 UTC |
177
+
178
+ Inspect the store at any time:
179
+
180
+ ```bash
181
+ cat ~/.contexer/<repo_slug>.json
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Troubleshooting
187
+
188
+ **Claude isn't storing decisions automatically.**
189
+ Claude calls `update_context` at the end of significant decisions, not continuously. If something specific was missed, say *"store that decision"* and Claude will call it immediately.
190
+
191
+ **A decision was stored but later ignored.**
192
+ Constraints and conventions are injected at session start. If you added a new constraint mid-session, it will appear from the next session onward.
193
+
194
+ **A decision is outdated or wrong.**
195
+ Say *"delete the X decision"* or edit `~/.contexer/<repo_slug>.json` directly and remove the entry.
196
+
197
+ **The novelty filter rejected a new decision.**
198
+ If the content is too similar to an existing one (>70% token overlap), it is silently discarded. Rephrase it to be more specific, or remove the old entry first.
199
+
200
+ **No context was injected at session start.**
201
+ If no decisions are stored for the repo, the bootstrap flow runs instead. Complete bootstrap once and all future sessions will have context.
202
+
203
+ ---
204
+
205
+ ## License
206
+
207
+ MIT
@@ -0,0 +1,186 @@
1
+ # Contexer
2
+
3
+ Contexer is a lightweight MCP server for Claude Code that automatically captures decisions made during coding sessions and surfaces them at the start of every future session — so Claude never starts blind.
4
+
5
+ ## The problem
6
+
7
+ Every Claude Code session starts with no memory of the previous one. CLAUDE.md files require manual maintenance and go stale. When Claude works autonomously, the reasoning behind decisions disappears when the session ends. Teams end up re-explaining the same constraints, conventions, and architecture choices every time.
8
+
9
+ Contexer solves this by capturing decisions as they happen — silently, automatically, in the background — and replaying them as project rules at session start.
10
+
11
+ ---
12
+
13
+ ## Quick start
14
+
15
+ Install takes under two minutes. See **[docs/install.md](docs/install.md)** for full steps, verification, and uninstall.
16
+ For hooks, tool internals, filter logic, and storage layout see **[docs/architecture.md](docs/architecture.md)**.
17
+
18
+ **Plugin (recommended):**
19
+
20
+ ```
21
+ /plugin marketplace add bhargavamin/contexer
22
+ /plugin install contexer@contexer
23
+ /reload-plugins
24
+ ```
25
+
26
+ **Manual:**
27
+
28
+ ```bash
29
+ git clone git@github.com:bhargavamin/contexer.git ~/tools/contexer
30
+ bash ~/tools/contexer/scripts/install.sh
31
+ ```
32
+
33
+ After install, **open a new Claude Code session in any repo.** If no context exists, Claude will automatically run a bootstrap to capture your first decisions. If context exists, all constraints and conventions are injected at session start.
34
+
35
+ ---
36
+
37
+ ## How it works
38
+
39
+ You work normally. Contexer runs in the background via Claude Code hooks.
40
+
41
+ ```
42
+ Session opens
43
+ └─▶ All conventions + constraints injected as project rules
44
+ Architecture/pattern decisions available on demand (JIT)
45
+ First session with no context → bootstrap runs automatically
46
+
47
+ You type a prompt
48
+ └─▶ Your first message is stored as the current task description
49
+ └─▶ "Why/reason/rationale/decided" questions auto-fetch matching decisions
50
+
51
+ Claude works
52
+ └─▶ Calls get_context when it needs architecture or pattern context
53
+ └─▶ Calls update_context when it makes a significant decision
54
+
55
+ Context window nears limit
56
+ └─▶ Claude is reminded to save any unsaved decisions before compaction
57
+ └─▶ After compaction, full context is reloaded automatically
58
+ ```
59
+
60
+ **You never call any tool directly.** Claude handles all tool calls. If Claude misses something, say *"store that decision"* and it will call `update_context` immediately.
61
+
62
+ ---
63
+
64
+ ## Decision types
65
+
66
+ Every stored decision has a `subtype` that controls when and how it is surfaced.
67
+
68
+ | Subtype | What it captures | Injected at session start? |
69
+ |---|---|---|
70
+ | `constraint` | Rules that must always apply — "never commit untested code" | Yes — always |
71
+ | `convention` | Team or project standards — "use uv not pip", "conventional commits" | Yes — always |
72
+ | `architecture` | Structural decisions — "chose FastMCP over low-level mcp.Server" | No — fetched on demand |
73
+ | `pattern` | Recurring implementation approaches — "plain dicts as function boundaries" | No — fetched on demand |
74
+
75
+ Constraints and conventions are injected directly at every session start because they apply to every task. Architecture and pattern decisions are large and task-specific — Claude fetches them just-in-time when the task requires them.
76
+
77
+ ---
78
+
79
+ ## Managing decisions
80
+
81
+ All operations use natural language. Claude translates them into the right tool call.
82
+
83
+ ### Store a decision
84
+
85
+ ```
86
+ "store that as a constraint"
87
+ "save this as a convention: always use uv not pip"
88
+ "remember this architecture decision"
89
+ ```
90
+
91
+ Claude calls `update_context` with the content and the appropriate subtype. The server applies a novelty filter before storing — content with more than 70% token overlap with an existing decision is silently discarded as a duplicate.
92
+
93
+ > **Note:** Your first prompt each session is automatically stored as the *task description* — not as a decision or constraint. If you open a session with an instruction like *"always update docs before committing"*, it is captured as the task, not stored as a constraint. To store it as a constraint, either complete the turn (Claude will call `update_context` at the end) or explicitly say *"store that as a constraint"*.
94
+
95
+ ### Query decisions
96
+
97
+ ```
98
+ "show me all constraints"
99
+ "what decisions did we make about postgres?"
100
+ "show everything stored for this repo"
101
+ ```
102
+
103
+ | Example call | What it returns |
104
+ |---|---|
105
+ | `get_context()` | Latest 10 decisions — overview |
106
+ | `get_context(entry_type="constraint")` | Up to 25 constraints |
107
+ | `get_context(query="postgres")` | Up to 25 decisions matching "postgres" |
108
+ | `get_context(limit=50)` | Up to 50 decisions |
109
+
110
+ ### Update a decision
111
+
112
+ ```
113
+ "update the uv decision — we switched back to pip"
114
+ "correct the constraint about commit format"
115
+ ```
116
+
117
+ Claude calls `update_context` with the revised content. The old entry is not removed — a new entry is added alongside it. If the revised content is too similar to the original (>70% token overlap), it will be filtered as a duplicate. Rephrase it to include what changed.
118
+
119
+ ### Remove a decision
120
+
121
+ ```
122
+ "delete the postgres decision"
123
+ "remove all outdated constraints"
124
+ ```
125
+
126
+ Claude reads `~/.contexer/<repo_slug>.json` and removes the matching entry directly. The file is plain JSON — you can also edit or prune it manually at any time.
127
+
128
+ ---
129
+
130
+ ## Tools reference
131
+
132
+ | Tool | Triggered by | What it does |
133
+ |---|---|---|
134
+ | `capture_context` | `UserPromptSubmit` hook — once per session | Stores the first prompt as the task description |
135
+ | `update_context` | Claude, mid-task | Nominates a decision; server filters before storing |
136
+ | `get_context` | Claude, on demand | Returns stored decisions — filtered by keyword or subtype |
137
+ | `get_context_for_prompt` | `UserPromptSubmit` hook — every prompt | Detects rationale questions and auto-injects matching decisions |
138
+ | `bootstrap_context` | Claude, first session with no context | Scans repo stack for inferable decisions; surfaces gap questions |
139
+
140
+ ---
141
+
142
+ ## Storage
143
+
144
+ Decisions are stored locally at `~/.contexer/<repo_slug>.json` — one file per repo, capped at 500 entries. No cloud, no database, no accounts.
145
+
146
+ Each entry contains:
147
+
148
+ | Field | Values |
149
+ |---|---|
150
+ | `id` | UUID |
151
+ | `type` | `task` or `decision` |
152
+ | `subtype` | `architecture` \| `constraint` \| `pattern` \| `convention` |
153
+ | `content` | The full decision text |
154
+ | `session_id` | UUID for the session that created it |
155
+ | `timestamp` | ISO 8601 UTC |
156
+
157
+ Inspect the store at any time:
158
+
159
+ ```bash
160
+ cat ~/.contexer/<repo_slug>.json
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Troubleshooting
166
+
167
+ **Claude isn't storing decisions automatically.**
168
+ Claude calls `update_context` at the end of significant decisions, not continuously. If something specific was missed, say *"store that decision"* and Claude will call it immediately.
169
+
170
+ **A decision was stored but later ignored.**
171
+ Constraints and conventions are injected at session start. If you added a new constraint mid-session, it will appear from the next session onward.
172
+
173
+ **A decision is outdated or wrong.**
174
+ Say *"delete the X decision"* or edit `~/.contexer/<repo_slug>.json` directly and remove the entry.
175
+
176
+ **The novelty filter rejected a new decision.**
177
+ If the content is too similar to an existing one (>70% token overlap), it is silently discarded. Rephrase it to be more specific, or remove the old entry first.
178
+
179
+ **No context was injected at session start.**
180
+ If no decisions are stored for the repo, the bootstrap flow runs instead. Complete bootstrap once and all future sessions will have context.
181
+
182
+ ---
183
+
184
+ ## License
185
+
186
+ MIT
File without changes
@@ -0,0 +1,3 @@
1
+ from contexer.server import main
2
+
3
+ main()
@@ -0,0 +1,78 @@
1
+ import json
2
+ import uuid
3
+ from mcp.server.fastmcp import FastMCP
4
+ from contexer import store
5
+
6
+ SESSION_ID = str(uuid.uuid4())
7
+ mcp = FastMCP("contexer")
8
+
9
+
10
+ @mcp.tool()
11
+ def capture_context(description: str, repo_path: str = "") -> str:
12
+ """Called at the start of every task. Captures the developer's task description for the given repo."""
13
+ resolved = store._resolve_repo(repo_path)
14
+ if not resolved:
15
+ return "Skipped — repo path not detected."
16
+ entry_id = store.capture_task(resolved, description, SESSION_ID)
17
+ if entry_id is None:
18
+ return "Skipped — does not look like a task description."
19
+ return f"Captured. id={entry_id}"
20
+
21
+
22
+ @mcp.tool()
23
+ def update_context(content: str, repo_path: str = "", subtype: str = "") -> str:
24
+ """Called when Claude Code makes a significant decision mid-task. The server filters before storing.
25
+
26
+ subtype: optional classification for filtered retrieval — architecture | constraint | pattern | convention
27
+ """
28
+ resolved = store._resolve_repo(repo_path)
29
+ if not resolved:
30
+ return "Skipped — repo path not detected."
31
+ stored, entry_id = store.update_decision(resolved, content, SESSION_ID, subtype)
32
+ if stored:
33
+ return f"Stored. id={entry_id}"
34
+ return "Filtered — did not meet storage criteria."
35
+
36
+
37
+ @mcp.tool()
38
+ def get_context(repo_path: str = "", query: str = "", entry_type: str = "", limit: int = 0) -> str:
39
+ """Returns stored context for the current repository. Call this when the task requires project context.
40
+
41
+ query: optional keyword filter (case-insensitive substring match against decision content).
42
+ entry_type: optional subtype filter — architecture | constraint | pattern | convention
43
+ limit: max decisions to return (0 = auto: 25 for filtered queries, 10 for unfiltered overview).
44
+ """
45
+ resolved = store._resolve_repo(repo_path)
46
+ if not resolved:
47
+ return "No repo path detected."
48
+ return store.get_context(resolved, query, entry_type, limit)
49
+
50
+
51
+ @mcp.tool()
52
+ def bootstrap_context(repo_path: str = "") -> str:
53
+ """Scans a repo for inferable decisions and gap questions. Present inferred
54
+ items to the user for confirmation, store confirmed ones via update_context,
55
+ then ask the gap questions and store each answer."""
56
+ resolved = store._resolve_repo(repo_path)
57
+ if not resolved:
58
+ return json.dumps({"error": "repo path not detected"})
59
+ return json.dumps(store.bootstrap_scan(resolved), indent=2)
60
+
61
+
62
+ @mcp.tool()
63
+ def get_context_for_prompt(repo_path: str = "", prompt: str = "") -> str:
64
+ """Auto-called by UserPromptSubmit hook on every prompt. Detects rationale/decision
65
+ questions (why, reason, rationale, decided...) and injects matching stored decisions
66
+ as additionalContext. Returns empty string for non-rationale prompts — silent no-op."""
67
+ resolved = store._resolve_repo(repo_path)
68
+ if not resolved:
69
+ return ""
70
+ return store.get_context_for_prompt(resolved, prompt)
71
+
72
+
73
+ def main():
74
+ mcp.run()
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()