dot-context 0.1.0a1__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.
- dot_context-0.1.0a1/LICENSE +21 -0
- dot_context-0.1.0a1/PKG-INFO +152 -0
- dot_context-0.1.0a1/README.md +110 -0
- dot_context-0.1.0a1/dot/__init__.py +10 -0
- dot_context-0.1.0a1/dot/api.py +220 -0
- dot_context-0.1.0a1/dot/cli.py +636 -0
- dot_context-0.1.0a1/dot/config.py +126 -0
- dot_context-0.1.0a1/dot/context/__init__.py +11 -0
- dot_context-0.1.0a1/dot/context/assembler.py +144 -0
- dot_context-0.1.0a1/dot/context/formatter.py +134 -0
- dot_context-0.1.0a1/dot/context/ranker.py +122 -0
- dot_context-0.1.0a1/dot/conversations/__init__.py +37 -0
- dot_context-0.1.0a1/dot/conversations/claude_code.py +346 -0
- dot_context-0.1.0a1/dot/conversations/ingest.py +206 -0
- dot_context-0.1.0a1/dot/conversations/source.py +93 -0
- dot_context-0.1.0a1/dot/conversations/watcher.py +141 -0
- dot_context-0.1.0a1/dot/daemon.py +396 -0
- dot_context-0.1.0a1/dot/doctor.py +228 -0
- dot_context-0.1.0a1/dot/indexer/__init__.py +12 -0
- dot_context-0.1.0a1/dot/indexer/chunker.py +235 -0
- dot_context-0.1.0a1/dot/indexer/embedder.py +120 -0
- dot_context-0.1.0a1/dot/indexer/parser.py +324 -0
- dot_context-0.1.0a1/dot/indexer/watcher.py +151 -0
- dot_context-0.1.0a1/dot/integrations/__init__.py +1 -0
- dot_context-0.1.0a1/dot/integrations/claude.py +106 -0
- dot_context-0.1.0a1/dot/integrations/copilot.py +66 -0
- dot_context-0.1.0a1/dot/integrations/git.py +148 -0
- dot_context-0.1.0a1/dot/integrations/mcp.py +217 -0
- dot_context-0.1.0a1/dot/memory/__init__.py +6 -0
- dot_context-0.1.0a1/dot/memory/decay.py +50 -0
- dot_context-0.1.0a1/dot/memory/decisions.py +169 -0
- dot_context-0.1.0a1/dot/memory/shared.py +162 -0
- dot_context-0.1.0a1/dot/memory/store.py +661 -0
- dot_context-0.1.0a1/dot_context.egg-info/PKG-INFO +152 -0
- dot_context-0.1.0a1/dot_context.egg-info/SOURCES.txt +49 -0
- dot_context-0.1.0a1/dot_context.egg-info/dependency_links.txt +1 -0
- dot_context-0.1.0a1/dot_context.egg-info/entry_points.txt +2 -0
- dot_context-0.1.0a1/dot_context.egg-info/requires.txt +27 -0
- dot_context-0.1.0a1/dot_context.egg-info/top_level.txt +2 -0
- dot_context-0.1.0a1/pyproject.toml +78 -0
- dot_context-0.1.0a1/setup.cfg +4 -0
- dot_context-0.1.0a1/tests/test_api.py +90 -0
- dot_context-0.1.0a1/tests/test_cli_init.py +62 -0
- dot_context-0.1.0a1/tests/test_context.py +58 -0
- dot_context-0.1.0a1/tests/test_conversation_capture_integration.py +199 -0
- dot_context-0.1.0a1/tests/test_conversations.py +444 -0
- dot_context-0.1.0a1/tests/test_doctor.py +346 -0
- dot_context-0.1.0a1/tests/test_indexer.py +73 -0
- dot_context-0.1.0a1/tests/test_mcp.py +76 -0
- dot_context-0.1.0a1/tests/test_memory.py +87 -0
- dot_context-0.1.0a1/tests/test_shared.py +118 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dot Contributors
|
|
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,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dot-context
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Dot — a local-first AI context memory daemon. Stop re-explaining your codebase to every AI tool.
|
|
5
|
+
Author: Dot Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/aryanp-spektra/dot-context-engine
|
|
8
|
+
Keywords: ai,context,memory,daemon,copilot,claude,embeddings
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: typer>=0.12
|
|
19
|
+
Requires-Dist: fastapi>=0.110
|
|
20
|
+
Requires-Dist: uvicorn[standard]>=0.29
|
|
21
|
+
Requires-Dist: watchdog>=4.0
|
|
22
|
+
Requires-Dist: GitPython>=3.1
|
|
23
|
+
Requires-Dist: SQLAlchemy>=2.0
|
|
24
|
+
Requires-Dist: APScheduler>=3.10
|
|
25
|
+
Requires-Dist: pydantic>=2.6
|
|
26
|
+
Requires-Dist: httpx>=0.27
|
|
27
|
+
Requires-Dist: rich>=13.7
|
|
28
|
+
Provides-Extra: ml
|
|
29
|
+
Requires-Dist: chromadb>=0.5; extra == "ml"
|
|
30
|
+
Requires-Dist: sentence-transformers>=2.7; extra == "ml"
|
|
31
|
+
Provides-Extra: treesitter
|
|
32
|
+
Requires-Dist: tree-sitter>=0.21; extra == "treesitter"
|
|
33
|
+
Requires-Dist: tree-sitter-language-pack>=0.4; extra == "treesitter"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy>=1.9; extra == "dev"
|
|
39
|
+
Provides-Extra: all
|
|
40
|
+
Requires-Dist: dot-context[dev,ml,treesitter]; extra == "all"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# ● dot
|
|
44
|
+
|
|
45
|
+
**A local-first AI context memory daemon.** Stop re-explaining your codebase to every AI tool.
|
|
46
|
+
|
|
47
|
+
Every AI tool starts from zero: you explain your architecture to Claude Code, then again
|
|
48
|
+
to Copilot, then again in a new chat. Dot ends that. It runs silently in the background,
|
|
49
|
+
builds a deep understanding of your codebase and the decisions behind it, and serves the
|
|
50
|
+
right context to any AI tool through a local REST API.
|
|
51
|
+
|
|
52
|
+
**Local. Private. Model-agnostic. Open source.** No code leaves your machine — embeddings
|
|
53
|
+
are generated locally with sentence-transformers, storage is SQLite + ChromaDB on disk.
|
|
54
|
+
|
|
55
|
+
## How it works
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
filesystem ──▶ watcher ──▶ AST parser ──▶ semantic chunker ──▶ local embeddings
|
|
59
|
+
git history ──▶ decision miner ──────────────────────────────▶ ┌─────────────┐
|
|
60
|
+
AI chats ────▶ decision capture ─────────────────────────────▶ │ SQLite + │
|
|
61
|
+
│ ChromaDB │
|
|
62
|
+
any AI tool ◀── /context (ranked, budgeted, formatted) ◀┴─────────────┘
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- **dot-indexer** chunks code by function/class (not fixed token windows), extracts
|
|
66
|
+
docstrings, imports, and TODO/`decided to…` comments, and embeds everything locally.
|
|
67
|
+
- **dot-memory** mines architectural decisions from commit messages and conversations,
|
|
68
|
+
with a forgetting curve — stale memories decay, frequently used ones are reinforced.
|
|
69
|
+
- **dot-context** assembles context ranked by semantic similarity, file proximity,
|
|
70
|
+
recency, and edit frequency, fills a token budget greedily, and formats it for the
|
|
71
|
+
consumer (Claude XML, concise Copilot, markdown, or raw JSON).
|
|
72
|
+
|
|
73
|
+
## Quick start
|
|
74
|
+
|
|
75
|
+
> Not on PyPI yet — install from source. Full walkthrough with test
|
|
76
|
+
> experiments: [docs/getting-started.md](docs/getting-started.md).
|
|
77
|
+
> Deep technical internals: [docs/internals.md](docs/internals.md)
|
|
78
|
+
> (prerequisites taught from zero in [docs/foundations.md](docs/foundations.md)).
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git clone https://github.com/aryanp-spektra/dot-context-engine.git
|
|
82
|
+
cd dot-context-engine && pip install -e ".[ml]" # or ".[dev]" for the light build
|
|
83
|
+
|
|
84
|
+
cd your-project
|
|
85
|
+
dot init # index the project, wire up git + Claude Code hooks
|
|
86
|
+
dot daemon start # keep watching in the background
|
|
87
|
+
|
|
88
|
+
dot ask "how does auth middleware work?"
|
|
89
|
+
dot inject "refactoring the billing module" --fmt claude | pbcopy
|
|
90
|
+
dot status
|
|
91
|
+
dot dashboard # web UI at http://localhost:7337/ui
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### CLI
|
|
95
|
+
|
|
96
|
+
| command | what it does |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `dot init` | initialize Dot in a project (+ git hook, CLAUDE.md, Claude Code hook) |
|
|
99
|
+
| `dot status` | what Dot knows about the current project |
|
|
100
|
+
| `dot ask "…"` | query your codebase in natural language |
|
|
101
|
+
| `dot inject [query]` | print assembled context — pipe it anywhere |
|
|
102
|
+
| `dot memory list/add/export/delete` | browse and manage captured decisions |
|
|
103
|
+
| `dot sync` | force re-index |
|
|
104
|
+
| `dot forget "pattern"` | remove memories matching a pattern |
|
|
105
|
+
| `dot dashboard` | open the web UI |
|
|
106
|
+
| `dot daemon run/start/stop/install-service` | control the daemon (launchd/systemd) |
|
|
107
|
+
|
|
108
|
+
### REST API (localhost:7337)
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
GET /status daemon health + project stats
|
|
112
|
+
GET /context?query=&file=&fmt=claude|copilot|markdown|raw
|
|
113
|
+
POST /memory capture a decision GET /memory browse
|
|
114
|
+
POST /memory/conversation extract decisions from an AI transcript
|
|
115
|
+
DELETE /memory/{id} forget
|
|
116
|
+
GET /graph dependency graph JSON
|
|
117
|
+
POST /ask natural-language codebase query
|
|
118
|
+
POST /sync force re-index
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Integrations
|
|
122
|
+
|
|
123
|
+
- **Claude Code** — `dot init` adds a CLAUDE.md section and a SessionStart hook that
|
|
124
|
+
injects context at the start of every session.
|
|
125
|
+
- **VS Code / Copilot** — the [extension](vscode-extension/) shows "what Dot knows about
|
|
126
|
+
this file" in a sidebar, registers Dot as a Language Model tool for Copilot Chat
|
|
127
|
+
(`#dotContext`), and offers one-click decision capture.
|
|
128
|
+
- **Anything else** — `curl localhost:7337/context?query=...&fmt=raw`.
|
|
129
|
+
|
|
130
|
+
## Development
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
make install # editable install with dev extras
|
|
134
|
+
make test # pytest
|
|
135
|
+
make lint # ruff
|
|
136
|
+
make dashboard # build the web UI into dashboard/dist (served at /ui)
|
|
137
|
+
make extension # compile the VS Code extension
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The ML stack (`chromadb`, `sentence-transformers`) and tree-sitter are **optional
|
|
141
|
+
extras** — without them Dot degrades to a deterministic hashing embedder, SQLite
|
|
142
|
+
brute-force vector search, and heuristic parsing, so the full pipeline still works
|
|
143
|
+
(and tests run) on any machine.
|
|
144
|
+
|
|
145
|
+
See [docs/getting-started.md](docs/getting-started.md) for the full
|
|
146
|
+
walkthrough and test experiments, [docs/internals.md](docs/internals.md)
|
|
147
|
+
for the complete technical deep dive (architecture, algorithms, math, and
|
|
148
|
+
trade-offs), and [docs/integrations.md](docs/integrations.md) for tool wiring.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# ● dot
|
|
2
|
+
|
|
3
|
+
**A local-first AI context memory daemon.** Stop re-explaining your codebase to every AI tool.
|
|
4
|
+
|
|
5
|
+
Every AI tool starts from zero: you explain your architecture to Claude Code, then again
|
|
6
|
+
to Copilot, then again in a new chat. Dot ends that. It runs silently in the background,
|
|
7
|
+
builds a deep understanding of your codebase and the decisions behind it, and serves the
|
|
8
|
+
right context to any AI tool through a local REST API.
|
|
9
|
+
|
|
10
|
+
**Local. Private. Model-agnostic. Open source.** No code leaves your machine — embeddings
|
|
11
|
+
are generated locally with sentence-transformers, storage is SQLite + ChromaDB on disk.
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
filesystem ──▶ watcher ──▶ AST parser ──▶ semantic chunker ──▶ local embeddings
|
|
17
|
+
git history ──▶ decision miner ──────────────────────────────▶ ┌─────────────┐
|
|
18
|
+
AI chats ────▶ decision capture ─────────────────────────────▶ │ SQLite + │
|
|
19
|
+
│ ChromaDB │
|
|
20
|
+
any AI tool ◀── /context (ranked, budgeted, formatted) ◀┴─────────────┘
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **dot-indexer** chunks code by function/class (not fixed token windows), extracts
|
|
24
|
+
docstrings, imports, and TODO/`decided to…` comments, and embeds everything locally.
|
|
25
|
+
- **dot-memory** mines architectural decisions from commit messages and conversations,
|
|
26
|
+
with a forgetting curve — stale memories decay, frequently used ones are reinforced.
|
|
27
|
+
- **dot-context** assembles context ranked by semantic similarity, file proximity,
|
|
28
|
+
recency, and edit frequency, fills a token budget greedily, and formats it for the
|
|
29
|
+
consumer (Claude XML, concise Copilot, markdown, or raw JSON).
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
> Not on PyPI yet — install from source. Full walkthrough with test
|
|
34
|
+
> experiments: [docs/getting-started.md](docs/getting-started.md).
|
|
35
|
+
> Deep technical internals: [docs/internals.md](docs/internals.md)
|
|
36
|
+
> (prerequisites taught from zero in [docs/foundations.md](docs/foundations.md)).
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/aryanp-spektra/dot-context-engine.git
|
|
40
|
+
cd dot-context-engine && pip install -e ".[ml]" # or ".[dev]" for the light build
|
|
41
|
+
|
|
42
|
+
cd your-project
|
|
43
|
+
dot init # index the project, wire up git + Claude Code hooks
|
|
44
|
+
dot daemon start # keep watching in the background
|
|
45
|
+
|
|
46
|
+
dot ask "how does auth middleware work?"
|
|
47
|
+
dot inject "refactoring the billing module" --fmt claude | pbcopy
|
|
48
|
+
dot status
|
|
49
|
+
dot dashboard # web UI at http://localhost:7337/ui
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### CLI
|
|
53
|
+
|
|
54
|
+
| command | what it does |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `dot init` | initialize Dot in a project (+ git hook, CLAUDE.md, Claude Code hook) |
|
|
57
|
+
| `dot status` | what Dot knows about the current project |
|
|
58
|
+
| `dot ask "…"` | query your codebase in natural language |
|
|
59
|
+
| `dot inject [query]` | print assembled context — pipe it anywhere |
|
|
60
|
+
| `dot memory list/add/export/delete` | browse and manage captured decisions |
|
|
61
|
+
| `dot sync` | force re-index |
|
|
62
|
+
| `dot forget "pattern"` | remove memories matching a pattern |
|
|
63
|
+
| `dot dashboard` | open the web UI |
|
|
64
|
+
| `dot daemon run/start/stop/install-service` | control the daemon (launchd/systemd) |
|
|
65
|
+
|
|
66
|
+
### REST API (localhost:7337)
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
GET /status daemon health + project stats
|
|
70
|
+
GET /context?query=&file=&fmt=claude|copilot|markdown|raw
|
|
71
|
+
POST /memory capture a decision GET /memory browse
|
|
72
|
+
POST /memory/conversation extract decisions from an AI transcript
|
|
73
|
+
DELETE /memory/{id} forget
|
|
74
|
+
GET /graph dependency graph JSON
|
|
75
|
+
POST /ask natural-language codebase query
|
|
76
|
+
POST /sync force re-index
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Integrations
|
|
80
|
+
|
|
81
|
+
- **Claude Code** — `dot init` adds a CLAUDE.md section and a SessionStart hook that
|
|
82
|
+
injects context at the start of every session.
|
|
83
|
+
- **VS Code / Copilot** — the [extension](vscode-extension/) shows "what Dot knows about
|
|
84
|
+
this file" in a sidebar, registers Dot as a Language Model tool for Copilot Chat
|
|
85
|
+
(`#dotContext`), and offers one-click decision capture.
|
|
86
|
+
- **Anything else** — `curl localhost:7337/context?query=...&fmt=raw`.
|
|
87
|
+
|
|
88
|
+
## Development
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
make install # editable install with dev extras
|
|
92
|
+
make test # pytest
|
|
93
|
+
make lint # ruff
|
|
94
|
+
make dashboard # build the web UI into dashboard/dist (served at /ui)
|
|
95
|
+
make extension # compile the VS Code extension
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The ML stack (`chromadb`, `sentence-transformers`) and tree-sitter are **optional
|
|
99
|
+
extras** — without them Dot degrades to a deterministic hashing embedder, SQLite
|
|
100
|
+
brute-force vector search, and heuristic parsing, so the full pipeline still works
|
|
101
|
+
(and tests run) on any machine.
|
|
102
|
+
|
|
103
|
+
See [docs/getting-started.md](docs/getting-started.md) for the full
|
|
104
|
+
walkthrough and test experiments, [docs/internals.md](docs/internals.md)
|
|
105
|
+
for the complete technical deep dive (architecture, algorithms, math, and
|
|
106
|
+
trade-offs), and [docs/integrations.md](docs/integrations.md) for tool wiring.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Dot — a local-first AI context memory daemon.
|
|
2
|
+
|
|
3
|
+
Dot watches your codebase, indexes it semantically, captures architectural
|
|
4
|
+
decisions, and serves the right context to any AI tool via a local REST API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
DEFAULT_PORT = 7337
|
|
10
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Local REST API, served on localhost:7337.
|
|
2
|
+
|
|
3
|
+
Endpoints:
|
|
4
|
+
GET /status daemon health + project stats
|
|
5
|
+
GET /context assembled context for a file/query
|
|
6
|
+
POST /memory capture a decision/memory
|
|
7
|
+
GET /memory browse memories
|
|
8
|
+
DELETE /memory/{id} forget a memory
|
|
9
|
+
GET /graph dependency graph as JSON
|
|
10
|
+
POST /ask natural-language query of the codebase
|
|
11
|
+
POST /sync force re-index
|
|
12
|
+
POST /hooks/git/commit git post-commit ping (installed by dot init)
|
|
13
|
+
GET /ui web dashboard (when dashboard/dist is built)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import threading
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import TYPE_CHECKING, Literal
|
|
22
|
+
|
|
23
|
+
from fastapi import FastAPI, HTTPException, Query, Response
|
|
24
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
25
|
+
from pydantic import BaseModel, Field
|
|
26
|
+
|
|
27
|
+
from dot import __version__
|
|
28
|
+
from dot.context.formatter import FORMATS, context_to_dict, format_context
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from dot.daemon import Daemon
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MemoryIn(BaseModel):
|
|
37
|
+
content: str = Field(min_length=3)
|
|
38
|
+
kind: Literal["decision", "rejected", "action_item", "note", "conversation"] = "note"
|
|
39
|
+
source: str = "api"
|
|
40
|
+
file_path: str = ""
|
|
41
|
+
tags: list[str] = []
|
|
42
|
+
confidence: float = Field(default=1.0, ge=0.0, le=1.0)
|
|
43
|
+
share: bool = False # also append to dot-memories.jsonl for the team
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ConversationIn(BaseModel):
|
|
47
|
+
transcript: str = Field(min_length=10)
|
|
48
|
+
source: str = "conversation"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AskIn(BaseModel):
|
|
52
|
+
question: str = Field(min_length=3)
|
|
53
|
+
current_file: str | None = None
|
|
54
|
+
fmt: str = "markdown"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SyncIn(BaseModel):
|
|
58
|
+
force: bool = False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def create_app(daemon: Daemon) -> FastAPI:
|
|
62
|
+
app = FastAPI(
|
|
63
|
+
title="Dot",
|
|
64
|
+
version=__version__,
|
|
65
|
+
description="Local-first AI context memory daemon",
|
|
66
|
+
)
|
|
67
|
+
# The dashboard dev server (vite) runs on another port; same machine only.
|
|
68
|
+
app.add_middleware(
|
|
69
|
+
CORSMiddleware,
|
|
70
|
+
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
|
|
71
|
+
allow_methods=["*"],
|
|
72
|
+
allow_headers=["*"],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@app.get("/status")
|
|
76
|
+
def status() -> dict:
|
|
77
|
+
return daemon.status()
|
|
78
|
+
|
|
79
|
+
@app.get("/context")
|
|
80
|
+
def get_context(
|
|
81
|
+
query: str = "",
|
|
82
|
+
file: str | None = Query(default=None, description="current file path"),
|
|
83
|
+
fmt: str = Query(default="raw", description=f"one of {FORMATS}"),
|
|
84
|
+
token_budget: int | None = Query(default=None, ge=100, le=100_000),
|
|
85
|
+
profile: str | None = None,
|
|
86
|
+
):
|
|
87
|
+
if fmt not in FORMATS:
|
|
88
|
+
raise HTTPException(422, f"fmt must be one of {FORMATS}")
|
|
89
|
+
context = daemon.assembler.assemble(
|
|
90
|
+
query=query, current_file=file, token_budget=token_budget, profile=profile
|
|
91
|
+
)
|
|
92
|
+
if fmt == "raw":
|
|
93
|
+
return context_to_dict(context)
|
|
94
|
+
return Response(
|
|
95
|
+
content=format_context(context, fmt),
|
|
96
|
+
media_type="text/plain; charset=utf-8",
|
|
97
|
+
headers={"x-dot-assembly-ms": f"{context.assembly_ms:.1f}"},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@app.post("/memory", status_code=201)
|
|
101
|
+
def add_memory(body: MemoryIn) -> dict:
|
|
102
|
+
memory = daemon.store.add_memory(
|
|
103
|
+
content=body.content,
|
|
104
|
+
kind=body.kind,
|
|
105
|
+
source=body.source,
|
|
106
|
+
file_path=body.file_path,
|
|
107
|
+
tags=body.tags,
|
|
108
|
+
confidence=body.confidence,
|
|
109
|
+
)
|
|
110
|
+
shared = False
|
|
111
|
+
if body.share:
|
|
112
|
+
from dot.memory.shared import export_memory
|
|
113
|
+
|
|
114
|
+
shared = export_memory(daemon.config, memory)
|
|
115
|
+
return {"id": memory.memory_id, "kind": memory.kind, "shared": shared}
|
|
116
|
+
|
|
117
|
+
@app.post("/memory/conversation", status_code=201)
|
|
118
|
+
def capture_conversation(body: ConversationIn) -> dict:
|
|
119
|
+
captured = daemon.decisions.capture_from_conversation(body.transcript, body.source)
|
|
120
|
+
return {"captured": len(captured), "ids": [memory.memory_id for memory in captured]}
|
|
121
|
+
|
|
122
|
+
@app.get("/memory")
|
|
123
|
+
def list_memories(
|
|
124
|
+
kind: str | None = None,
|
|
125
|
+
query: str | None = None,
|
|
126
|
+
limit: int = Query(default=50, ge=1, le=1000),
|
|
127
|
+
) -> dict:
|
|
128
|
+
if query:
|
|
129
|
+
memories = daemon.store.query_memories(query, n=limit)
|
|
130
|
+
else:
|
|
131
|
+
memories = daemon.store.list_memories(kind=kind, limit=limit)
|
|
132
|
+
return {
|
|
133
|
+
"memories": [
|
|
134
|
+
{
|
|
135
|
+
"id": memory.memory_id,
|
|
136
|
+
"kind": memory.kind,
|
|
137
|
+
"content": memory.content,
|
|
138
|
+
"source": memory.source,
|
|
139
|
+
"file_path": memory.file_path,
|
|
140
|
+
"tags": memory.tags,
|
|
141
|
+
"confidence": memory.confidence,
|
|
142
|
+
"weight": round(memory.weight, 4),
|
|
143
|
+
"created_at": memory.created_at.isoformat() if memory.created_at else None,
|
|
144
|
+
}
|
|
145
|
+
for memory in memories
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@app.get("/memory/export")
|
|
150
|
+
def export_memories() -> dict:
|
|
151
|
+
return {"project": daemon.config.project_name, "memories": daemon.store.export_memories()}
|
|
152
|
+
|
|
153
|
+
@app.delete("/memory/{memory_id}")
|
|
154
|
+
def delete_memory(memory_id: str) -> dict:
|
|
155
|
+
if not daemon.store.delete_memory(memory_id):
|
|
156
|
+
raise HTTPException(404, "memory not found")
|
|
157
|
+
return {"deleted": memory_id}
|
|
158
|
+
|
|
159
|
+
@app.get("/graph")
|
|
160
|
+
def graph() -> dict:
|
|
161
|
+
return daemon.store.dependency_graph()
|
|
162
|
+
|
|
163
|
+
@app.post("/ask")
|
|
164
|
+
def ask(body: AskIn):
|
|
165
|
+
"""Natural-language query: returns the most relevant code + decisions.
|
|
166
|
+
|
|
167
|
+
Dot is model-agnostic and fully local — it retrieves and ranks; the
|
|
168
|
+
calling tool (Claude, Copilot, a script) does the generation.
|
|
169
|
+
"""
|
|
170
|
+
context = daemon.assembler.assemble(body.question, current_file=body.current_file)
|
|
171
|
+
if body.fmt == "raw":
|
|
172
|
+
return context_to_dict(context)
|
|
173
|
+
if body.fmt not in FORMATS:
|
|
174
|
+
raise HTTPException(422, f"fmt must be one of {FORMATS}")
|
|
175
|
+
return Response(
|
|
176
|
+
content=format_context(context, body.fmt),
|
|
177
|
+
media_type="text/plain; charset=utf-8",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
@app.post("/sync", status_code=202)
|
|
181
|
+
def sync(body: SyncIn | None = None) -> dict:
|
|
182
|
+
force = bool(body and body.force)
|
|
183
|
+
thread = threading.Thread(
|
|
184
|
+
target=daemon.full_sync, kwargs={"force": force}, daemon=True, name="dot-api-sync"
|
|
185
|
+
)
|
|
186
|
+
thread.start()
|
|
187
|
+
return {"status": "sync started", "force": force}
|
|
188
|
+
|
|
189
|
+
@app.post("/conversations/scan", status_code=200)
|
|
190
|
+
def scan_conversations() -> dict:
|
|
191
|
+
"""Incrementally scan Claude Code transcripts and capture decisions.
|
|
192
|
+
|
|
193
|
+
Unlike :http:post:`/sync` (which fires a heavy re-index in the
|
|
194
|
+
background), this runs synchronously: a conversation scan is cheap
|
|
195
|
+
(incremental byte-offset reads of local JSONL) so the caller gets the
|
|
196
|
+
real counts back immediately. Returns ``{"enabled": False, ...}`` when
|
|
197
|
+
capture is off rather than erroring, so clients can probe safely.
|
|
198
|
+
"""
|
|
199
|
+
return daemon.scan_conversations()
|
|
200
|
+
|
|
201
|
+
@app.post("/hooks/git/commit")
|
|
202
|
+
def git_commit_hook() -> dict:
|
|
203
|
+
captured = daemon.decisions.mine_git(max_count=5)
|
|
204
|
+
return {"decisions_captured": captured}
|
|
205
|
+
|
|
206
|
+
# Serve the built dashboard at /ui when present.
|
|
207
|
+
dist = Path(__file__).resolve().parent.parent / "dashboard" / "dist"
|
|
208
|
+
if dist.is_dir():
|
|
209
|
+
from fastapi.staticfiles import StaticFiles
|
|
210
|
+
|
|
211
|
+
app.mount("/ui", StaticFiles(directory=str(dist), html=True), name="dashboard")
|
|
212
|
+
else:
|
|
213
|
+
|
|
214
|
+
@app.get("/ui")
|
|
215
|
+
def ui_placeholder() -> dict:
|
|
216
|
+
return {
|
|
217
|
+
"message": "dashboard not built — run `npm install && npm run build` in dashboard/",
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return app
|