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.
Files changed (51) hide show
  1. dot_context-0.1.0a1/LICENSE +21 -0
  2. dot_context-0.1.0a1/PKG-INFO +152 -0
  3. dot_context-0.1.0a1/README.md +110 -0
  4. dot_context-0.1.0a1/dot/__init__.py +10 -0
  5. dot_context-0.1.0a1/dot/api.py +220 -0
  6. dot_context-0.1.0a1/dot/cli.py +636 -0
  7. dot_context-0.1.0a1/dot/config.py +126 -0
  8. dot_context-0.1.0a1/dot/context/__init__.py +11 -0
  9. dot_context-0.1.0a1/dot/context/assembler.py +144 -0
  10. dot_context-0.1.0a1/dot/context/formatter.py +134 -0
  11. dot_context-0.1.0a1/dot/context/ranker.py +122 -0
  12. dot_context-0.1.0a1/dot/conversations/__init__.py +37 -0
  13. dot_context-0.1.0a1/dot/conversations/claude_code.py +346 -0
  14. dot_context-0.1.0a1/dot/conversations/ingest.py +206 -0
  15. dot_context-0.1.0a1/dot/conversations/source.py +93 -0
  16. dot_context-0.1.0a1/dot/conversations/watcher.py +141 -0
  17. dot_context-0.1.0a1/dot/daemon.py +396 -0
  18. dot_context-0.1.0a1/dot/doctor.py +228 -0
  19. dot_context-0.1.0a1/dot/indexer/__init__.py +12 -0
  20. dot_context-0.1.0a1/dot/indexer/chunker.py +235 -0
  21. dot_context-0.1.0a1/dot/indexer/embedder.py +120 -0
  22. dot_context-0.1.0a1/dot/indexer/parser.py +324 -0
  23. dot_context-0.1.0a1/dot/indexer/watcher.py +151 -0
  24. dot_context-0.1.0a1/dot/integrations/__init__.py +1 -0
  25. dot_context-0.1.0a1/dot/integrations/claude.py +106 -0
  26. dot_context-0.1.0a1/dot/integrations/copilot.py +66 -0
  27. dot_context-0.1.0a1/dot/integrations/git.py +148 -0
  28. dot_context-0.1.0a1/dot/integrations/mcp.py +217 -0
  29. dot_context-0.1.0a1/dot/memory/__init__.py +6 -0
  30. dot_context-0.1.0a1/dot/memory/decay.py +50 -0
  31. dot_context-0.1.0a1/dot/memory/decisions.py +169 -0
  32. dot_context-0.1.0a1/dot/memory/shared.py +162 -0
  33. dot_context-0.1.0a1/dot/memory/store.py +661 -0
  34. dot_context-0.1.0a1/dot_context.egg-info/PKG-INFO +152 -0
  35. dot_context-0.1.0a1/dot_context.egg-info/SOURCES.txt +49 -0
  36. dot_context-0.1.0a1/dot_context.egg-info/dependency_links.txt +1 -0
  37. dot_context-0.1.0a1/dot_context.egg-info/entry_points.txt +2 -0
  38. dot_context-0.1.0a1/dot_context.egg-info/requires.txt +27 -0
  39. dot_context-0.1.0a1/dot_context.egg-info/top_level.txt +2 -0
  40. dot_context-0.1.0a1/pyproject.toml +78 -0
  41. dot_context-0.1.0a1/setup.cfg +4 -0
  42. dot_context-0.1.0a1/tests/test_api.py +90 -0
  43. dot_context-0.1.0a1/tests/test_cli_init.py +62 -0
  44. dot_context-0.1.0a1/tests/test_context.py +58 -0
  45. dot_context-0.1.0a1/tests/test_conversation_capture_integration.py +199 -0
  46. dot_context-0.1.0a1/tests/test_conversations.py +444 -0
  47. dot_context-0.1.0a1/tests/test_doctor.py +346 -0
  48. dot_context-0.1.0a1/tests/test_indexer.py +73 -0
  49. dot_context-0.1.0a1/tests/test_mcp.py +76 -0
  50. dot_context-0.1.0a1/tests/test_memory.py +87 -0
  51. 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