evoctx 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.
- evoctx-0.1.0/.github/workflows/ci.yml +28 -0
- evoctx-0.1.0/.github/workflows/release.yml +51 -0
- evoctx-0.1.0/.gitignore +19 -0
- evoctx-0.1.0/CHANGELOG.md +46 -0
- evoctx-0.1.0/CONTRIBUTING.md +44 -0
- evoctx-0.1.0/LICENSE +21 -0
- evoctx-0.1.0/PKG-INFO +375 -0
- evoctx-0.1.0/README.md +350 -0
- evoctx-0.1.0/SECURITY.md +126 -0
- evoctx-0.1.0/docs/clients.md +84 -0
- evoctx-0.1.0/docs/instructions-snippet.md +44 -0
- evoctx-0.1.0/examples/grants.example.yaml +61 -0
- evoctx-0.1.0/pyproject.toml +41 -0
- evoctx-0.1.0/src/evoctx/__init__.py +7 -0
- evoctx-0.1.0/src/evoctx/audit.py +159 -0
- evoctx-0.1.0/src/evoctx/cli.py +1190 -0
- evoctx-0.1.0/src/evoctx/db.py +467 -0
- evoctx-0.1.0/src/evoctx/grants.py +199 -0
- evoctx-0.1.0/src/evoctx/install.py +328 -0
- evoctx-0.1.0/src/evoctx/paths.py +28 -0
- evoctx-0.1.0/src/evoctx/policy.py +289 -0
- evoctx-0.1.0/src/evoctx/redact.py +66 -0
- evoctx-0.1.0/src/evoctx/secrets.py +48 -0
- evoctx-0.1.0/src/evoctx/server.py +209 -0
- evoctx-0.1.0/src/evoctx/synonyms.py +65 -0
- evoctx-0.1.0/templates/fastapi.md +121 -0
- evoctx-0.1.0/tests/conftest.py +24 -0
- evoctx-0.1.0/tests/test_audit.py +129 -0
- evoctx-0.1.0/tests/test_audit_prune.py +79 -0
- evoctx-0.1.0/tests/test_cli.py +112 -0
- evoctx-0.1.0/tests/test_concurrency.py +48 -0
- evoctx-0.1.0/tests/test_db.py +113 -0
- evoctx-0.1.0/tests/test_delete.py +56 -0
- evoctx-0.1.0/tests/test_export.py +42 -0
- evoctx-0.1.0/tests/test_grants.py +191 -0
- evoctx-0.1.0/tests/test_hooks.py +125 -0
- evoctx-0.1.0/tests/test_install.py +98 -0
- evoctx-0.1.0/tests/test_note_limits_and_doctor.py +87 -0
- evoctx-0.1.0/tests/test_provenance.py +68 -0
- evoctx-0.1.0/tests/test_redact.py +112 -0
- evoctx-0.1.0/tests/test_search_pagination.py +89 -0
- evoctx-0.1.0/tests/test_secrets.py +87 -0
- evoctx-0.1.0/tests/test_server.py +78 -0
- evoctx-0.1.0/tests/test_sessions.py +63 -0
- evoctx-0.1.0/tests/test_synonyms.py +107 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
14
|
+
python: ["3.10", "3.12"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python }}
|
|
21
|
+
- name: Install
|
|
22
|
+
run: pip install -e .[dev]
|
|
23
|
+
- name: Test
|
|
24
|
+
run: pytest tests/ -v
|
|
25
|
+
- name: Build wheel
|
|
26
|
+
run: |
|
|
27
|
+
pip install build
|
|
28
|
+
python -m build
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.x"
|
|
16
|
+
- name: Build sdist and wheel
|
|
17
|
+
run: |
|
|
18
|
+
pip install build
|
|
19
|
+
python -m build
|
|
20
|
+
- uses: actions/upload-artifact@v4
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment: release
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write # required for PyPI trusted publishing (OIDC) — no API token stored anywhere
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/download-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
37
|
+
|
|
38
|
+
github-release:
|
|
39
|
+
needs: publish
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
permissions:
|
|
42
|
+
contents: write # to create the GitHub release
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/download-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
|
48
|
+
- uses: softprops/action-gh-release@v2
|
|
49
|
+
with:
|
|
50
|
+
files: dist/*
|
|
51
|
+
generate_release_notes: true
|
evoctx-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.pyc
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
|
|
9
|
+
# Local store / personal data — never commit
|
|
10
|
+
*.db
|
|
11
|
+
context/
|
|
12
|
+
|
|
13
|
+
# Client configs with machine-specific paths
|
|
14
|
+
.mcp.json
|
|
15
|
+
mcp.cmd
|
|
16
|
+
|
|
17
|
+
# Design doc — private, never ship
|
|
18
|
+
personal-context-layer.md
|
|
19
|
+
/personal_docs/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-07-14 — first beta
|
|
4
|
+
|
|
5
|
+
Initial public release: a local, personal context store for AI coding assistants over MCP.
|
|
6
|
+
|
|
7
|
+
**Store & tools**
|
|
8
|
+
- SQLite store with FTS5 search — porter stemming (word-form matching) plus a hand-curated
|
|
9
|
+
dev-term synonym dictionary ("auth bug" also finds "login failure"); projects, notes, and
|
|
10
|
+
work sessions
|
|
11
|
+
- 9 MCP tools: `list_projects`, `get_project`, `search_context`, `get_by_id`, `write_note`,
|
|
12
|
+
`start_session`, `end_session`, `recent_sessions`, `list_grants`
|
|
13
|
+
- Sessions registered at start (not just summarized at the end) so an interrupted session still
|
|
14
|
+
surfaces its intent and partial notes instead of vanishing silently
|
|
15
|
+
- Every note records `author_client` — which client wrote it, so past notes read as
|
|
16
|
+
informational context rather than as commands from the current session
|
|
17
|
+
|
|
18
|
+
**Policy engine**
|
|
19
|
+
- Grants (`grants.yaml`, hand-edited, hot-reloaded): per-client tool allow-lists, project/tag
|
|
20
|
+
scoping, mandatory expiry
|
|
21
|
+
- Write-time secret redaction, unconditional, at the storage layer — common secret shapes are
|
|
22
|
+
masked before they ever touch disk, regardless of entry point or active grant
|
|
23
|
+
- Read-time per-grant redaction with stable session-local tokenization
|
|
24
|
+
- Append-only audit log (SQL-trigger enforced) recording every tool call, denial, and the
|
|
25
|
+
effective post-redaction query
|
|
26
|
+
|
|
27
|
+
**CLI (`evoctx`)**
|
|
28
|
+
- Full parity with every MCP tool, plus human-only operations an AI client can never call:
|
|
29
|
+
`delete`, `delete-project`, `export` (JSON/Markdown)
|
|
30
|
+
- `evoctx install <client>` writes MCP config for Claude Code, Claude Desktop, Cursor,
|
|
31
|
+
Windsurf, VS Code, and Codex CLI
|
|
32
|
+
- `evoctx install claude-code --hooks` additionally wires SessionStart/Stop hooks and
|
|
33
|
+
`permissions.deny` rules — the one enforcement mechanism beyond convention
|
|
34
|
+
- `evoctx doctor` — store health, schema version, active grant, cloud-sync warning,
|
|
35
|
+
activity-staleness warning, client-config detection
|
|
36
|
+
|
|
37
|
+
**Safety & portability**
|
|
38
|
+
- WAL-mode SQLite for real multi-process concurrency
|
|
39
|
+
- Cross-platform store location via `platformdirs`, with a legacy-path fallback for
|
|
40
|
+
pre-packaging installs
|
|
41
|
+
- Schema migrations (`PRAGMA user_version`), including a guard against opening a store with a
|
|
42
|
+
newer release than the running code
|
|
43
|
+
|
|
44
|
+
See [SECURITY.md](SECURITY.md) for what this does and doesn't protect against, and
|
|
45
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for the schema, so your data is never locked to this
|
|
46
|
+
project's code.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
evoctx is a young, single-maintainer project. That's a real risk if you're trusting it with
|
|
4
|
+
your accumulated notes — projects like this stall. Two things are designed to make that survivable
|
|
5
|
+
rather than catastrophic if it happens.
|
|
6
|
+
|
|
7
|
+
## Your data outlives the project
|
|
8
|
+
|
|
9
|
+
The store is 4 plain SQLite tables. If this project goes quiet, your data isn't locked in —
|
|
10
|
+
it's queryable with any SQLite client on day one, and `evoctx export` (or `sqlite3 store.db`
|
|
11
|
+
directly) gets it all out in JSON or Markdown.
|
|
12
|
+
|
|
13
|
+
```sql
|
|
14
|
+
notes(id, title, content, project, tags, session_id, author_client, created_at, updated_at)
|
|
15
|
+
projects(name, overview, conventions, open_questions, updated_at)
|
|
16
|
+
sessions(id, project, client, intent, started_at, ended_at, summary)
|
|
17
|
+
audit_log(id, ts, connection_id, client, grant_name, tool, action, query,
|
|
18
|
+
record_ids, result_count, response_bytes, redactions_applied)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`tags` is a JSON array stored as text. Everything else is exactly what it looks like. No opaque
|
|
22
|
+
blobs, no proprietary format, no encryption key you'd need this project's code to unwrap.
|
|
23
|
+
|
|
24
|
+
## How to contribute
|
|
25
|
+
|
|
26
|
+
- **Bug reports / feature requests:** open an issue. Include `evoctx doctor` output — it covers
|
|
27
|
+
version, schema version, store size, and client-config detection in one command.
|
|
28
|
+
- **Pull requests:** run `pytest tests/ -v` before opening one; CI runs the same matrix
|
|
29
|
+
(ubuntu/macOS/Windows × Python 3.10/3.12). New MCP tools need a CLI mirror in the same PR —
|
|
30
|
+
see the parity table in [README.md](README.md#cli-evoctx).
|
|
31
|
+
- **Security concerns:** see [SECURITY.md](SECURITY.md) first — it may already be a documented,
|
|
32
|
+
known limitation rather than something to report as new.
|
|
33
|
+
|
|
34
|
+
## Design principles, if you're extending this
|
|
35
|
+
|
|
36
|
+
- **CLI/MCP parity.** Every MCP tool gets a CLI mirror. The store must stay human-operable, not
|
|
37
|
+
just AI-operable.
|
|
38
|
+
- **Deletion and bulk export are human-only.** Never add an MCP tool that deletes or bulk-exports.
|
|
39
|
+
This is deliberate, not an oversight to "fix."
|
|
40
|
+
- **Grants gate the MCP path, not the filesystem.** Don't design a feature that implies it's a
|
|
41
|
+
sandbox against the AI client itself — see SECURITY.md §1.
|
|
42
|
+
- **Write-time redaction lives in `db.py`, not `policy.py`.** It has to catch CLI writes too,
|
|
43
|
+
which never pass through the policy engine. Keep the built-in secret floor at the lowest
|
|
44
|
+
common layer.
|
evoctx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Evo Bytes SRL
|
|
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.
|
evoctx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: evoctx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local personal context store for AI coding assistants, exposed over MCP — by Evo Bytes
|
|
5
|
+
Project-URL: Homepage, https://evobytes.ro
|
|
6
|
+
Author-email: Evo Bytes SRL <info@evobytes.ro>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai,claude,context,mcp,memory,model-context-protocol
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: mcp[cli]<2,>=1.9
|
|
20
|
+
Requires-Dist: platformdirs>=4
|
|
21
|
+
Requires-Dist: pyyaml>=6
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# evoctx
|
|
27
|
+
|
|
28
|
+
A local, personal context store for AI coding assistants, exposed over [MCP](https://modelcontextprotocol.io). By [Evo Bytes](https://evobytes.ro).
|
|
29
|
+
|
|
30
|
+
Your assistant reads project context at session start, searches past decisions mid-work, and writes notes back before ending — so the next session (in any MCP-capable client) starts oriented instead of from zero. One SQLite store on your machine, shared across every workspace and every client that connects. No cloud component.
|
|
31
|
+
|
|
32
|
+
> **Status: pre-beta.** Grants, redaction, audit logging, and sessions all work today. Not yet on PyPI — install from source until the first tagged release. Expect breaking changes until then.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
Requires Python 3.10+. Until the package is on PyPI, install from source:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
git clone <repo-url>
|
|
42
|
+
cd evoctx
|
|
43
|
+
python -m venv .venv
|
|
44
|
+
.venv/bin/pip install -e . # Windows: .venv\Scripts\pip.exe install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This installs one command into the venv: `evoctx`.
|
|
48
|
+
|
|
49
|
+
- `evoctx <subcommand>` — the CLI for humans. `evoctx help` shows everything.
|
|
50
|
+
- `evoctx serve` — launches the MCP server (stdio). Not for humans; your AI client runs this via its MCP config, written for you by `evoctx install` below.
|
|
51
|
+
|
|
52
|
+
## Making `evoctx` a system command
|
|
53
|
+
|
|
54
|
+
Right after install, `evoctx` only exists inside the venv — running it from a normal terminal
|
|
55
|
+
needs the full path or an activated venv. To get a plain `evoctx` from anywhere:
|
|
56
|
+
|
|
57
|
+
**Recommended: [pipx](https://pipx.pypa.io)** (installs the CLI into its own isolated
|
|
58
|
+
environment and puts it on PATH for you, on Windows/macOS/Linux alike):
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
pipx install -e . # from this repo, for now
|
|
62
|
+
pipx install evoctx # once it's on PyPI
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Alternative: [uv](https://docs.astral.sh/uv/)**, same idea:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
uv tool install -e .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Manual, no extra tool** — add the venv's executable folder to your `PATH`:
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
# Windows (PowerShell) — persists across terminals
|
|
75
|
+
[Environment]::SetEnvironmentVariable("Path", "$env:Path;$PWD\.venv\Scripts", "User")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# macOS / Linux — add to ~/.zshrc or ~/.bashrc
|
|
80
|
+
export PATH="$(pwd)/.venv/bin:$PATH"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Either way, only the CLI needs a friendly name on `PATH` — MCP client configs reference
|
|
84
|
+
the `evoctx` executable by full path (`evoctx install` resolves and writes that path,
|
|
85
|
+
plus the `serve` argument, for you).
|
|
86
|
+
|
|
87
|
+
## Where everything lives
|
|
88
|
+
|
|
89
|
+
One folder holds it all — run `evoctx doctor` and read the "store dir" line:
|
|
90
|
+
|
|
91
|
+
| File | What it is |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `store.db` | The store: notes, projects, sessions, audit log (SQLite) |
|
|
94
|
+
| `grants.yaml` | What connected AI clients may do — hand-edited, hot-reloaded |
|
|
95
|
+
| `active_grant.json` | Which grant is currently active (written by `evoctx grant activate`) |
|
|
96
|
+
|
|
97
|
+
Default folder (first match wins):
|
|
98
|
+
1. `CONTEXT_STORE` env var, if set
|
|
99
|
+
2. `~/.context/` if a store already exists there (legacy)
|
|
100
|
+
3. Per-OS user data dir — Windows: `%LOCALAPPDATA%\evoctx`, macOS: `~/Library/Application Support/evoctx`, Linux: `~/.local/share/evoctx`
|
|
101
|
+
|
|
102
|
+
## First ten minutes
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# 1. See that everything is healthy (also prints where your store lives)
|
|
106
|
+
evoctx doctor
|
|
107
|
+
|
|
108
|
+
# 2. Try it with sample data
|
|
109
|
+
evoctx demo-seed
|
|
110
|
+
evoctx search "JWT" # finds the demo decision note
|
|
111
|
+
evoctx get-project demo-webapp # overview, conventions, open questions
|
|
112
|
+
|
|
113
|
+
# 3. Register your real project (auto-detects stack from package.json/pyproject/README)
|
|
114
|
+
cd ~/code/my-api
|
|
115
|
+
evoctx init
|
|
116
|
+
|
|
117
|
+
# 4. Connect your AI client and restart it
|
|
118
|
+
evoctx install claude-code # or: cursor, windsurf, vscode, codex, claude-desktop
|
|
119
|
+
|
|
120
|
+
# 5. Paste the instructions snippet into the client's rules file
|
|
121
|
+
# (docs/instructions-snippet.md — this is what makes the AI actually use the store)
|
|
122
|
+
|
|
123
|
+
# 6. Work normally. Afterwards, see exactly what the AI did:
|
|
124
|
+
evoctx audit --since 1h
|
|
125
|
+
evoctx recent-sessions
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
From then on: the AI registers a session when it starts, reads your project context, records
|
|
129
|
+
decisions as it works, and closes the session with a summary. Next session — same client or a
|
|
130
|
+
different one — starts oriented.
|
|
131
|
+
|
|
132
|
+
## Connecting a client
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
evoctx install claude-code # or: claude-desktop, cursor, windsurf, vscode, codex
|
|
136
|
+
evoctx doctor # verify store, grants, server command, client configs
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`install` writes the MCP block into the client's config (existing file backed up to `.bak`,
|
|
140
|
+
existing entries merged, block printed for manual paste). Manual per-client setup:
|
|
141
|
+
[docs/clients.md](docs/clients.md). ChatGPT Desktop is not supported — no local stdio MCP.
|
|
142
|
+
|
|
143
|
+
`doctor` checks package/store/schema/FTS5/grant/server-command/client-configs, plus:
|
|
144
|
+
store size and note count, days since the last note or session (a configured client with no
|
|
145
|
+
recent activity usually means the instructions snippet isn't actually being followed), and
|
|
146
|
+
whether the store folder sits inside OneDrive/iCloud/Dropbox/Google Drive — those sync an
|
|
147
|
+
unencrypted SQLite file off-device by default, silently.
|
|
148
|
+
|
|
149
|
+
**Claude Code only:** `evoctx install claude-code --hooks` additionally writes `SessionStart`
|
|
150
|
+
and `Stop` hooks into `~/.claude/settings.json` (backed up first, like every other `install_*`
|
|
151
|
+
write) — the one enforcement mechanism in this project that goes beyond convention.
|
|
152
|
+
`SessionStart` nudges the assistant to call `start_session`/`recent_sessions`; `Stop` blocks
|
|
153
|
+
**once** (never twice — it checks Claude Code's own `stop_hook_active` flag) if a session opened
|
|
154
|
+
in the last 12 hours was never closed with `end_session`. It's a nudge, not a lock: ignoring the
|
|
155
|
+
message and asking Claude to stop again always succeeds.
|
|
156
|
+
|
|
157
|
+
Then tell the assistant to actually use it — add to your global instructions file (e.g. `~/.claude/CLAUDE.md`; canonical version in [docs/instructions-snippet.md](docs/instructions-snippet.md)):
|
|
158
|
+
|
|
159
|
+
```markdown
|
|
160
|
+
## MCP Context Store (evoctx)
|
|
161
|
+
MUST DO, every session:
|
|
162
|
+
- **Start:** call `start_session(project, intent)` first, then `recent_sessions()` — open entries
|
|
163
|
+
were likely interrupted; check their notes. Then `get_project(name)` for anything the task touches.
|
|
164
|
+
- **Before any non-trivial decision:** `search_context()` first — don't contradict a stored decision
|
|
165
|
+
without surfacing it.
|
|
166
|
+
- **As you go:** `write_note()` for non-obvious decisions/gotchas immediately (auto-linked to the session).
|
|
167
|
+
- **End:** call `end_session(summary)` — what was built, decided, and left open.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## MCP tools
|
|
171
|
+
|
|
172
|
+
| Tool | When the assistant uses it |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `start_session(project?, intent?)` | First call of a session — registers the work session; every `write_note` after is linked to it |
|
|
175
|
+
| `recent_sessions(project?, limit?)` | Right after — see what recent sessions did; open ones were likely interrupted, their notes are the only record |
|
|
176
|
+
| `list_projects()` | Discover registered projects |
|
|
177
|
+
| `get_project(name)` | Load overview, conventions, open questions, recent notes |
|
|
178
|
+
| `search_context(query, project?, limit?)` | Find relevant notes mid-work |
|
|
179
|
+
| `get_by_id(id)` | Full content after a search hit |
|
|
180
|
+
| `write_note(title, content, project?, tags?)` | Record decisions and observations as you go |
|
|
181
|
+
| `end_session(summary)` | Last call — closes the session with what was built/decided/open |
|
|
182
|
+
| `list_grants()` | What the active grant allows — so "do you have access to X?" gets an honest answer |
|
|
183
|
+
|
|
184
|
+
Writes are append-only by design: the assistant can add notes but never edit or delete. Deletion is a human-only operation. Note content over 256KB is rejected — split it into multiple notes. Every note also carries `author_client` (`cli` for a human, the client name for an assistant) so past notes read as informational context, not as instructions from the current session.
|
|
185
|
+
|
|
186
|
+
**Why sessions instead of just an end-of-session note:** a summary written at the end depends on the session surviving to the end. A session registered at the start shows up in `recent_sessions()` even if it crashes one minute in — flagged open, with its intent line and any notes it managed to write. Interrupted work stays visible instead of vanishing.
|
|
187
|
+
|
|
188
|
+
## CLI (`evoctx`)
|
|
189
|
+
|
|
190
|
+
Every MCP tool has a CLI mirror — the store is fully human-operable, not just AI-operable:
|
|
191
|
+
|
|
192
|
+
| MCP tool | CLI |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `list_projects()` | `evoctx list-projects` |
|
|
195
|
+
| `get_project(name)` | `evoctx get-project NAME` |
|
|
196
|
+
| `search_context(...)` | `evoctx search-context QUERY [--project NAME] [--limit N]` (alias: `search`) |
|
|
197
|
+
| `get_by_id(id)` | `evoctx get-by-id ID` — ID prefix is enough (alias: `show`) |
|
|
198
|
+
| `write_note(...)` | `evoctx write-note "Title" --project NAME --tags "#decision" --text "..."` (alias: `add`) |
|
|
199
|
+
| `start_session(...)` | `evoctx start-session [--project NAME] [--intent "..."]` |
|
|
200
|
+
| `end_session(summary)` | `evoctx end-session "summary" [--id ID]` — default: latest open |
|
|
201
|
+
| `recent_sessions(...)` | `evoctx recent-sessions [--project NAME] [--limit N]` |
|
|
202
|
+
|
|
203
|
+
Plus store management commands with no MCP equivalent (deliberately — deletion and bulk ops are human-only):
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
evoctx init [--dir PATH] [--template NAME] # register project (auto-detects stack)
|
|
207
|
+
evoctx new-project NAME --overview "..."
|
|
208
|
+
evoctx import-dir path/to/notes/ --project NAME
|
|
209
|
+
evoctx update-project NAME --field conventions --file conventions.md
|
|
210
|
+
evoctx list [--project NAME] # list notes
|
|
211
|
+
evoctx delete ID | --project NAME | --tag TAG # delete note(s) — asks to confirm, or pass --force
|
|
212
|
+
evoctx delete-project NAME [--with-notes] # delete a project (notes kept unlinked by default)
|
|
213
|
+
evoctx export [--format json|markdown] [PATH] # export everything — the whole store is yours to take
|
|
214
|
+
evoctx dump [--project NAME] [--note ID] # read-only dump of the whole store
|
|
215
|
+
evoctx templates # reusable convention templates (_fastapi, ...)
|
|
216
|
+
evoctx sync [PATH] # sync a markdown folder into the store
|
|
217
|
+
evoctx demo-seed # sample data to try things out (never automatic)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Deletion and export exist because privacy and portability are only real if you can act on
|
|
221
|
+
them.** Deletion is CLI-only, confirmed by default, never an MCP tool — an AI client can add
|
|
222
|
+
to the store but never remove from it. Export dumps every project, full note content, and every
|
|
223
|
+
session to JSON or Markdown, so "portable across vendors" is a command you can run, not a claim
|
|
224
|
+
you have to trust.
|
|
225
|
+
|
|
226
|
+
## Grants — what a client may do
|
|
227
|
+
|
|
228
|
+
Every tool call passes a policy gate. Grants live in `grants.yaml` in your store folder
|
|
229
|
+
(hand-edited, hot-reloaded — no restart needed); exactly one is active at a time.
|
|
230
|
+
|
|
231
|
+
First run auto-creates an `everything` grant (all tools, all projects, 30-day expiry) and
|
|
232
|
+
activates it, so nothing is broken out of the box. When it expires, calls fail with a message
|
|
233
|
+
naming the fix:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
evoctx grant activate everything
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
To tighten from there, add a scoped grant to `grants.yaml`
|
|
240
|
+
(full commented examples: [examples/grants.example.yaml](examples/grants.example.yaml)):
|
|
241
|
+
|
|
242
|
+
```yaml
|
|
243
|
+
- name: daily-coding
|
|
244
|
+
client: "*"
|
|
245
|
+
tools: [list_projects, get_project, search_context, get_by_id,
|
|
246
|
+
write_note, start_session, end_session, recent_sessions, list_grants]
|
|
247
|
+
scope:
|
|
248
|
+
projects: ["*"]
|
|
249
|
+
exclude_tags: ["#personal", "#finance"] # these notes become invisible
|
|
250
|
+
redactions:
|
|
251
|
+
- pattern: "sk-[A-Za-z0-9]{20,}" # mask API keys in every response
|
|
252
|
+
replace: "[API_KEY]"
|
|
253
|
+
expires: "+7d" # mandatory — ISO or +Nd/+Nh/+Nm
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
…then switch to it:
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
evoctx grant activate daily-coding # picked up by a running server immediately
|
|
260
|
+
evoctx grant list # all grants, active one starred
|
|
261
|
+
evoctx grant show daily-coding
|
|
262
|
+
evoctx grant deactivate # every call denied until re-activated
|
|
263
|
+
evoctx list-grants # what the active grant allows (MCP mirror)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
A denied call returns an error naming the exact `evoctx grant activate` command to fix it,
|
|
267
|
+
so the AI can relay it to you. Scope-blocked notes read as nonexistent (no existence leak);
|
|
268
|
+
out-of-scope **writes** fail loudly instead — silent write loss is worse.
|
|
269
|
+
|
|
270
|
+
## Redaction
|
|
271
|
+
|
|
272
|
+
Two layers, different jobs:
|
|
273
|
+
|
|
274
|
+
**Write-time (unconditional, built-in).** Every write — `write_note`, `start_session`,
|
|
275
|
+
`end_session`, from the CLI or from an MCP client, regardless of which grant is active —
|
|
276
|
+
is scanned for common secret shapes (API key prefixes, AWS/GitHub/Slack/Google tokens,
|
|
277
|
+
PEM private key blocks, `password=`/`token=` assignments) and masked *before it touches disk*.
|
|
278
|
+
This is the one place every write path funnels through, so a pasted secret doesn't sit raw in
|
|
279
|
+
`store.db` waiting for a read-time rule to hide it. Best-effort — regex can't catch every secret
|
|
280
|
+
shape or a value the model paraphrases — but it means the store isn't a plaintext secrets
|
|
281
|
+
aggregator by default.
|
|
282
|
+
|
|
283
|
+
**Read-time (per-grant, custom).** Grants can also carry their own regex redactions, applied to
|
|
284
|
+
every string leaving the store on a *read* (search hits, note content, project docs, session
|
|
285
|
+
summaries — and the audit log's own query field). Placeholders are stable within a session — the
|
|
286
|
+
same client name always becomes the same `[CLIENT]` token, distinct values get `[CLIENT_2]`,
|
|
287
|
+
`[CLIENT_3]` — so the AI can reason about entities without learning them. The original→placeholder
|
|
288
|
+
map lives in memory only and dies with the process; it is never written to disk. This layer is
|
|
289
|
+
one-way and doesn't touch what's stored — only what's returned.
|
|
290
|
+
|
|
291
|
+
## Audit — what did the AI actually see?
|
|
292
|
+
|
|
293
|
+
Every tool call is logged to an append-only `audit_log` table (SQL triggers block UPDATE/DELETE):
|
|
294
|
+
timestamp, client, connection, grant, tool, read/write/denied, the effective (post-redaction)
|
|
295
|
+
query, record IDs touched, result count, response size. Response *content* is never logged —
|
|
296
|
+
that would duplicate the store.
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
evoctx audit # newest first
|
|
300
|
+
evoctx audit --client cursor --since 24h
|
|
301
|
+
evoctx audit --action denied # what got blocked, and from whom
|
|
302
|
+
evoctx audit --tool get_by_id -n 100
|
|
303
|
+
evoctx audit-prune --older-than 90d # reclaim space — the one deliberate exception to append-only
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Deliberately **not** an MCP tool: the AI reading its own access record would defeat the mirror
|
|
307
|
+
and leak cross-client activity between differently-scoped grants. Human-only, CLI-only.
|
|
308
|
+
|
|
309
|
+
The log grows unbounded by design — pruning it any other way would mean giving some client a
|
|
310
|
+
way to edit its own trail. `evoctx audit-prune` is the single human-only exception: it drops the
|
|
311
|
+
append-only triggers, deletes rows older than the cutoff, and restores them immediately.
|
|
312
|
+
`evoctx doctor` shows the current row count so growth is visible, not silent.
|
|
313
|
+
|
|
314
|
+
## Threat model — honest limits
|
|
315
|
+
|
|
316
|
+
This is policy for cooperative clients, not a sandbox. Client identity is self-reported, the
|
|
317
|
+
store is unencrypted, and an AI client with its own filesystem tools can bypass grants entirely
|
|
318
|
+
by reading `store.db` or editing `grants.yaml` directly — policy gates the MCP protocol path,
|
|
319
|
+
not your disk. Full breakdown, including what to actually do about it (pairing with your
|
|
320
|
+
client's own permission system), in **[SECURITY.md](SECURITY.md)**.
|
|
321
|
+
|
|
322
|
+
## Roadmap — known gaps, not hidden ones
|
|
323
|
+
|
|
324
|
+
- **Search is keyword-based (SQLite FTS5), not semantic.** It knows word *forms* (porter
|
|
325
|
+
stemming — "configuring" matches "configuration") and a hand-curated dictionary of common
|
|
326
|
+
dev-term synonyms ("auth bug" also matches a note about "login failure" — `auth`/`login`/
|
|
327
|
+
`authentication`, `bug`/`error`/`issue`/`failure`, and similar groups in
|
|
328
|
+
[synonyms.py](src/evoctx/synonyms.py)). It does not know *meaning* — a synonym pair that isn't
|
|
329
|
+
in the dictionary, or two notes that are conceptually related without sharing any recognized
|
|
330
|
+
term, won't connect. Real embedding-based semantic search is the top post-beta priority; it's
|
|
331
|
+
a real dependency (a local embedding model or a vector index) rather than a quick addition,
|
|
332
|
+
which is why the deeper version didn't make beta — the synonym dictionary is the cheap 80% of
|
|
333
|
+
the gain without the dependency weight.
|
|
334
|
+
- **Encryption at rest** — not implemented; see [SECURITY.md §4](SECURITY.md).
|
|
335
|
+
- **Redaction is one-way.** Grant-based read-time redaction masks values in responses but has no
|
|
336
|
+
client-side re-hydration step to reverse it — once masked, the AI only ever sees the
|
|
337
|
+
placeholder, by design for now.
|
|
338
|
+
- **No defense against prompt injection via stored notes** — see [SECURITY.md §2](SECURITY.md).
|
|
339
|
+
This isn't solved by any AI memory system today, evoctx included.
|
|
340
|
+
- **Single active grant at a time** — no union semantics across multiple simultaneously active
|
|
341
|
+
grants.
|
|
342
|
+
|
|
343
|
+
None of these are silent limitations — if one of them matters for your use case, it's listed
|
|
344
|
+
here on purpose, not something you're expected to discover by hitting it.
|
|
345
|
+
|
|
346
|
+
## Layout
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
src/evoctx/
|
|
350
|
+
├── server.py MCP server entry point (FastMCP, stdio)
|
|
351
|
+
├── db.py SQLite schema, FTS5 search, migration runner
|
|
352
|
+
├── grants.py grants.yaml model, validation, expiry, hot-reload
|
|
353
|
+
├── policy.py Policy engine — every tool call passes through here
|
|
354
|
+
├── redact.py Response redaction with stable tokenization
|
|
355
|
+
├── audit.py Append-only audit log + query helpers
|
|
356
|
+
├── install.py Client config writers + doctor checks
|
|
357
|
+
├── cli.py evoctx CLI
|
|
358
|
+
└── paths.py Cross-platform store location resolution
|
|
359
|
+
docs/
|
|
360
|
+
├── clients.md Per-client manual setup
|
|
361
|
+
└── instructions-snippet.md Canonical rules-file block for every client
|
|
362
|
+
examples/
|
|
363
|
+
└── grants.example.yaml Commented grant recipes to copy from
|
|
364
|
+
SECURITY.md What this protects against, what it doesn't, and why
|
|
365
|
+
CONTRIBUTING.md How to contribute, and why your data outlives the project either way
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Contributing
|
|
369
|
+
|
|
370
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) — includes the exact schema, so your notes are never
|
|
371
|
+
locked to this project's code even if it stalls.
|
|
372
|
+
|
|
373
|
+
## License
|
|
374
|
+
|
|
375
|
+
[MIT](LICENSE) © Evo Bytes SRL
|