memos-engine 0.1.0__tar.gz → 0.2.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.
- memos_engine-0.2.0/.github/release.yml +10 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/.github/workflows/release.yml +4 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/.gitignore +3 -0
- memos_engine-0.2.0/AGENTS.md +263 -0
- memos_engine-0.2.0/CHANGELOG.md +19 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/PKG-INFO +15 -4
- {memos_engine-0.1.0 → memos_engine-0.2.0}/README.md +13 -3
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/cli/main.py +2 -0
- memos_engine-0.2.0/memos/indexer/python.py +184 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/pyproject.toml +2 -1
- memos_engine-0.2.0/tests/fixtures/python_mini/src/main.py +5 -0
- memos_engine-0.2.0/tests/fixtures/python_mini/src/types.py +11 -0
- memos_engine-0.2.0/tests/fixtures/python_mini/src/utils.py +9 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_cli_index.py +73 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_cli_query.py +35 -0
- memos_engine-0.2.0/tests/test_python_indexer.py +200 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_resolver.py +66 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/uv.lock +20 -2
- memos_engine-0.1.0/AGENTS.md +0 -150
- memos_engine-0.1.0/CHANGELOG.md +0 -11
- {memos_engine-0.1.0 → memos_engine-0.2.0}/.github/workflows/ci.yml +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/.python-version +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/AGENTS_EXAMPLE.md +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/LICENSE +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/api/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/api/main.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/api/schemas.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/cli/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/db.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/migrations/0001_init.sql +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/migrations/0002_vec.sql +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/migrations/0003_prompt_version.sql +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/migrations/0004_memory_fts.sql +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/migrations/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/models.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/core/schema.sql +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/indexer/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/indexer/base.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/indexer/diff.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/indexer/go.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/indexer/typescript.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/mcp/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/mcp/server.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/query/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/query/core.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/search/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/search/base.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/search/embeddings.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/memos/search/sqlite_vec_store.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/conftest.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/go_mini/__init__.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/go_mini/src/main.go +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/go_mini/src/types.go +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/go_mini/src/utils.go +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/import_cycle/src/a.ts +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/import_cycle/src/b.ts +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/typescript_mini/src/index.ts +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/typescript_mini/src/types.ts +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/fixtures/typescript_mini/src/utils.ts +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_api.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_crud.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_dependency_graph.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_go_indexer.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_hygiene.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_impact.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_llm_summary.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_mcp.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_memory_hygiene.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_migrations.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_query.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_reindex.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_schema.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_semantic_search.py +0 -0
- {memos_engine-0.1.0 → memos_engine-0.2.0}/tests/test_typescript_indexer.py +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
> Этот репозиторий проиндексирован `memos` — используй MCP-инструменты
|
|
4
|
+
> memos для навигации по коду. Это быстрее grep/cat и точнее.
|
|
5
|
+
|
|
6
|
+
## Code navigation policy (memos)
|
|
7
|
+
|
|
8
|
+
Этот проект проиндексирован `memos` — структурным индексом кода (symbols,
|
|
9
|
+
call edges, imports) с episodic memory. Инструмент доступен через MCP-тулы.
|
|
10
|
+
**Прежде чем читать файл целиком или делать grep по коду — используй memos.**
|
|
11
|
+
Это быстрее, точнее и не тратит контекст на нерелевантные строки.
|
|
12
|
+
|
|
13
|
+
### Обязательный порядок действий
|
|
14
|
+
|
|
15
|
+
1. **В начале сессии**, если ещё не открыт проект — вызови `open_project`
|
|
16
|
+
с абсолютным путём к корню репозитория. Это одноразовое действие на
|
|
17
|
+
сессию, если проект уже открыт — не вызывай повторно.
|
|
18
|
+
|
|
19
|
+
2. **Чтобы найти определение функции/класса/переменной** — используй
|
|
20
|
+
`find_symbol_tool(name, kind=None, file_path=None)`.
|
|
21
|
+
Не используй `grep`/`cat` для этого — memos даёт точный результат
|
|
22
|
+
(файл, строки, сигнатура, exported) без ложных совпадений по имени
|
|
23
|
+
в комментариях/строках.
|
|
24
|
+
|
|
25
|
+
3. **Чтобы понять, кто вызывает функцию или что вызывает она** — используй
|
|
26
|
+
`find_calls_tool(symbol_name, direction="callers"|"callees")`.
|
|
27
|
+
Это заменяет ручной grep по имени функции во всём репозитории.
|
|
28
|
+
|
|
29
|
+
4. **Перед тем как редактировать функцию** — вызови `get_context_tool(symbol_id)`.
|
|
30
|
+
Он возвращает символ + всех callers + callees + episodic memory заметки +
|
|
31
|
+
кэшированный LLM-summary (если есть). **Это заменяет повторное чтение
|
|
32
|
+
всего файла и всех зависимых файлов** — не открывай их вручную, если
|
|
33
|
+
`get_context_tool` уже дал нужную картину.
|
|
34
|
+
|
|
35
|
+
5. **Чтобы посмотреть всё содержимое файла структурно** (какие символы,
|
|
36
|
+
вызовы, импорты) — используй `get_module_tool(path)` вместо чтения
|
|
37
|
+
файла целиком, если тебе не нужен именно raw-текст (форматирование,
|
|
38
|
+
комментарии, точный синтаксис для правки).
|
|
39
|
+
|
|
40
|
+
6. **Для поиска по смыслу, а не по точному имени** ("где у нас логика
|
|
41
|
+
авторизации", "функция, которая парсит даты") — используй
|
|
42
|
+
`semantic_search_tool(query, top_k)`.
|
|
43
|
+
|
|
44
|
+
7. **После значимого решения, вывода или "гочи"**, которое стоит помнить
|
|
45
|
+
в будущих сессиях (архитектурное решение, причина странного кода,
|
|
46
|
+
TODO с контекстом) — сохрани заметку через `memory_add_note(content,
|
|
47
|
+
scope_type, scope_id, kind)`. Не полагайся только на свой ответ в чате —
|
|
48
|
+
он не переживёт сессию, а memory_entries переживают.
|
|
49
|
+
|
|
50
|
+
8. **Перед началом работы с заметками** — вызови `get_memories(scope_type,
|
|
51
|
+
scope_id)`, чтобы узнать, что уже известно про этот файл/символ/проект
|
|
52
|
+
из прошлых сессий, прежде чем исследовать заново.
|
|
53
|
+
|
|
54
|
+
### Когда grep/cat/read_file всё-таки уместны
|
|
55
|
+
|
|
56
|
+
- Файл ещё не проиндексирован (новый, не .ts/.tsx/.go — расширения см. ниже)
|
|
57
|
+
или недавно создан и явно устарел в индексе
|
|
58
|
+
- Нужен именно raw-текст: точное форматирование, конкретные комментарии,
|
|
59
|
+
импорты в исходном виде для правки строки
|
|
60
|
+
- `find_symbol_tool`/`semantic_search_tool` не нашли ничего релевантного —
|
|
61
|
+
тогда grep как fallback, но перепроверь опечатки в имени символа сначала
|
|
62
|
+
|
|
63
|
+
### Что НЕ делать
|
|
64
|
+
|
|
65
|
+
- Не грепай по имени функции, чтобы найти её вызовы — используй
|
|
66
|
+
`find_calls_tool`. Grep даёт ложные совпадения (комментарии, строки,
|
|
67
|
+
похожие имена в других scope) и не различает caller/callee направление.
|
|
68
|
+
- Не читай весь файл, чтобы понять один символ — `get_context_tool` уже
|
|
69
|
+
дал тебе минимально достаточный контекст.
|
|
70
|
+
- Не забывай `open_project`, если сессия началась с чистого MCP-сервера —
|
|
71
|
+
без этого все остальные тулы вернут ошибку "no project open".
|
|
72
|
+
- Не дублируй информацию, которая уже есть в `get_memories` — сначала
|
|
73
|
+
проверь память, потом исследуй заново.
|
|
74
|
+
|
|
75
|
+
### Available tools cheat sheet
|
|
76
|
+
|
|
77
|
+
| Tool | Когда использовать |
|
|
78
|
+
|------|---------------------|
|
|
79
|
+
| `open_project(path)` | В начале сессии, один раз на проект |
|
|
80
|
+
| `find_symbol_tool(name, kind?, file_path?)` | Найти определение символа по имени |
|
|
81
|
+
| `find_calls_tool(symbol_name, direction)` | Callers или callees символа |
|
|
82
|
+
| `get_module_tool(path)` | Всё содержимое файла структурно |
|
|
83
|
+
| `get_context_tool(symbol_id)` | Полный контекст перед правкой функции |
|
|
84
|
+
| `rename_impact_tool(symbol_id)` | Анализ последствий переименования символа |
|
|
85
|
+
| `diff_impact_tool(path)` | Анализ blast radius exported-символов файла |
|
|
86
|
+
| `semantic_search_tool(query, top_k?)` | Поиск по смыслу, не по точному имени |
|
|
87
|
+
| `list_files_tool(path_filter?)` | Список индексированных файлов |
|
|
88
|
+
| `list_symbols_tool(file_path?, kind?)` | Список индексированных символов |
|
|
89
|
+
| `list_projects_tool()` | Инфо и статистика по текущему проекту |
|
|
90
|
+
| `memory_add_note(content, scope_type, scope_id?, kind?)` | Сохранить заметку/решение |
|
|
91
|
+
| `get_memories(scope_type?, scope_id?)` | Прочитать заметки из прошлых сессий |
|
|
92
|
+
| `find_unused_symbols_tool()` | Найти неиспользуемые private функции |
|
|
93
|
+
| `find_dead_imports_tool()` | Найти неразрешившиеся импорты |
|
|
94
|
+
| `get_dependency_graph_tool()` | Граф зависимостей между файлами |
|
|
95
|
+
| `find_import_cycles_tool()` | Найти циклы в импортах |
|
|
96
|
+
| `memory_search_tool(query, top_k?)` | Полнотекстовый поиск по memory entries |
|
|
97
|
+
| `memory_prune_tool(older_than_days?, kind?, apply?)` | Удаление устаревших записей (dry-run по умолчанию) |
|
|
98
|
+
| `reindex_file_tool(path)` | Переиндексировать один файл после правки |
|
|
99
|
+
|
|
100
|
+
Индексируются файлы с расширениями `.ts`, `.tsx`, `.go`. Индекс лежит в
|
|
101
|
+
`.memos/memory.db` в корне проекта и обновляется через `memos index --path .`
|
|
102
|
+
(инкрементально, по content hash — быстро на повторных запусках). Для
|
|
103
|
+
переиндексации одного файла после правки используй `reindex_file_tool(path)`
|
|
104
|
+
— это быстрее и дешевле полного прохода.
|
|
105
|
+
|
|
106
|
+
Если каких-то тулов из таблицы нет в твоём MCP-клиенте — значит memos ещё
|
|
107
|
+
не переиндексирован для этого проекта или сервер не запущен: запусти
|
|
108
|
+
`memos serve-mcp` и настрой подключение согласно README проекта memos.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## Workflow
|
|
112
|
+
|
|
113
|
+
After completing each task: `ruff check .` + `pytest` green, then commit with
|
|
114
|
+
a short descriptive title and all details in the commit body.
|
|
115
|
+
|
|
116
|
+
## Commands
|
|
117
|
+
|
|
118
|
+
| Command | What |
|
|
119
|
+
|---------|------|
|
|
120
|
+
| `uv run pytest` | all tests |
|
|
121
|
+
| `uv run pytest -v` | verbose |
|
|
122
|
+
| `uv run pytest tests/test_crud.py::test_symbol_crud` | single test |
|
|
123
|
+
| `uv run memos index --path . --full` | index project at path |
|
|
124
|
+
| `uv run memos query symbol <name> [--kind KIND]` | find symbols by name |
|
|
125
|
+
| `uv run memos query calls <name> [--direction callers\|callees]` | find callers/callees |
|
|
126
|
+
| `uv run memos query module <path>` | show everything for a file |
|
|
127
|
+
| `uv run memos serve --path . --port 8000` | start FastAPI server |
|
|
128
|
+
| `uv run memos serve-mcp --path .` | start MCP server (stdio) |
|
|
129
|
+
| `uv run pytest tests/test_mcp.py` | MCP server tests |
|
|
130
|
+
| `uv run pytest tests/test_llm_summary.py` | LLM summary tests |
|
|
131
|
+
| `uv run pytest tests/test_impact.py` | Impact analysis tests |
|
|
132
|
+
| `uv run pytest tests/test_hygiene.py` | Dead code / hygiene tests |
|
|
133
|
+
| `uv run pytest tests/test_dependency_graph.py` | Dependency graph tests |
|
|
134
|
+
| `uv run pytest tests/test_memory_hygiene.py` | Memory search & prune tests |
|
|
135
|
+
| `uv run pytest tests/test_reindex.py` | Reindex tool tests |
|
|
136
|
+
| `curl http://localhost:8000/symbols/{id}/context` | API: get context for symbol |
|
|
137
|
+
| `uv add <pkg>` | add dependency |
|
|
138
|
+
| `uv add --dev <pkg>` | add dev dependency |
|
|
139
|
+
| `uv sync` | reinstall after pyproject.toml changes |
|
|
140
|
+
|
|
141
|
+
## Layout
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
memos/
|
|
145
|
+
core/
|
|
146
|
+
db.py # get_connection(WAL+FK), run_migrations, CRUD (all return pydantic models), resolve_call_edges
|
|
147
|
+
models.py # Project, File, Symbol, CallEdge, Import, MemoryEntry
|
|
148
|
+
schema.sql # human-readable DDL copy
|
|
149
|
+
migrations/
|
|
150
|
+
0001_init.sql # authoritative DDL
|
|
151
|
+
0002_vec.sql
|
|
152
|
+
0003_prompt_version.sql
|
|
153
|
+
0004_memory_fts.sql
|
|
154
|
+
indexer/
|
|
155
|
+
base.py # LanguageIndexer ABC + ParsedSymbol/Call/Import/Result dataclasses
|
|
156
|
+
typescript.py # TypeScriptIndexer (tree-sitter, handles .ts + .tsx)
|
|
157
|
+
go.py # GoIndexer (tree-sitter, export by name case)
|
|
158
|
+
python.py # PythonIndexer (tree-sitter, export by _ prefix) # NEW
|
|
159
|
+
diff.py # compute_file_hash, should_reindex
|
|
160
|
+
query/
|
|
161
|
+
core.py # find_symbol, find_calls, get_module, find_calls_by_id, semantic_search,
|
|
162
|
+
# list_files, list_symbols, get_or_generate_summary, get_context,
|
|
163
|
+
# get_rename_impact, get_diff_impact, find_unused_symbols, find_dead_imports,
|
|
164
|
+
# get_dependency_graph, find_import_cycles, memory_search, memory_prune
|
|
165
|
+
# — pure query layer over db
|
|
166
|
+
api/
|
|
167
|
+
main.py # FastAPI app — thin adapter over query/core.py
|
|
168
|
+
schemas.py # pydantic response models for API
|
|
169
|
+
mcp/
|
|
170
|
+
server.py # MCP server (FastMCP, stdio) — thin adapter over query/core.py
|
|
171
|
+
search/
|
|
172
|
+
base.py # EmbeddingModel + VectorStore ABCs
|
|
173
|
+
embeddings.py # FastEmbedEmbedding (all-MiniLM-L6-v2, 384-dim)
|
|
174
|
+
sqlite_vec_store.py # SqliteVecStore (sqlite-vec vec0 table)
|
|
175
|
+
cli/
|
|
176
|
+
main.py # argparse: "memos index [--path .] [--full] [--no-embed]"
|
|
177
|
+
# "memos query (symbol|calls|module)"
|
|
178
|
+
# "memos serve [--path] [--port]"
|
|
179
|
+
# "memos serve-mcp [--path]"
|
|
180
|
+
tests/
|
|
181
|
+
conftest.py # fixture: in-memory sqlite with migrations applied
|
|
182
|
+
test_schema.py # table existence checks
|
|
183
|
+
test_crud.py # CRUD + cascade delete
|
|
184
|
+
test_migrations.py# idempotent re-run
|
|
185
|
+
test_typescript_indexer.py # 18 unit tests on TS parsing
|
|
186
|
+
test_go_indexer.py # 18 unit tests on Go parsing
|
|
187
|
+
test_python_indexer.py # 18 unit tests on Python parsing
|
|
188
|
+
test_cli_index.py # 11 integration tests: index flow (TS + Go + Python)
|
|
189
|
+
test_resolver.py # 8 unit tests on call-edge resolution (+ Python)
|
|
190
|
+
test_query.py # 12 unit tests on query/core.py
|
|
191
|
+
test_cli_query.py # 10 integration tests: query flow (TS + Go + Python)
|
|
192
|
+
test_api.py # 7 integration tests: FastAPI endpoints
|
|
193
|
+
test_mcp.py # 15 tests: MCP server tools
|
|
194
|
+
test_semantic_search.py # 8 tests: VecStore CRUD + semantic_search query
|
|
195
|
+
test_llm_summary.py # 11 tests: get_or_generate_summary, get_context, source_hash
|
|
196
|
+
test_impact.py # 8 tests: rename_impact, diff_impact
|
|
197
|
+
test_hygiene.py # 8 tests: unused symbols, dead imports
|
|
198
|
+
test_dependency_graph.py # 8 tests: dependency graph + cycle detection
|
|
199
|
+
test_memory_hygiene.py # 9 tests: memory search + prune
|
|
200
|
+
test_reindex.py # 3 tests: reindex_file_tool
|
|
201
|
+
test_migrations.py # 2 tests: idempotent re-run
|
|
202
|
+
fixtures/
|
|
203
|
+
typescript_mini/src/ # 3 .ts files for integration testing
|
|
204
|
+
go_mini/src/ # 3 .go files for integration testing
|
|
205
|
+
python_mini/src/ # 3 .py files for integration testing # NEW
|
|
206
|
+
import_cycle/src/ # 2 .ts files with circular import
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Dependencies
|
|
210
|
+
|
|
211
|
+
- **pydantic** — all CRUD returns models, not raw rows
|
|
212
|
+
- **tree-sitter + tree-sitter-typescript + tree-sitter-go + tree-sitter-python** — AST parsing (.ts, .tsx, .go, .py)
|
|
213
|
+
- **stdlib sqlite3** — connection mgmt, WAL journal, FK enforcement
|
|
214
|
+
- **fastapi + uvicorn** — HTTP API
|
|
215
|
+
- **mcp[cli]** — MCP server (FastMCP, stdio transport)
|
|
216
|
+
- **fastembed** — ONNX embeddings (all-MiniLM-L6-v2, 384-dim)
|
|
217
|
+
- **sqlite-vec** — vector search extension for sqlite
|
|
218
|
+
- **rich** — CLI progress bars
|
|
219
|
+
- **pytest** (dev)
|
|
220
|
+
- **httpx** (dev, for TestClient)
|
|
221
|
+
- **pytest-anyio** (dev, for async MCP tests)
|
|
222
|
+
- **hatchling** (build)
|
|
223
|
+
|
|
224
|
+
## Architecture conventions
|
|
225
|
+
|
|
226
|
+
- `indexer/typescript.py` produces plain dataclasses (`ParseResult`), not pydantic models — conversion happens in CLI when calling CRUD
|
|
227
|
+
- `cli/main.py` is a thin argparse wrapper over `indexer/` + `core/db.py`; no business logic in CLI
|
|
228
|
+
- `query/core.py` has no knowledge of CLI, API, or MCP — all three are thin adapters over it
|
|
229
|
+
- `mcp/server.py` is a thin FastMCP wrapper over `query/core.py`; same pattern as `api/main.py`
|
|
230
|
+
- All MCP tools return JSON strings (parsable by the LLM), never raw text
|
|
231
|
+
- `index_file()` in cli/main.py is the unit of change: delete old → parse → insert new
|
|
232
|
+
- `memos index` stores DB at `{project_root}/.memos/memory.db`
|
|
233
|
+
- call_edges are inserted with `callee_symbol_id=NULL` (first pass); resolution is Task 5
|
|
234
|
+
- `callee_symbol_id` and `resolved_file_id` use `ON DELETE SET NULL` — when a callee symbol is deleted (e.g. during reindex), the FK is automatically nulled, then re-resolved in the second pass
|
|
235
|
+
- `export` keyword detection (TS): `export_statement` wraps declarations; the walker passes `exported=True` to children
|
|
236
|
+
- Go export: determined by `name[0].isupper()` — no `export_statement`
|
|
237
|
+
- Python export: determined by `name[0] != '_'` — convention-based, like Go
|
|
238
|
+
- Go methods: `method_declaration` nodes are separate from type declarations; receiver type is extracted from `receiver` field
|
|
239
|
+
- Go imports: both single `import "x"` and grouped `import ( "x" "y" )` forms are handled via `import_spec` / `import_spec_list`
|
|
240
|
+
- Python imports: `import_statement` (single/as) and `import_from_statement` (from/relative) — no resolver yet
|
|
241
|
+
|
|
242
|
+
## What does not exist yet
|
|
243
|
+
|
|
244
|
+
- Type checking, codegen — none configured
|
|
245
|
+
|
|
246
|
+
## Execution order (from spec §6)
|
|
247
|
+
|
|
248
|
+
1. ✅ `core/db.py` + `models.py` + `schema.sql` + migrations + tests
|
|
249
|
+
2. ✅ `indexer/base.py` + `indexer/typescript.py` + CLI `index`
|
|
250
|
+
3. ✅ `indexer/go.py`
|
|
251
|
+
4. ✅ `query/core.py` (find_symbol, find_calls, get_module) + CLI `query`
|
|
252
|
+
5. ✅ Call-edge resolution (second pass) + cross-file tests
|
|
253
|
+
6. ✅ FastAPI wrapper
|
|
254
|
+
7. ✅ Semantic search (sqlite-vec + sentence-transformers)
|
|
255
|
+
8. ✅ MCP server
|
|
256
|
+
9. ✅ `memory_entries` write-path via `memory_add_note`
|
|
257
|
+
10. ✅ LLM enrichment: `get_or_generate_summary()` with content_hash check, `get_context()` composite function
|
|
258
|
+
11. ✅ Section 0: Technical debt (dedup, batch resolve, SAVEPOINT, threading.Lock)
|
|
259
|
+
12. ✅ Section 1: Impact analysis (rename_impact, diff_impact, MCP, API, tests)
|
|
260
|
+
13. ✅ Section 2: Dead code / hygiene (unused symbols, dead imports, MCP, API, tests)
|
|
261
|
+
14. ✅ Section 3: Dependency graph (graph + cycle detection, MCP, API, tests)
|
|
262
|
+
15. ✅ Section 4: Memory search & hygiene (FTS5 search, prune, MCP, API, tests)
|
|
263
|
+
16. ✅ Section 5: Reindex file tool (per-file reindex via MCP, tests)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 — 2026-07-21
|
|
4
|
+
|
|
5
|
+
- Python language support (indexer, tests, fixtures)
|
|
6
|
+
- Self-indexed: memos-engine uses memos for its own development
|
|
7
|
+
- PyPI package `memos-engine` with MIT license
|
|
8
|
+
- CI: GitHub Actions release workflow (tag → build → publish)
|
|
9
|
+
- GitHub Release automation with changelog categorization
|
|
10
|
+
|
|
11
|
+
## 0.1.0 — 2026-07-21
|
|
12
|
+
|
|
13
|
+
- Initial release
|
|
14
|
+
- TypeScript/Go structural index via tree-sitter
|
|
15
|
+
- MCP server with 20 tools
|
|
16
|
+
- FastAPI with 15 endpoints
|
|
17
|
+
- Semantic search (fastembed + sqlite-vec)
|
|
18
|
+
- Episodic memory (FTS5)
|
|
19
|
+
- Impact analysis, dead code, dependency graph, memory hygiene
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memos-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Memory OS for AI coding agents — structural code index with episodic memory
|
|
5
5
|
Project-URL: Repository, https://github.com/TAskMAster339/memos-engine
|
|
6
6
|
Author: TAskMAster339
|
|
@@ -18,6 +18,7 @@ Requires-Dist: pydantic
|
|
|
18
18
|
Requires-Dist: rich>=15.0.0
|
|
19
19
|
Requires-Dist: sqlite-vec>=0.1.0
|
|
20
20
|
Requires-Dist: tree-sitter-go>=0.25.0
|
|
21
|
+
Requires-Dist: tree-sitter-python>=0.23.0
|
|
21
22
|
Requires-Dist: tree-sitter-typescript>=0.23.2
|
|
22
23
|
Requires-Dist: tree-sitter>=0.26.0
|
|
23
24
|
Requires-Dist: uvicorn[standard]>=0.34.0
|
|
@@ -27,10 +28,20 @@ Description-Content-Type: text/markdown
|
|
|
27
28
|
|
|
28
29
|
[GitHub: TAskMAster339/memos-engine](https://github.com/TAskMAster339/memos-engine.git)
|
|
29
30
|
|
|
31
|
+
<div align="center">
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+

|
|
35
|
+

|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
|
|
30
40
|
`memos` builds a **structural index** (symbols, call edges, imports) of a
|
|
31
|
-
TypeScript/TSX / Go codebase using tree-sitter and stores it in
|
|
32
|
-
first layer of a larger *Memory OS* for AI coding agents
|
|
33
|
-
grepping text, agents query **structure** (definitions,
|
|
41
|
+
TypeScript / TSX / Go / Python codebase using tree-sitter and stores it in
|
|
42
|
+
SQLite. It is the first layer of a larger *Memory OS* for AI coding agents
|
|
43
|
+
— instead of grepping text, agents query **structure** (definitions,
|
|
44
|
+
callers, callees).
|
|
34
45
|
|
|
35
46
|
## Integrating memos into your project
|
|
36
47
|
|
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[GitHub: TAskMAster339/memos-engine](https://github.com/TAskMAster339/memos-engine.git)
|
|
4
4
|
|
|
5
|
+
<div align="center">
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
5
14
|
`memos` builds a **structural index** (symbols, call edges, imports) of a
|
|
6
|
-
TypeScript/TSX / Go codebase using tree-sitter and stores it in
|
|
7
|
-
first layer of a larger *Memory OS* for AI coding agents
|
|
8
|
-
grepping text, agents query **structure** (definitions,
|
|
15
|
+
TypeScript / TSX / Go / Python codebase using tree-sitter and stores it in
|
|
16
|
+
SQLite. It is the first layer of a larger *Memory OS* for AI coding agents
|
|
17
|
+
— instead of grepping text, agents query **structure** (definitions,
|
|
18
|
+
callers, callees).
|
|
9
19
|
|
|
10
20
|
## Integrating memos into your project
|
|
11
21
|
|
|
@@ -32,6 +32,7 @@ from memos.core.db import (
|
|
|
32
32
|
from memos.core.models import CallEdge, File, Import, Project, Symbol
|
|
33
33
|
from memos.indexer.diff import compute_file_hash, should_reindex
|
|
34
34
|
from memos.indexer.go import GoIndexer
|
|
35
|
+
from memos.indexer.python import PythonIndexer
|
|
35
36
|
from memos.indexer.typescript import TypeScriptIndexer
|
|
36
37
|
from memos.query.core import (
|
|
37
38
|
add_memory_entry,
|
|
@@ -46,6 +47,7 @@ EXTENSION_INDEXERS = {
|
|
|
46
47
|
".ts": TypeScriptIndexer(tsx=False),
|
|
47
48
|
".tsx": TypeScriptIndexer(tsx=True),
|
|
48
49
|
".go": GoIndexer(),
|
|
50
|
+
".py": PythonIndexer(),
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
SKIP_DIRS = {".git", ".memos", "node_modules", "__pycache__", ".venv", "target"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
import tree_sitter_python
|
|
4
|
+
from tree_sitter import Language, Parser
|
|
5
|
+
|
|
6
|
+
from memos.indexer.base import (
|
|
7
|
+
LanguageIndexer,
|
|
8
|
+
ParsedCall,
|
|
9
|
+
ParsedImport,
|
|
10
|
+
ParsedSymbol,
|
|
11
|
+
ParseResult,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_PY_LANG = Language(tree_sitter_python.language())
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PythonIndexer(LanguageIndexer):
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.parser = Parser()
|
|
20
|
+
self.parser.language = _PY_LANG
|
|
21
|
+
|
|
22
|
+
def language(self) -> str:
|
|
23
|
+
return "python"
|
|
24
|
+
|
|
25
|
+
def parse(self, source: str, file_path: str) -> ParseResult:
|
|
26
|
+
source_bytes = bytes(source, "utf-8")
|
|
27
|
+
tree = self.parser.parse(source_bytes)
|
|
28
|
+
result = ParseResult()
|
|
29
|
+
self._walk(tree.root_node, source_bytes, result)
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
def _walk(self, node, source, result, scope=None, class_name=None): # noqa: C901, PLR0911, PLR0912
|
|
33
|
+
t = node.type
|
|
34
|
+
|
|
35
|
+
if t == "function_definition":
|
|
36
|
+
name_node = node.child_by_field_name("name")
|
|
37
|
+
name = self._node_text(name_node, source) if name_node else None
|
|
38
|
+
if name:
|
|
39
|
+
kind = "method" if class_name else "function"
|
|
40
|
+
self._add_symbol(
|
|
41
|
+
node, source, result, kind, name, parent_name=class_name,
|
|
42
|
+
)
|
|
43
|
+
method_scope = f"{class_name}.{name}" if class_name else name
|
|
44
|
+
for child in node.children:
|
|
45
|
+
self._walk(
|
|
46
|
+
child, source, result,
|
|
47
|
+
scope=method_scope, class_name=class_name,
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if t == "class_definition":
|
|
52
|
+
name_node = node.child_by_field_name("name")
|
|
53
|
+
name = self._node_text(name_node, source) if name_node else None
|
|
54
|
+
if name:
|
|
55
|
+
self._add_symbol(node, source, result, "class", name)
|
|
56
|
+
for child in node.children:
|
|
57
|
+
self._walk(child, source, result, scope=name, class_name=name)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
if t == "decorated_definition":
|
|
61
|
+
for child in node.children:
|
|
62
|
+
self._walk(child, source, result, scope=scope, class_name=class_name)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
if t == "expression_statement":
|
|
66
|
+
if scope is None and class_name is None:
|
|
67
|
+
for child in node.children:
|
|
68
|
+
if child.type == "assignment":
|
|
69
|
+
self._handle_assignment(child, source, result)
|
|
70
|
+
for child in node.children:
|
|
71
|
+
self._walk(child, source, result, scope=scope, class_name=class_name)
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if t == "call":
|
|
75
|
+
self._add_call(node, source, result, scope)
|
|
76
|
+
for child in node.children:
|
|
77
|
+
self._walk(child, source, result, scope=scope, class_name=class_name)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
if t == "import_statement":
|
|
81
|
+
self._add_import(node, source, result)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
if t == "import_from_statement":
|
|
85
|
+
self._add_from_import(node, source, result)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
for child in node.children:
|
|
89
|
+
self._walk(child, source, result, scope=scope, class_name=class_name)
|
|
90
|
+
|
|
91
|
+
def _handle_assignment(self, node, source, result):
|
|
92
|
+
left = node.child_by_field_name("left")
|
|
93
|
+
if left and left.type == "identifier":
|
|
94
|
+
name = self._node_text(left, source)
|
|
95
|
+
kind = "const" if name.isupper() else "variable"
|
|
96
|
+
text = source[node.start_byte : node.end_byte].decode("utf-8")
|
|
97
|
+
content_hash = hashlib.sha256(text.encode()).hexdigest()
|
|
98
|
+
exported = not name.startswith("_")
|
|
99
|
+
result.symbols.append(
|
|
100
|
+
ParsedSymbol(
|
|
101
|
+
name=name,
|
|
102
|
+
kind=kind,
|
|
103
|
+
signature=text,
|
|
104
|
+
start_line=node.start_point[0] + 1,
|
|
105
|
+
end_line=node.end_point[0] + 1,
|
|
106
|
+
exported=exported,
|
|
107
|
+
content_hash=content_hash,
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _add_symbol(self, node, source, result, kind, name, parent_name=None): # noqa: PLR0913
|
|
112
|
+
text = source[node.start_byte : node.end_byte].decode("utf-8")
|
|
113
|
+
content_hash = hashlib.sha256(text.encode()).hexdigest()
|
|
114
|
+
exported = not name.startswith("_")
|
|
115
|
+
|
|
116
|
+
if kind in ("function", "method"):
|
|
117
|
+
sig = self._extract_signature(node, source, text)
|
|
118
|
+
else:
|
|
119
|
+
sig = text.split("{", 1)[0].strip() if "{" in text else text
|
|
120
|
+
|
|
121
|
+
result.symbols.append(
|
|
122
|
+
ParsedSymbol(
|
|
123
|
+
name=name,
|
|
124
|
+
kind=kind,
|
|
125
|
+
signature=sig,
|
|
126
|
+
start_line=node.start_point[0] + 1,
|
|
127
|
+
end_line=node.end_point[0] + 1,
|
|
128
|
+
exported=exported,
|
|
129
|
+
content_hash=content_hash,
|
|
130
|
+
parent_name=parent_name,
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def _extract_signature(self, node, source, text):
|
|
135
|
+
params = node.child_by_field_name("parameters")
|
|
136
|
+
if params is None:
|
|
137
|
+
return None
|
|
138
|
+
params_end = params.end_byte - node.start_byte
|
|
139
|
+
return_type = node.child_by_field_name("return_type")
|
|
140
|
+
if return_type:
|
|
141
|
+
params_end = return_type.end_byte - node.start_byte
|
|
142
|
+
colon_idx = text.find(":", params_end)
|
|
143
|
+
if colon_idx != -1:
|
|
144
|
+
return text[: colon_idx + 1].strip()
|
|
145
|
+
return text
|
|
146
|
+
|
|
147
|
+
def _add_call(self, node, source, result, caller_name):
|
|
148
|
+
func_node = node.child_by_field_name("function")
|
|
149
|
+
if func_node is None:
|
|
150
|
+
func_node = node.children[0] if node.children else None
|
|
151
|
+
if func_node is None:
|
|
152
|
+
return
|
|
153
|
+
if func_node.type in ("identifier", "attribute"):
|
|
154
|
+
name = self._node_text(func_node, source)
|
|
155
|
+
else:
|
|
156
|
+
return
|
|
157
|
+
result.calls.append(
|
|
158
|
+
ParsedCall(
|
|
159
|
+
caller_name=caller_name,
|
|
160
|
+
callee_name=name,
|
|
161
|
+
line=node.start_point[0] + 1,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _add_import(self, node, source, result):
|
|
166
|
+
for child in node.children:
|
|
167
|
+
if child.type == "dotted_name":
|
|
168
|
+
path = self._node_text(child, source)
|
|
169
|
+
result.imports.append(ParsedImport(imported_path=path))
|
|
170
|
+
elif child.type == "aliased_import":
|
|
171
|
+
name_node = child.child_by_field_name("name")
|
|
172
|
+
if name_node:
|
|
173
|
+
path = self._node_text(name_node, source)
|
|
174
|
+
result.imports.append(ParsedImport(imported_path=path))
|
|
175
|
+
|
|
176
|
+
def _add_from_import(self, node, source, result):
|
|
177
|
+
module_node = node.child_by_field_name("module_name")
|
|
178
|
+
if module_node:
|
|
179
|
+
path = self._node_text(module_node, source)
|
|
180
|
+
result.imports.append(ParsedImport(imported_path=path))
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def _node_text(node, source):
|
|
184
|
+
return source[node.start_byte : node.end_byte].decode("utf-8")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "memos-engine"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Memory OS for AI coding agents — structural code index with episodic memory"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -23,6 +23,7 @@ dependencies = [
|
|
|
23
23
|
"sqlite-vec>=0.1.0",
|
|
24
24
|
"rich>=15.0.0",
|
|
25
25
|
"mcp[cli]>=1.28.1",
|
|
26
|
+
"tree-sitter-python>=0.23.0",
|
|
26
27
|
]
|
|
27
28
|
|
|
28
29
|
[project.urls]
|