Создано: 2026-07-27. Статус: активный. Источник: 4 раунда протокольного аудита (graph.py, cypher-стек, indexing-стек, write_tools, search/engine, error_handler, remote_embedder, ci.yml, server_tools).
- Файлы:
src/core/search/cypher_parser.py:427,src/core/search/cypher_sql.py:84 - Статус: ✅ FIXED (parser + sql.py)
- Детали:
- Parser
_parse_return_itemберётself.advance().valueбез проверки типа токена (L427) cypher_sql.pyL84:f" AS {item.alias}"— f-string подстановка без валидации- PoC:
MATCH (n:Function) RETURN n.name AS "x FROM nodes-- "→ SQL:SELECT n.name AS x FROM nodes-- FROM nodes AS n --обрезает остаток SQL, возвращает ВСЕ строки таблицы nodes вместо отфильтрованные- Контраст:
_property_ref_to_sql(L321-324) валидирует имена свойств черезre.fullmatch, alias — нет
- Parser
- Фикс (parser): ✅ Добавлена проверка
alias_token.type not in (TokenType.IDENTIFIER, TokenType.STRING)→SyntaxError - Фикс (sql.py): ✅ Добавлена валидация alias через
re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", item.alias)перед f-string подстановкой (L84-88)
- Файл:
src/core/search/engine.py:352,736 - Статус: ✅ FIXED
- Детали:
filter_expr = f"layer = '{layer}'" if layer else ""— f-string подстановка пользовательского ввода в LanceDB SQL- PoC:
search_code(query="test", filter_layer="core' OR '1'='1")→layer = 'core' OR '1'='1'→ возвращает чанки из ВСЕХ слоёв - Контраст:
indexer_table.py:_escape_sql_valueкорректно экранирует кавычки и backslash, но здесь не использовался
- Фикс: ✅ Вызов
IndexerTableMixin._escape_sql_value(layer)перед f-string подстановкой в обоих местах (L352-356 и L740-742). ИмпортIndexerTableMixinуже есть на уровне модуля (L18).
- Файлы:
.github/workflows/ci.yml:59,scripts/verify_clean_state.sh:53,55,58,62 - Статус: ✅ FIXED (verify_clean_state.sh --no-clone)
- Детали:
verify_clean_state.shиспользовалvenv/Scripts/pip.exeиvenv/Scripts/python.exe— Windows-формат- CI job
clean-stateзапускается наubuntu-latest→venv/Scripts/pip.exeне существует → exit 127 - Также
bash scripts/verify_clean_state.shв CI вызывает скрипт, который внутри делаетgit cloneвнешнего репозитория — ненадёжно в CI
- Фикс: ✅ Заменены
venv/Scripts/pip.exe→venv/bin/pip,venv/Scripts/python.exe→venv/bin/python - Примечание: ✅ закрыто —
verify_clean_state.shпараметризован ($1= repo URL, default сохранён; флаг--no-cloneпропускает clone и тестирует текущий каталог), CI вызываетbash scripts/verify_clean_state.sh --no-clone "${{ github.repository }}"— тестируется тот SHA, который checkout-нул раннер. Self-clone убран, локальный ручной запуск без аргументов сохраняет прежний полный клон.
- Файл:
src/mcp/tools/codebase_tool.py:148-164 - Статус: ✅ FIXED
- Детали:
- Docstring:
⚠️ ВНИМАНИЕ: Изоляция (sandbox) ОТСУТСТВЕТ. - Код реально использует sandbox:
execute_sandboxedсSANDBOX_MODE_STRICTпо умолчанию (AST validation + module allowlist + subprocess isolation) - Администратор, читающий docstring, мог решить инструмент нельзя включать
- Docstring:
- Фикс: ✅ Docstring синхронизирован: указано что sandbox активен (
execute_sandboxed+SANDBOX_MODE_STRICT), перечислены механизмы изоляции
- Файлы:
src/core/sandbox/executor.py:45-52,350 - Статус: ✅ FIXED (2026-07-31)
- Детали:
- F-1:
ALLOWED_MODULESсодержал import-механику (importlib*,pkgutil,runpy,modulefinder,zipimport) — AST-валидация пропускалаimport importlib, runtime_safe_importблокировал (несоответствие слоёв).importlib.import_module("os")— RCE-вектор при расхождении runtime-слоя. - F-4:
env = os.environ.copy()— ВСЕ секреты родителя (API-ключи, токены) доступны sandbox-скрипту. - F-2 (сопутствующий):
__build_class__отсутствовал вBLOCKED_NAMES; F-3:"sys"в runtime-allowlist при AST-блокировкеimport sys.
- F-1:
- Фикс: import-механика удалена из
ALLOWED_MODULES;__build_class__добавлен вBLOCKED_NAMES;"sys"убран из_USER_ALLOWED;_build_minimal_env()(PATH="", SYSTEMROOT, SYSTEMDRIVE, TEMP/TMP, PYTHONPATH) вместоos.environ.copy(). +6 тестов вtests/test_sandbox.py(40 passed). - Верификация: Qwen review (2026-07-31): F-1/F-2/F-3/F-4 ✅ CONFIRMED, F-5 ❌ REFUTED (mkstemp уже 0600).
- Файл:
src/core/graph.py:918-975 - Статус: ✅ FIXED (2026-07-31 — parent-pointer BFS + пакетная реконструкция)
- Детали: BFS хранит все пути целиком в queue (
List[List[Tuple[int, Optional[int]]]]). Для графа со средней степенью 10 и max_depth=10 — до 10^10 путей в памяти. Стандартный BFS хранит parent-pointer и восстанавливает путь в конце. - Фикс:
shortest_pathпереписан на parent-pointer BFS (parent: Dict[node_id, (parent_id, edge_id)]) — память O(V) вместо O(V×depth);_reconstruct_path— 2 пакетных запроса (nodes IN (...), edges IN (...)) вместо N+1.
- Файл:
src/core/graph.py:1235-1285 - Статус: ✅ FIXED (2026-07-31 — батч-lookup узлов одним запросом)
- Детали: Для каждого ребра 2 SELECT + 1 INSERT. Для батча из 10000 рёбер — 30000 SQL-вызовов вместо одного
INSERT ... SELECT FROM ... JOIN. - Фикс: предзагрузка всех
qualified_name → idоднимSELECT ... WHERE qualified_name IN (...), затем цикл только INSERT.
- Файл:
src/core/indexing/db_manager.py:318-364,indexer.py:449-460,338-380 - Статус: ✅ FIXED (2026-07-31 — read-секции под RLock)
- Детали:
reset_connectionпересоздаётself.tableпод_write_lock, ноsearch()(L304) этот lock не захватывает. Параллельный write во время search →LanceError: table modified during scanили stale reference. - Фикс:
_index_single_file/_parse_file_only/move_chunks_metadata— чтенияself.table.search()обёрнуты вwith self._table_write_lock:(RLock, реентерабельно с reset_connection).
- Файл:
src/core/indexing/db_manager.py:269-316 - Статус: ✅ FIXED (2026-07-31)
- Детали: Закрывает старую БД и открывает новую без захвата
_write_lock. Параллельный write в этот момент → запись в закрытую БД → crash. - Фикс: всё тело
switch_dbобёрнуто вwith self._write_lock:(RLock).
- Файл:
src/core/indexing/indexer.py:540-591 - Статус: ✅ FIXED (2026-07-31 — сериализация под
_table_write_lock) - Детали: Read → Delete → Modify → Add. Если процесс упадёт между Delete и Add, чанки пропадут из индекса без восстановления.
- Фикс: вся последовательность read→delete→add обёрнута в
with self._table_write_lock:— исключено «чтение чужого половинчатого состояния» и stale table reference.
- Файл:
src/core/search/cypher_executor.py:66-68 - Статус: ✅ FIXED (2026-07-31)
- Детали:
_get_conn()возвращает rawsqlite3.Connectionбез захватаself._graph._lock. Параллельныйadd_edgeво время Cypher-запроса →sqlite3.OperationalError: database is locked. - Фикс: execute обёрнут в
with self._graph._lock:.
- Файл:
src/mcp/tools/write_tools.py(все write-операции) - Статус: 🔍 IN PROGRESS
- Детали:
file_pathот пользователя передаётся вPath(file_path).resolve()без проверки, что он внутриproject_path. Можно записать файл за пределами проекта.
- Файл:
src/mcp/tools/write_tools.py:99-126 - Статус: 🔍 IN PROGRESS
- Детали:
new_nameиsymbolне проверяются как идентификаторы. Пользователь может передатьnew_name = "evil(); import os; os.system('rm -rf /')"— текстовая замена черезstr.replaceвставит вредоносный код.
- Файл:
src/mcp/tools/write_tools.py:450-455 - Статус: 🔍 IN PROGRESS
- Детали: LSP может вернуть URI для файла за пределами проекта — запись выполнится без проверки.
- Файл:
src/core/error_handler.py:454 - Статус: 🔍 IN PROGRESS
- Детали: При timeout
elapsed = int((time.perf_counter() - start_time) - 1000)— вычитает 1000 секунд вместо перевода в миллисекунды. Записывает отрицательную latency в метрики, ломая min_ms, avg_ms, P50/P95.
- Файл:
src/core/error_handler.py:593-598 - Статус: ✅ CONFIRMED FIXED (уже было в 5601de39:
except TimeoutError: future.cancel(); raise) - Детали: При
TimeoutErrorfuture остаётся в пуле, поток продолжает выполнение. 4 зависших timeout-а исчерпают_SYNC_POOL(max_workers=4), все последующие sync-вызовы будут ждать вечно.
- Файл:
src/providers/embedder/remote_embedder.py:717-718,731-732 - Статус: 🔍 IN PROGRESS
- Детали: При падении провайдера возвращаются нулевые векторы. Индексация проходит «успешно», но векторный поиск возвращает случайные результаты (cosine similarity с нулевым вектором = 0 для всех). Нет маркера в БД «этот чанк имеет fallback-вектор».
- Файл:
src/core/indexing/db_manager.py:60,indexer.py:76 - Статус: ✅ FIXED (2026-07-31 —
threading.RLockв обоих местах) - Детали:
threading.Lockне реентерабелен. Будущий рефакторинг, добавляющийreset_connectionвнутрьwith self._table_write_lock:, создаст deadlock. - Фикс: заменено на RLock (требуется:
reset_connectionвызывает_warmup_cache, read-секции вложены в write-lock). Проверено:acquire(blocking=False)нигде не используется.
- Файл:
src/core/indexing/db_manager.py:428-490 - Статус: ✅ FIXED (2026-07-31 — raise при таймауте + retry-loop)
- Детали: Между
lock_path.unlink()иos.open(... O_EXCL)другой процесс может создать lock file.os.openпадает сFileExistsError, ловится и превращается вRuntimeError— процесс крашится вместо ожидания. Бонус-находка: после 30с ожидания код молча возвращался БЕЗ захвата лока (писатель без блокировки). - Фикс: таймаут ожидания → явный
RuntimeError; захват после steal — retry-loop 5 попыток с паузой 0.5с.
- Файл:
src/utils/zed_config.py:389-397 - Статус: ✅ FIXED (2026-07-31 — хирургическое удаление через
_set_top_level) - Детали:
remove_zed_settingsпарсил JSONC и сериализовал черезjson.dumps— ВСЕ комментарии пользователя в settings.json терялись. Модуль декларирует контракт «JSONC comments stay byte-for-byte» (docstring L15-16) — нарушение. Docstring функции документировал потерю как tradeoff, но фикс возможен тем же приёмом, что вpatch_zed_settings. - Фикс: удаление через текстовую хирургию
_set_top_levelпо обоим ключам (перезаписываются только управляемые блоки, комментарии вне их — byte-for-byte) + атомарная запись. - Верификация: Claude review (2026-07-31): ✅ CONFIRMED по коду (json.dumps rewrite), severity понижена с P0 до P1 (документировано в docstring).
- Файл:
src/utils/zed_config.py:343 - Статус: ✅ FIXED (2026-07-31 —
_atomic_write_texttemp+os.replace) - Детали:
settings_path.write_text(new_content)на Windows неатомарно (truncate + write). Zed читает settings.json при каждом focus — окно нулевого файла → пользователь получает пустой конфиг. - Фикс: общий хелпер
_atomic_write_text(mkstemp в той же директории + fsync + os.replace), применён вpatch_zed_settingsиremove_zed_settings. - Верификация: Claude review (2026-07-31): ✅ CONFIRMED по коду.
P1-17: CodeParser — tree-sitter Parser не потокобезопасен при параллельном парсинге (Qwen review B-1)
- Файл:
src/core/indexing/parser.py:69,331,671(+index_project_runner.py:200-206) - Статус: ✅ FIXED (2026-07-31 — thread-local parsers + кэш)
- Детали: CodeParser — DI-singleton, а
IndexProjectRunnerпарсит вThreadPoolExecutor(max_workers=4): все воркеры вызываютself.parsers[ext].parse()на ОДНОМ tree-sitter Parser (не reentrant, переиспользует cursor) + общий_cache_*(гоночный повтор дерева другого файла). Ошибка B-1 Qwen оказалась серьёзнее заявленного Medium — реальный race при каждой переиндексации. - Фикс:
_get_parser(ext)— thread-local копии Parser'ов (Language иммутабелен, безопасно шарить); кэш дерева перенесён вthreading.local. Обе точкиparse()(L343, L693) переведены на_get_parser. - Верификация: Qwen review (2026-07-31): ✅ CONFIRMED (severity поднят Medium→High — race в пуле парсинга);
tests/test_assignments.py+test_symbol_index_call_graph.py(105 passed).
- Файл:
src/core/intelligence/layer.py - Статус: ⏳ PARTIAL (2026-07-31 — legacy grandfathered через BLE001 per-file-ignores; ruff enforce для новых файлов)
- Детали: 18 из 22
except Exceptionсpassилиreturn []. Программные ошибки маскируются под «нет данных». - Решено: ruff
selectвключает BLE (P2-17) — новые файлы обязаны быть без BLE001; 664 legacy-нарушения задедклайрены. Полная расчистка — отдельный рефакторинг.
- Файл:
src/core/intelligence/layer.py:783-800 - Статус: ✅ FIXED (2026-07-31 — blake2b вместо hash())
- Детали:
hash()рандомизирован черезPYTHONHASHSEED. Один и тот же код-фрагмент при разных запусках получит разныеincident_id, ломая дедупликацию.
- Файл:
src/core/intelligence/layer.py:340-375 - Статус: ✅ FIXED (2026-07-31 — psutil / ss fallback)
- Детали:
netstat -anoне работает на Linux/macOS._find_pidвсегда возвращает 0 на не-Windows. RAM-метрика неполная.
- Файл:
src/core/intelligence/layer.py:124-130 - Статус: ✅ FIXED (2026-07-31 — единый threading.Lock + async-адаптер)
- Детали:
intel_log_incidentиспользуетasyncio.Lock,intel_auto_collect_adrsиспользуетthreading.Lock. Оба пишут вIntelligenceStore— race condition. - Фикс:
_write_lock = threading.Lock()один на sync+async; async-методы используют_AsyncLockAdapter(asyncio.to_thread acquire);_sync_write_lockудалён.
- Файл:
src/core/search/engine.py:89-101,735-810 - Статус: ✅ FIXED (2026-07-31 — TTL 30с + invalidate_cache())
- Детали: Кэш на 500 записей без TTL. После reindex кэш не инвалидируется — поиск возвращает устаревшие результаты.
- Фикс: записи хранят (timestamp, results); чтение проверяет
time.monotonic() - ts <= 30; истёкшие удаляются; добавлен публичныйinvalidate_cache().
- Файл:
src/core/search/engine.py:303-317 - Статус: ⏳ TECH DEBT (ACCEPTED, 2026-07-31) — закрыт как осознанный выбор владельцем-протоколом: НЕ deadlock, а starvation с
future.result(timeout=30); протокол проекта (§0.1 AGENTS.md) сам запрещает 3+ параллельных MCP-вызовов, поэтому max_workers=2 не достижимо легитимно; persistent loop — риск выше пользы - Детали:
asyncio.runсоздаёт новый event loop в каждом вызове. 3+ параллельных запроса → третий ждёт. - Решено: общий пул уже был (batch 5601de39); полный переход на persistent loop — отдельный рефакторинг (риск выше пользы для текущего использования).
- Верификация (Claude review 2026-07-31, вторая волна): ✅ CONFIRMED по коду (L308-316, max_workers=2, вызовы только
search_with_mode/searchизSearchCodeTool.execute) — starvation с таймаутом 30с, не circular deadlock; воркеры пула не ждут друг друга; дубликат не создавался; закрыт с обоснованием.
- Файл:
src/core/search/engine.py(множество методов) - Статус: ⏳ PARTIAL (2026-07-31 — см. P2-1: BLE001 enforce для новых, legacy grandfathered)
- Детали: Пользователь не отличит «нет результатов» от «поиск упал».
- Файл:
src/mcp/tools/write_tools.py:307-310 - Статус: ✅ FIXED (2026-07-31 —
p.stemвместо rstrip) - Детали:
rstrip(".py")удаляет любую комбинацию символов.,p,yс конца.happy.py→ha. Должно бытьp.stem. - Фикс:
stem = p.stem— статус был устаревшим (🔍 IN PROGRESS), фактически закрыт предыдущей сессией.
- Файл:
src/mcp/tools/write_tools.py— 7 точек записи (было L386-413, факт.:_action_replaceL380,_action_insertL446,_apply_changesL610,_apply_workspace_editL662,_apply_deleteL720,_apply_moveL749/L753/L765) - Статус: ✅ FIXED (2026-07-31 — общий хелпер
_atomic_write) - Детали: Read → modify → write_text без tempfile+rename. Если процесс упадёт — файл в неконсистентном состоянии. Scope расширен по Claude review (КРИТ-1): атомарный паттерн был только в
_apply_changes(mkstemp+os.replace), остальные 6 точек писали напрямую. - Фикс: модульный
_atomic_write(path, content)(mkstemp в той же директории + fsync + os.replace + cleanup при ошибке), применён во всех 7 точках (включая inline-блок_apply_changes— унифицирован). - Верификация: Claude review (2026-07-31): ✅ CONFIRMED (6 неатомарных точек + 1 атомарная); ruff clean; py_compile OK.
- Файл:
src/providers/embedder/remote_embedder.py:644-660 - Статус: ✅ FIXED (2026-07-31 — смена mode теперь даёт явный RuntimeError+fallback, не нулевые векторы; см. P1-12 фикс batch)
- Детали: Блокировка отпускается после чтения
self.mode, но HTTP-запрос выполняется без блокировки. Смена mode посреди batch → часть чанков получит векторы от одного провайдера, часть — нулевые.
- Файл:
src/providers/embedder/remote_embedder.py:63-69,382-520 - Статус: ✅ FIXED (2026-07-31 — отдельный
_sync_clientс timeout=2s для сканера/health; embedding-client остаётся один) - Детали: Один Client с connection pool для llama.cpp, LM Studio, ONNX. Медленный провайдер блокирует остальных.
- Файл:
src/core/error_handler.py:235-250 - Статус: ✅ FIXED (2026-07-31 — collections.deque(maxlen=50) и deque(maxlen=1000) для latencies)
- Детали:
list.pop(0)сдвигает все элементы. Под_TOOL_METRICS_LOCKна каждый вызов инструмента. Bottleneck при высокой нагрузке.
- Файл:
src/core/indexing/indexer_table.py:26-47 - Статус: 🔍 IN PROGRESS
- Детали: Экранирование покрывает 3 вектора, но не двойные кавычки, Unicode-обходы. Зависит от того, что DataFusion не изменит escape-правила.
- Файл:
src/core/indexing/index_status.py:37-90,indexer.py:141-146 - Статус: ✅ FIXED (2026-07-31 — lock передан в IndexStatusReporter, чтения под ним)
- Детали:
total_chunksобновился, аunique_filesещё нет — статус вернёт неконсистентные числа.
- Файл:
src/core/indexing/db_manager.py:192-222 - Статус: ✅ FIXED (2026-07-31 — обёрнуто в
with self._write_lock:, RLock) - Детали: Читает
self.tableбез_write_lock. Если параллельный поток делаетreset_connection, может попасть на закрытую таблицу.
- Файл:
.github/workflows/ci.yml - Статус: ✅ FIXED (2026-07-31 — Python 3.10 в matrix, actions/checkout@v5)
- Детали:
actions/setup-python@v5с python 3.11/3.12, но нет 3.10. Node 20 в matrix может быть deprecated.
- Файл:
ruff.toml - Статус: ✅ FIXED (2026-07-31 — BLE в select + per-file-ignores для 84 legacy-файлов, 664 нарушения)
- Детали: 532 broad
exceptпо проекту не ловятся ruff-ом.
- Файл:
src/core/di_container.py:123-147 - Статус: ✅ FIXED (2026-07-31 —
threading.Lockв resolve) - Детали:
resolveбез блокировки: чтение_instances→ вызов factory → запись. Два параллельных resolve создадут два экземпляра (напр., два PropertyGraph на один WAL). Сейчас латентно: фабрики черезadd_factoryне регистрируются (все —add_singleton),create_service_collectionоднопоточный. - Фикс:
threading.Lockвокруг lookup + factory-вызов; при появлении фабрик с re-entrant resolve потребуется RLock (закомментировано в коде). - Верификация: Claude review (2026-07-31): ✅ CONFIRMED (severity P1→P2 — латентно).
- Файл:
src/utils/zed_config.py:296-301 - Статус: ✅ FIXED (2026-07-31 — space-aware executable detection)
- Детали:
split(maxsplit=1)обрезает путьC:\Users\John Doe\...\python.exeдоC:\Users\John. Реальный кейс: Windows-профили с пробелом.install.pyпередаётf"{PYTHON_EXE} -u -m src.main"без кавычек. - Фикс: ищем самый длинный существующий файл-префикс как executable; если префикс не существует (команда из PATH, напр. "python") — первый токен целиком.
- Верификация: Claude review (2026-07-31): ✅ CONFIRMED по коду.
- Файл:
src/providers/reranker/llama_runner.py:991-996(+startL885,_spawn_rerankerL1072) - Статус: ✅ FIXED (2026-07-31 — закрытие fh в except, 3 места)
- Детали:
stderr=(_fh := open(self._log_path(), 'ab'))— walrus внутри вызова_popen_with_job; при исключенииexceptлогировал, но НЕ закрывал fd. Паттерн в 3 местах (start,_spawn_embedder,_spawn_reranker). - Фикс: локальная
log_fh(initNoneперед try — защита от NameError при отказеopen()), walrus →stderr=(log_fh := ...), присваиваниеself._*_log_fh = log_fhпосле успеха, закрытие в except.stop()/stop_reranker()уже закрывали fh на успешном пути. - Верификация: Claude review (2026-07-31): ✅ CONFIRMED по коду.
- Файл:
src/core/graph.py:55-60 - Статус: ✅ FIXED (2026-07-31 — blake2b)
- Детали:
hash(str(path))(PYTHONHASHSEED) → два MCP-процесса получали РАЗНЫЕ имена мутексов для одной БД → cross-process lock не работал на Windows multi-window. - Фикс:
hashlib.blake2b(str(path).encode(), digest_size=4).hexdigest()(паттерн P1-8/engine cache).
P2-22: graph.delete_node/delete_edge/import_compressed — conn.total_changes cumulative (Qwen review C-5)
- Файл:
src/core/graph.py:520,831,1475 - Статус: ✅ FIXED (2026-07-31 —
cursor.rowcount) - Детали:
total_changes— суммарно с момента открытия соединения:delete_nodeнесуществующего узла возвращал True;node_countв логе импорта был завышен. - Фикс:
cur = conn.execute(...)→return cur.rowcount > 0;node_count = node_cur.rowcount.
- Файл:
src/core/search/scoring.py:74,127 - Статус: ✅ FIXED (2026-07-31 — детерминированный tie-break)
- Детали:
sorted(key=lambda k: scores[k], reverse=True)— tie-break зависел от порядка вставки в dict. - Фикс:
key=lambda k: (-scores[k], k)— вторичный ключfile:chunk_index.
- Файлы:
src/core/search/engine.py:480-521,src/core/search/scoring.py:314-413 - Статус: ✅ FIXED (2026-07-31 — MMR после sort+cut, reorder-only)
- Детали: MMR переупорядочивал + бустил скоры, но последующий
sort(final_score)(после bucket weights и co-change boost) отменял MMR-порядок; искусственный boost ×1.08 искажал выдачу. - Фикс: MMR перенесён ПОСЛЕ sort+cut и перед reranker'ом (при отсутствии reranker'а MMR-порядок доживает до выдачи); убран блок мутации final_score (reorder-only).
- Файл:
src/core/graph.py:842-887 - Статус: ✅ FIXED (2026-07-31 —
max_nodes=1000) - Детали: на hub-файлах (1000+ рёбер) обход O(V+E) без ограничения.
- Фикс: параметр
max_nodes: int = 1000, обход прерывается при достижении (и на входе уровня, и в цикле рёбер).
- Файл:
src/core/graph.py:376 - Статус: ✅ FIXED (2026-07-31 — 64MB)
- Детали: 256MB × 5 проектов (multi-window) = 1.25GB виртуальной памяти; на 32-bit Python — риск.
- Фикс:
PRAGMA mmap_size=67108864(64MB, вровень с page cache).
- Файлы:
src/core/di_container.py:369-374,src/providers/embedder/remote_embedder.py:365 - Статус: ✅ FIXED (2026-07-31 — публичный
set_circuit_breaker) - Детали:
if hasattr(embedder, "_breaker"): embedder._breaker = ...— запись в приватный атрибут извне. - Фикс: метод
RemoteEmbedder.set_circuit_breaker(breaker)+ вызов черезgetattr(embedder, "set_circuit_breaker", None)с fallback.
- Файл:
src/core/graph.py:1395-1420,1453-1500 - Статус: ✅ FIXED (2026-07-31 — try/finally + unlink(missing_ok=True))
- Детали: Если
subprocess.run(check=True)упадёт,temp_db.unlink()не выполнится →.tmp.dbостанется на диске.
- Файл:
src/core/graph.py:409-420,370 - Статус: ✅ CONFIRMED FIXED (busy_timeout=30000 уже установлен на connection, L370)
- Детали: На больших графах checkpoint может занять минуты и заблокировать закрытие. Нет
PRAGMA busy_timeout. - Верификация:
PRAGMA busy_timeout=30000присутствует в_get_conn— ожидание блокировки ограничено 30с, ошибка checkpoint перехватывается try/except,conn.close()выполняется в finally.
- Файл:
src/core/graph.py:353,359-374 - Статус: ⏳ ACCEPTED tech debt (2026-07-31 — задокументировано: сериализация через RLock — осознанный компромисс безопасности)
- Детали: Все операции сериализуются через один
threading.RLock. WAL mode позволяет конкурентные чтения, но блокировка это отменяет. - Решение: переход на read-write lock (RWLock) — отдельный рефакторинг с аудитом всех 40+ методов; текущая сериализация исключает класс гонок целиком. Зафиксировано как осознанный техдолг.
- Файл:
src/core/graph.py:995-1027 - Статус: ✅ FIXED (2026-07-31 — параметр limit + logger.warning при усечении)
- Детали: Возвращает максимум 200 кандидатов; если dead code больше, пользователь не узнает об усечении.
- Файл:
src/core/search/cypher_executor.py:81-95 - Статус: ✅ FIXED (2026-07-31 — sql/sql_params удалены из stats)
- Детали: Сгенерированный SQL и параметры возвращаются в
statsи попадают в MCP-ответ. Раскрывает внутреннюю структуру таблиц.
- Файл:
src/core/search/cypher_sql.py:252-262 - Статус: ✅ FIXED (2026-07-31 — явный NotImplementedError вместо неверного single-hop)
- Детали: Запрос
MATCH (n)-[:CALLS*1..5]->(m)возвращает только прямых соседей, не 5 уровней. Только debug-level лог. - Фикс:
max_hops > 1→raise NotImplementedErrorс пояснением (пользователь получает ошибку, а не тихо неверные результаты).
- Файл:
src/mcp/tools/write_tools.py:674-704 - Статус: ✅ FIXED (2026-07-31 — проверка
short_name in lineперед удалением, иначе skip + error) - Детали: Удаляет строки по номеру без проверки
symbol in text_lines[idx]. Если индекс устарел — удалятся неправильные строки.
- Файл:
src/mcp/tools/write_tools.py:312-390 - Статус: ✅ FIXED (2026-07-31 — ast.parse для .py перед записью)
- Детали:
new_codeвставляется как есть. Нетast.parseдля Python-файлов перед записью.
- Файл:
src/mcp/tools/write_tools.py:569-602 - Статус: ✅ CONFIRMED FIXED (batch 5601de39: tempfile.mkstemp + os.replace)
- Детали: Нет backup-файла, нет
tempfile + os.replaceпаттерна.
- Файл:
src/core/error_handler.py:562-574,620-630 - Статус: ✅ FIXED (2026-07-31 — detail=None; traceback остаётся в логах)
- Детали:
traceback.format_exc(limit=3)возвращается в MCP-ответ пользователю. Раскрывает пути к файлам, имена модулей.
- Файл:
src/core/intelligence/layer.py:858-885 - Статус: ✅ FIXED (2026-07-31 — git log fallback для packfiles)
- Детали: Не поддерживает packfiles (
.git/objects/pack/*.pack). Еслиgit gcбыл сделан — ADR-коллектор молча вернёт «не найдено». - Фикс:
_read_commit_msg— сначала loose-объект; при отсутствии (packfile) —git log --format=%s%x00%bдля хэша.
- Файл:
src/core/indexing/db_manager.py:56,228-250 - Статус: ✅ FIXED (2026-07-31 — ленивое создание lock в ensure_async_table)
- Детали:
asyncio.Lock()создаётся без running loop. ЕслиLanceDBManagerинстанцируется в sync-коде startup, аensure_async_tableвызывается из async MCP-handler в другом event loop — lock может привязаться к неправильному loop.
- Файл:
src/mcp/server_tools.py:156-210 - Статус: ✅ FIXED (2026-07-31 — экземпляры кэшируются в
_name_cache) - Детали: Каждый класс создаётся дважды: первый раз для
.name(фильтр), второй раз для регистрации. Кэш хранит только имя, не экземпляр. 38 инстанциаций вместо 19.
- Файл:
src/mcp/server_tools.py - Статус: ✅ CONFIRMED FIXED (grep:
SymbolWriteTool/_action_writeотсутствуют в server_tools.py) - Детали: Локальный import + новая инстанция каждый вызов. Для MCP-сервера с сотнями write-операций — лишний overhead.
| # | Нарушение | Пункт AGENTS.md |
|---|---|---|
| 1 | MCP-FIRST не соблюдён — grep/read_file вместо MCP-инструментов | §0.2 |
| 2 | .agent_task_state.md не обновлялся для текущей задачи |
§0.1 |
| 3 | EXPERIMENTS_LOG.md не заполнен |
§1.6 |
| 4 | ## Verification Results не создана |
§0.1.1 |
| 5 | verified_from_clean_state не выполнен |
§7 п.7 |
| 6 | ## Definition of Done не проверен |
§7 |
| 7 | Числа без команды замера | §5.15 |
| 8 | Verified vs Recalled не разграничены |
§1.14 |
| 9 | AGENT_DIARY.md не обновлён |
§4 |
| 10 | KNOWN_ISSUES.md не синхронизирован |
§4 п.6 |
- ✅ Parser
_parse_return_item— добавлена валидация alias token type (P0-1 частично) - ✅
.agent_task_state.mdобновлён - ✅ MCP connectivity проверена (
debug_runtime_passport— RUN_ID: ae0c5dfb6fab) - ✅ Все P0-P3 проблемы выписаны в ISSUE.md
- ✅ G-1 (2026-07-31): 5 stub-тестов закрыты — test_file_exists (FileGuard), test_searcher (Searcher sync-путь), test_chunk_cache (IndexPipeline.process_file), test_idle_reload (OnnxEmbedderClient), test_real_path (real-path резолюция). 52 теста вместо 10 stub; 658 passed; ruff clean; verify_diary 20/20.
- ✅ G-2 (2026-07-31): E2E MCP smoke-тест —
tests/e2e/test_e2e_mcp_smoke.py: реальный embedder (llama.cpp :8080) → реальная LanceDB (временная) → реальный поиск (fast, FTS5-fusion). Проверка входа→выхода: запросmove_chunks_metadata→ чанк изfile_move_manager.py. Требует живого MCP+embedder; в CI скипается (безMSCODEBASE_E2E=1). Команда:MSCODEBASE_E2E=1 python -m pytest tests/e2e/test_e2e_mcp_smoke.py -v. - ⏳ P2-1/P2-7, P3-3: осознанный техдолг — задокументировано в статусах (legacy broad excepts grandfathered через BLE001 ignores; RWLock — отдельный рефакторинг)
- ✅ P2-6: закрыт как TECH DEBT (ACCEPTED, 2026-07-31) — starvation, не deadlock; max_workers=2 недостижим легитимно (протокол запрещает 3+ параллельных MCP); persistent loop отложен намеренно
- ✅ P0 deadlock реиндекса (2026-07-31, регрессия ac6e5ba0e P1-3) — см. «z.ai review верификация»: bulk known_hashes в
index_project_runner.py+ тест test_index_runner_deadlock (валидирован) - Верификация через pytest после каждого фикса — выполнено: 666 passed, 0 failed
AGENT_DIARY.mdиKNOWN_ISSUES.md— синхронизированы- ✅ HTTP 400 llama.cpp embedder (2026-08-01) — v2: нативный
/tokenizetruncation (лимит 480 < 512), фикс 48e695b8 (HF 512) опровергнут замером (макс 502, в прогоне 526). Реиндекс 22:37→22:47 ЗАВЕРШЁН: 4677 chunks, FTS5 built, HTTP 400=0, Aborted=0, E2E search_code OK. Попутно: vulkaninfo cp1251-краш (§5.16) + pylance==9.0.0 (known_hashes). Тесты 667 passed, 13 skipped; версия 3.3.11 (локально). - ✅ Pre-commit hook блокер (2026-08-01) — verify_diary: время в заголовке опционально (склейка записей 31.07), negative lookbehind в
_extract_code_functions/_check_test_file_exists, полеclean_state_reason(§0.2); hook-шаблон: Popen+utf-8+CREATE_NO_WINDOW (§5.16) + reconfigure (9.9); SyntaxError:36 в git_hooks_installer.py починен (экранирование\"\"\"в PRE_COMMIT_HOOK); dead-ссылки generate_docs удалены (скрипт не существовал). Hook прогон: verify_diary 36 ✅/0 ❌ + stale_detector OK, RC 0.
Проверено 16 пунктов код-ревью z.ai по §1.14: 3 ✅ CONFIRMED (починены), 1 ⏳ PARTIAL (орфанный код починен, продакшн-путь был чист), 12 ❌ REFUTED (уже исправлены ранее).
| ID | Утверждение | File:Line | Статус | Вердикт / Фикс |
|---|---|---|---|---|
| LOGIC-4 Blocker | await self._flush() внутри with self._lock → deadlock batch_full |
rate_limiter.py:168-171 | ❌ REFUTED | flush уже ВНЕ lock (INC-53EC); _flush callback тоже вне lock (L234) |
| WIN-1 High | hash(str(db_path)) рандомизирован → mutex не работает |
graph.py:59 | ❌ REFUTED | уже blake2b (a9d92e00 P2-21) |
| LOGIC-1 High | move ищет по file_hash, не file_path | file_move_manager.py:37 | ⏳ PARTIAL | орфанный класс никем не вызывается (продакшн — Indexer.move_chunks_metadata, уже по file_path); сам класс ПОЧИНЕН |
| LOGIC-2 High | delete+add нетранзакционен | file_move_manager.py:34-41 | ⏳ PARTIAL | то же; починено: read→delete→add под lock |
| SEC-1 High | socket/ssl/http/multiprocessing в ALLOWED_MODULES | executor.py:36-46 | ❌ REFUTED | allowlist уже чистый (только stdlib) |
| SEC-2 High | urllib.request в _USER_ALLOWED | executor.py:126-135 | ❌ REFUTED | отсутствует (Blocks: os/subprocess/socket/...) |
| ARCH-1 High | resolve() не thread-safe | di_container.py:136 | ❌ REFUTED | уже threading.Lock (P2-18) |
| WIN-3/4 High | UNC-пути в _path_to_uri/_uri_to_path | lsp_client.py:657-669 | ✅ CONFIRMED | Path.as_uri() + netloc-ветка; тест test_lsp_uri_conversion |
| LOGIC-5 Med | cache TTL 30s без инвалидации | engine.py:96 | ✅ CONFIRMED | invalidate_cache() в runner + _index_single_file |
| LOGIC-7 Low | _apply_co_change_boost не присвоен Searcher | engine.py:1121-1125 | ❌ REFUTED | assign-as-method есть |
| LOGIC-8 Low | MMR remaining не по relevance | scoring.py:402 | ✅ CONFIRMED | remaining.sort(key=relevance, reverse=True) |
| WIN-2 Med | mutex silent fallback | graph.py:75-76,86-87 | ✅ CONFIRMED | warning при CreateMutexW fail/exception |
| WIN-8 Med | f-string JSON с str(e) | server_factory.py:308-319 | ✅ CONFIRMED | json.dumps + ensure_ascii=False |
| SEC-4 Low | str(e) в MCP-ответе | error_handler.py:569,625 | ✅ CONFIRMED | _sanitize_error_message (пути → , 200 симв.) |
| SEC-5 Low | sandbox_mode env без валидации | codebase_tool.py:295 | ✅ CONFIRMED | fallback на strict при невалидном значении |
| LOGIC-3 Med | ручное replace("'","''") |
file_move_manager.py:21 | ⏳ PARTIAL | переведено на _escape_sql_value |
| WIN-7 Low | md5[:8] коллизии | indexer.py:36 | ❌ ACCEPTED | смена на [:16] = смена имени БД = reindex (breaking, §1.11); отложено |
| ARCH-6 Low | add_singleton(None) silent no-op | di_container.py:109 | ⏳ DEFERRED | все вызовы передают реальные инстансы; ValueError рискован |
- Проверено 8 находок код-ревью: 7 ✅ CONFIRMED, 1 ❌ REFUTED
- ✅ КРИТ-1 (неатомарные записи write_tools) → P2-9 (scope расширен до 7 точек), закрыт
- ✅ КРИТ-2 (remove_zed_settings комментарии) → P1-15, закрыт (severity P0→P1: документирован в docstring)
- ✅ КРИТ-3 (patch_zed_settings неатомарна) → P1-16, закрыт
- ✅ P1 di_container resolve race → P2-18, закрыт (severity P1→P2: латентно)
- ✅ P1 engine.py asyncio.run → = существующий P2-6 (tech debt, дубликат не создавался)
- ✅ P2 command.split пробелы → P2-19, закрыт
- ✅ P2 llama_runner fd leak → P2-20, закрыт
- ❌ P3 server.py
_env_project_root_cache— REFUTED: env процесса фиксирован при спавне,reset_project_root_cache()вызывается изserver_factory.pydelayed bridge recheck, динамический путь идёт через LSP bridge, не через этот кэш
- ✅ A: engine.py
asyncio.runв_sync_executor→ P2-6, закрыт как TECH DEBT (ACCEPTED) с обоснованием (см. P2-6 статус) - ❌ B: di_container.py closure late-binding
_create_indexer_for_path— REFUTED: default-args capture уже применён (L286-290), фабрика регистрируется черезadd_singleton, ветка_factoriesлатентная (L140-142 комментарий); риск late binding = 0 - ❌ C: zed_config.py
$ZED_WORKTREE_ROOTв env — REFUTED:server.py:_resolve_env_project_root(L393-405) явно обрабатывает literalraw.startswith("$"); доки Zed не описывают$VAR-интерполяцию в env MCP; live-паспорт: PROJECT_PATH=literal, резолв работает через SQLite bridge (приоритет 0)
| ID | Вердикт | Куда записан | Доказательство |
|---|---|---|---|
| F-1 | ✅ CONFIRMED | P0-5 | executor.py:45-47 importlib* в ALLOWED_MODULES; runtime _safe_import блокирует (несоответствие слоёв) |
| F-2 | ✅ CONFIRMED | P0-5 | executor.py:86 — __build_class__ отсутствовал |
| F-3 | ✅ CONFIRMED | P0-5 | executor.py:134 — "sys" в _USER_ALLOWED при AST-блокировке import sys |
| F-4 | ✅ CONFIRMED | P0-5 | executor.py:350 — os.environ.copy() → секреты в sandbox |
| F-5 | ❌ REFUTED | — | tempfile.mkstemp уже создаёт файл с mode 0600 |
| D-1 | ✅ CONFIRMED | P2-21 | graph.py:55 — hash() (PYTHONHASHSEED) |
| C-5 | ✅ CONFIRMED | P2-22 | graph.py:520,831,1475 — total_changes cumulative |
| E-1 | ❌ REFUTED | — | MCP жив (RUN_ID 4ad0072c3a68, PID 2064) из ext dir с относительным command — Zed резолвит относительно корня расширения |
| Shutdown-race | ⏳ ACCEPTED | — | server_factory.py:584-598 — guards уже есть (running loop → create_task, иначе asyncio.run); процесс завершается, worst case — закрытие клиентов best-effort |
| C-2 | ✅ CONFIRMED | P2-24 | engine.py:482-521 — sort после MMR отменял переупорядочивание |
| C-1 | ✅ CONFIRMED | P2-23 | scoring.py:74,127 — tie-break по вставке |
| C-4 | ✅ CONFIRMED | P2-25 | graph.py:864-885 — BFS без max_nodes |
| D-6 | ✅ CONFIRMED | P2-26 | graph.py:376 — mmap 256MB |
| B-6 | ✅ CONFIRMED | P2-27 | di_container.py:369-370 — hasattr + приватный атрибут |
| E-7 | ❌ REFUTED | — | server_factory.py:56-61 — GetLastError() != 87 уже считает ACCESS_DENIED (5≠87) живым |
| D-3 | ⏳ ACCEPTED (tech debt) | — | _start_llama_sync new_event_loop+set_event_loop выполняется до asyncio.run() и им же заменяется; утечка loop'а на старте — один раз, безвредно |
| B-1 | ✅ CONFIRMED (severity ↑ High) | P1-17 | parser.py:69,331,671 — общий Parser + общий _cache_* при пуле из 4 потоков |
| DI resolve race | ❌ REFUTED | = P2-18 | di_container.py:136 — threading.Lock уже добавлен (Claude review P1, закрыт P2-18) |
Итого: 12 ✅ CONFIRMED (все закрыты фиксами), 4 ❌ REFUTED, 2 ⏳ ACCEPTED. Полный pytest 616 passed, 0 failed; ruff clean; bump_version 3.3.9.
Источник:
`debug_runtime_passport`.mdL3394-3466 (аудит MCP-инструментов из окна Bot_snow). Уже закрыто этой сессией: multi-window CWD-first (229c7156), stale_detector/_grep_fallback__file__-root (e2950a14), search_quality мониторинг (f54bea8b). Тесты 894 passed.
- Фикс: файлы без единой строки кода (init.py с комментарием) не индексируются
(
_has_code_linesв index_parser.py + parser.py fallback); тесты BS-1. - Файл:
src/mcp/tools/search_tools.py(путь выдачи); корень — эмбеддер-fallback → мусорные чанки - Детали: на несуществующий запрос возвращает 6 «результатов» (5/6 — пустые
__init__.pyсfallback_lines). Должен вернуть честный пустой ответ + подсказку. Проверить, не должны ли пустые чанки индексироваться/фильтроваться при выдаче.
- Фикс: реальный chunk_index в FTS5-метаданных (не 0 → не схлопывается с dense в RRF), буст точного имени (запрос-идентификатор), дедуп (file, symbol); тесты BS-2.
- Файл:
src/mcp/tools/search_tools.py - Детали: запрос «финансовый отчёт инструктора за неделю» не нашёл
get_report(financial.py), дублируетdelete_scheduleв выдаче.
- Фикс: start_line/end_line добавлены в metadata vector_search/search_async/FTS5; рендер 0→1-based; тесты BS-3.
- Файл:
src/mcp/tools/search_tools.py - Детали: line 0/2 вместо реальных L27-71 (все 4 проверенных символа).
- Фикс: буст точного имени (get_db над dense-мусором) в hybrid_search_async и fast-ветке; тест test_bs4.
- Файл:
src/mcp/tools/search_tools.py - Детали: запрос
get_dbне нашёлasync def get_db()(database.py:37).
- Фикс: layer читает c.get("symbol") (был c.get("name") → всегда ""); фильтр пустых; format_analysis_result не режет значения посреди (str[:60] → рекурсивный рендер); тесты BS-5.
- Файл:
src/core/intelligence/(topology-инструмент) - Детали:
symbol='',file=''у callees, обрыв строки в JSON.
- Фикс: «индексер молчит» — critical только если reindex идёт (is_reindexing+молчит); idle после завершённой индексации → метрика watchdog_state; тесты BS-6.
- Файл:
src/core/intelligence/health.py - Детали: «индексер молчит 278с» — нормальный idle после завершённой индексации, а не критично.
- Фикс: параметры query/name добавлены в execute-схему (target — backward-compat); тесты BS-7.
- Файл:
src/mcp/tools/graph_tools.py(или регистрация схемы) - Детали: «query required»/«name required», таких параметров в схеме нет.
- Фикс: телеметрия читает DI-инстанс embedder (_resolve_active_embedder), а не новый RemoteEmbedder() → единая правда с health (llama_cpp); тесты BS-8.
- Файлы: логи — «E5-base ONNX не загрузился, fallback»; телеметрия —
Provider: onnx, multilingual-e5-small-int8; runtime status —llama.cpp 🟢 - Детали: три инструмента показывают три разных провайдера. Привести к единой правде (кто реально активен).
- Фикс: DI регистрирует глобальный singleton get_global_registry() (было два разных реестра); тест BS-9.
- Детали: passport «Cached: 1» vs health
registry_cached_projects: 0.
- Фикс: проверка счётчика только при наличии маркера «N total/tools» в README; тесты BS-10.
- Детали: «README tool count устарел» — в README его нет.
- Фикс: run_full_diagnostic/hotspots в asyncio.to_thread + wait_for(3s); замер 15634→1261ms; тест BS-11.
- Детали: дефолтный ответ (probability 0.3) занимает 16 сек.
- Фикс: модули — reversed-сегменты пути (не «D:»), пустые file_path фильтруются (symbol_index.py + graph_adapter.py); тесты BS-12.
- Детали:
affected_files: [-, bot.py],affected_modules: [D:].
- Фикс: action="symbol" → делегирование в get_symbol_info; system-под-действия работают (проверено); тесты BS-13.
- Файл:
src/mcp/tools/codebase_tool.py - Детали: под-действия
systemнедостижимы (есть прямые эквиваленты).
- Статус P1-10: код-баг исправлен 2026-07-27 (везде
* 1000); оставались старые метрики в tool_metrics.json → санитизация при загрузке + guard в record/summary; тесты BS-14. - Связь: уже покрыто P1-10 (
error_handler.py:454—elapsed = ... - 1000вместо* 1000). Проверить статус P1-10 и закрыть.
- ❌
DEV_DIARY.mdв расширении — НЕ нарушение (§0.6: заглушка-редирект на AGENT_DIARY.md). ⚠️ financial.py:72-75(сравнение дат) — код проекта Bot_snow, НЕ расширения. Отдельная задача для окна Bot_snow.