Skip to content

Commit b7f8078

Browse files
author
MSCodeBase Agent
committed
fix: audit sprint — heartbeat SetLastError(0), reranker weights to config, sqlite schema cols, PYTHONUTF8=1, shell=False in install.py
5/6 P1/P2 items fixed, 1 refuted (BM25 reindex = cache invalidation only). - server_factory.py: SetLastError(0) before OpenProcess; GetLastError only when handle==0 - settings.py/engine.py: SearchConfig.bm25_weight/dense_weight from env (BM25_WEIGHT/DENSE_WEIGHT) - server.py: PRAGMA table_info validation for scoped_kv_store/workspaces columns - zed_config.py: PYTHONUTF8=1 in _make_server_entry (Windows cp1251 -> UTF-8) - install.py: _run() -> shlex.split + shell=False; step_pip Popen -> args list; fix stray ) syntax error - di_container.py: no change needed — BM25Mixin.reindex() only invalidates _bm25 cache (bm25.py:37-40) Tests: 756 passed, 4 skipped, 0 failed
1 parent 8a07c23 commit b7f8078

8 files changed

Lines changed: 149 additions & 9 deletions

File tree

AGENT_DIARY.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,26 @@
223223
**Guard:** 37 passed (test_write_tools) + 10 passed (health/architecture/index_guard); import OK; ext синхронизирован (cp); live-проверка после Reload Window.
224224
**verified_from_clean_state:** ✅ yes — live-подтверждено на RUN_ID e3f3aabd7186: codebase(action="index", path="status") → 4842 chunks; codebase(action="write", ...) → modification guard + impact_token.
225225

226+
227+
---
228+
229+
## [2026-08-03 23:55] — Сессия: §1.19 Hard Triggers + аудит 29 пунктов + docs sync + commit/push
230+
231+
**Status:** ✅ Done
232+
**Root Cause:** протокол требовал жёстких триггеров (§1.19), аудит audit.md был неразмечен, README/doc badges устарели (649→747), DEV_DIARY не архивирован, CHANGELOG пуст, unpushed commit.
233+
**Fix:** (1) §1.19 Hard Triggers в личный AGENTS.md (5 блокираторов «запрещено без»); (2) 29 вердиктов в experiments/audit.md (4✅, 3⚠️, 7❌, 15📝); (3) README/docs/{ru,zh}: badges 747, 48 tools, dates 2026-08-03; (4) onnx off-by-one parents[3]; hub write sub-action dispatch; Py3.14 get_running_loop; (5) verify_diary 3 ложных ❌; (6) DEV_DIARY → ARCHIVED header; (7) commit 8a07c23e + push origin/main.
234+
**Guard:** pre-commit (verify_diary 25✅/0❌, stale_detector OK); bump_version --check ✅ (3.3.11); live E2E MCP chain verified on RUN_ID e3f3aabd7186 (edit→notify→reindex→search_code 4857 chunks).
235+
**verified_from_clean_state:** ✅ yes — clean clone+venv+install+pytest (747 passed) + live MCP chain verified.
236+
237+
---
238+
239+
## [2026-08-04] — Спринт: 6 пунктов аудита (5 ✅ Fixed, 1 ❌ Refuted) + docs + commit/push
240+
241+
**Status:** ✅ Done (5/6 FIXED, 1/6 REFUTED)
242+
**Root Cause:** 6 ❌ P1/P2 пунктов из experiments/audit.md требовали фикса: Heartbeat GetLastError, hardcoded reranker weights, BM25 sync reindex, SQLite schema cols, PYTHONUTF8, shell=True.
243+
**Fix:** (1) server_factory.py:57-68 SetLastError(0)+GetLastError только при handle==0; (2) settings.py SearchConfig.bm25/dense_weight из env + engine.py:86-89; (3) Item 8 REFUTED — BM25Mixin.reindex() = только инвалидация кэша `_bm25=None` под локом (bm25.py:37-40), блокировки нет; (4) server.py:266-289 PRAGMA table_info для {key,value}/{workspace,data}; (5) zed_config.py:270-273 env[PYTHONUTF8]="1"; (6) install.py _run()→shlex.split+shell=False, step_pip Popen→список, фикс stray `)` (syntax error).
244+
**Guard:** 756 passed, 4 skipped, 0 failed; AST OK на 7 файлах; shlex.split проверен на PowerShell-команде (4 args, скрипт цел); Ledger §0.1.1 — прошлая запись «Item 8 FIXED via ThreadPoolExecutor» была ложной (di_container.py не менялся) — сверять ledger с git status (§9 pitfall 1).
245+
**verified_from_clean_state:** ⚠️ no — pytest полный (756 passed) + AST, но verify_clean_state.sh (clone+venv) не гонялся в этой сессии.
246+
247+
---
248+

KNOWN_ISSUES.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2373,3 +2373,72 @@ Three fixes from the same review:
23732373
**Fix:** все записи < 2026-08-01 перенесены в docs/archive/AGENT_DIARY_2026_07.md (заголовок ARCHIVE — см. A...
23742374
- **Статус:** автоматически синхронизировано
23752375

2376+
2377+
## 2026-08-03 23:55 — §1.19 Hard Triggers внедрены в личный AGENTS.md (RESOLVED)
2378+
2379+
**Symptom:** протокол (§1.15, §1.16, §1.18, §3.5, §0.1.1) сформулирован как «обязан» — агент выполняет шаги, но не думает по ним (нет Phase Zero, Red Team, обобщения, немедленной meta-check, Verification Ledger).
2380+
**Root Cause:** «Обязан» — карта, которую можно не читать. Нужны «рельсы» («запрещено без»).
2381+
**Fix:** §1.19 ЖЁСТКИЕ ТРИГГЕРЫ: 5 блокираторов (Phase Zero, Red Team, grep-обобщение, немедленный META-CHECK, Verification Ledger ≥3 подзадачи). Формат «запрещено без» + Red Team формат `[🔓 RED TEAM] Атака N → Защита/Нет защиты`.
2382+
**Status:** ✅ внедрено в C:\Users\misha\AppData\Roaming\Zed\AGENTS.md (1375→1423 строки). Red Team 5/5 защищено на самой правке.
2383+
2384+
## 2026-08-03 23:55 — Аудит audit.md: 29 вердикты размечены (PARTIAL — 7 неисправлено, 15 рекомендаций)
2385+
2386+
**Symptom:** audit.md (2053 строк) содержал 24 пункта + solo-dev 5 без статусов.
2387+
**Root Cause:** аудит накапливался без обновления статусов.
2388+
**Fix:** скрипт .local/patch_audit_status.py вписал 29 вердиктов с File:Line: 4✅ (DI, resolve root, RRF, ack_impact), 3⚠️ (asyncio.Lock, progress cleanup, _distance inconsistency), 7❌ (Heartbeat GetLastError, SearchResultReranker weights, BM25 sync reindex, SQLite schema cols, PYTHONUTF8, shell=True, CodeParser leak), 15📝 рекомендаций (cancellation, OTel, Prometheus, ConfigReloader, chaos, property-based, AgentFriendlyError, ResourceGuard, uninstall, DevModeReloader и др.).
2389+
**Status:** ⚠️ частично — баги 1,2,3,6,8,10,11,12,13,16,17 остаются открытыми; рекомендации 15-29 — новые фичи, не баги.
2390+
2391+
## 2026-08-03 23:55 — Документация синхронизирована (RESOLVED)
2392+
2393+
**Symptom:** README/docs badges 649/667 tests (реально 747), tool counts 42/48 (реально 48), dates 2026-07-21, CHANGELOG пуст в корне.
2394+
**Fix:** README.md + docs/{ru,zh}/README.md: badges 747 passed, 48 tools, dates 2026-08-03; ru/zh heading anchors fixed; docs/en/CHANGELOG.md уже актуален (3.3.11, 48 tools).
2395+
**Status:** ✅ docs/en/ru/zh/README.md + bump_version --check ✅ (3.3.11).
2396+
2397+
## 2026-08-03 23:55 — Commit+push выполнен (RESOLVED)
2398+
2399+
2400+
## 2026-08-03 — Аудит audit.md: открытые пункты (29 вердиктов, 6✅, 6❌, 6⚠️, 11📝)
2401+
2402+
### ✅ ИСПРАВЛЕНО (спринт 2026-08-04, 5/6 FIXED + 1 REFUTED)
2403+
2404+
| # | Пункт | Файл | Статус | Доказательство |
2405+
|---|-------|------|--------|----------------|
2406+
| 2 | HeartbeatService: нет SetLastError(0) перед OpenProcess, fail-open | src/mcp/server_factory.py:57-68 | ✅ Fixed | SetLastError(0) перед OpenProcess; GetLastError читается только при handle==0; fail-open сохранён. Red Team 5/5. |
2407+
| 6 | SearchResultReranker: hardcoded веса bm25_weight=0.3, dense_weight=0.7 | src/config/settings.py + src/core/search/engine.py:86-89 | ✅ Fixed | SearchConfig.bm25_weight/dense_weight из env (BM25_WEIGHT/DENSE_WEIGHT, дефолты 0.3/0.7); engine читает get_config().search.* |
2408+
| 8 | BM25 reindex callback: синхронный reindex в DebounceBatch | src/core/di_container.py:296-300 | ❌ Refuted | `BM25Mixin.reindex()` (src/core/search/bm25.py:37-40) = только `_bm25 = None` под `_bm25_lock` (O(1), инвалидация кэша). Полный rebuild — лениво при следующем поиске. Блокировки потока НЕТ. Предыдущая запись ledger «FIXED via ThreadPoolExecutor» — ложь, правки не было. |
2409+
| 11 | SQLite schema validation: только таблицы, нет колонок | src/mcp/server.py:266-289 | ✅ Fixed | PRAGMA table_info(scoped_kv_store) → {key, value}; PRAGMA table_info(workspaces) → {workspace, data} |
2410+
| 12 | Encoding: нет PYTHONUTF8=1 | src/utils/zed_config.py:270-273 | ✅ Fixed | env[\"PYTHONUTF8\"] = \"1\" в _make_server_entry (Windows cp1251 → UTF-8) |
2411+
| 13 | install.py: shell=True в subprocess | install.py:254-264, 549-556 | ✅ Fixed | _run() → shlex.split + shell=False; step_pip Popen → список аргументов + shell=False; фикс stray `)` (синтаксическая ошибка) |
2412+
2413+
### ⚠️ ЧАСТИЧНО (P2 — требуют доработки)
2414+
2415+
| # | Пункт | Файл | Статус |
2416+
|---|-------|------|--------|
2417+
| 3 | asyncio.Lock создаётся вне event loop (cross-loop risk) | src/core/search/engine.py:91 | 🟡 Partial |
2418+
| 4 | Progress tracking: эвристический cleanup (len > 10, 1ч) | src/mcp/server.py:202-229 | 🟡 Partial |
2419+
| 10 | LanceDB _distance: engine OK, multi_project_searcher использует raw | src/core/multi_project_searcher.py:161-169 | 🟡 Partial |
2420+
| 17 | PropertyGraph: lock есть, _recover_from_wal отсутствует | src/core/graph.py:742-795 | 🟡 Partial |
2421+
| 19 | Rate limiting: только provider-level, нет MCP-level | src/core/rate_limiter.py | 🟡 Partial |
2422+
| 26 | Agent-friendly errors: error_boundary есть, AgentFriendlyError нет | src/mcp/tools/write_tools.py:135 | 🟡 Partial |
2423+
2424+
### 📝 РЕКОМЕНДАЦИИ (P3/P4 — tech debt / новые фичи)
2425+
2426+
| # | Пункт | Файл | Статус |
2427+
|---|-------|------|--------|
2428+
| 15 | Cancellation handling: MCP запросы не отменяются || 📝 Tech Debt |
2429+
| 16 | Tree-sitter: CodeParser leak (нет close/__del__) | src/core/indexing/parser.py:21 | 📝 Tech Debt |
2430+
| 18 | MCP Progress notifications: не используются notifications/progress || 📝 Tech Debt |
2431+
| 20 | OpenTelemetry: нет distributed tracing || 📝 Tech Debt |
2432+
| 21 | Prometheus: нет metrics integration || 📝 Tech Debt |
2433+
| 22 | Config hot-reload: ConfigReloader отсутствует || 📝 Tech Debt |
2434+
| 23 | Chaos-тесты: kill process during indexing || 📝 Tech Debt |
2435+
| 24 | Property-based тесты для scoring (RRF, cosine) || 📝 Tech Debt |
2436+
| 27 | ResourceGuard: защита от OOM || 📝 Tech Debt |
2437+
| 28 | One-command uninstall в install.py | install.py | 📝 Tech Debt |
2438+
| 29 | Hot-reload кода: DevModeReloader отсутствует || 📝 Tech Debt |
2439+
2440+
2441+
**Symptom:** 19+ изменённых файлов, unpushed commit 5ce0eaa3 (origin/main = ab44b00d).
2442+
**Fix:** commit 8a07c23e (34 files, +2362/-522), pre-commit OK (verify_diary 25✅, stale_detector OK), push origin/main.
2443+
**Status:** ✅ origin/main up to date (HEAD = 8a07c23e).
2444+

install.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,19 @@ def _is_within(path: Path, root: Path) -> bool:
252252

253253

254254
def _run(cmd: str, timeout: int = 120, capture: bool = True) -> Optional[subprocess.CompletedProcess]:
255-
"""subprocess.run с таймаутом и безопасным возвратом None вместо исключения."""
255+
"""subprocess.run с таймаутом и безопасным возвратом None вместо исключения.
256+
257+
Использует shell=False со списком аргументов для безопасности.
258+
Команда разбивается через shlex.split() для корректной обработки
259+
путей с пробелами и спецсимволами.
260+
"""
261+
import shlex
262+
256263
try:
264+
args = shlex.split(cmd)
257265
if capture:
258-
return subprocess.run(cmd, shell=True, timeout=timeout, capture_output=True, text=True)
259-
return subprocess.run(cmd, shell=True, timeout=timeout)
266+
return subprocess.run(args, shell=False, timeout=timeout, capture_output=True, text=True)
267+
return subprocess.run(args, shell=False, timeout=timeout)
260268
except subprocess.TimeoutExpired:
261269
logger.debug("command timed out after %ss: %s", timeout, cmd[:80])
262270
return None
@@ -539,8 +547,8 @@ def step_pip(lines, lang):
539547
_run(f'"{PYTHON_EXE}" -m pip install --upgrade pip', timeout=60)
540548

541549
proc = subprocess.Popen(
542-
f'"{PYTHON_EXE}" -m pip install -r "{req}"',
543-
shell=True,
550+
[PYTHON_EXE, "-m", "pip", "install", "-r", str(req)],
551+
shell=False,
544552
cwd=str(ZED_EXT_DIR),
545553
stdout=subprocess.PIPE,
546554
stderr=subprocess.STDOUT,

src/config/settings.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ class SearchConfig:
138138
default_search_limit: int = int(os.getenv("DEFAULT_SEARCH_LIMIT", "6"))
139139
max_search_results: int = int(os.getenv("MAX_SEARCH_RESULTS", "20"))
140140

141+
# BM25 + Dense weights (SearchResultReranker legacy)
142+
bm25_weight: float = field(
143+
default_factory=lambda: float(os.getenv("BM25_WEIGHT", "0.3"))
144+
)
145+
dense_weight: float = field(
146+
default_factory=lambda: float(os.getenv("DENSE_WEIGHT", "0.7"))
147+
)
148+
141149
# Query expansion
142150
query_synonyms_enabled: bool = (
143151
os.getenv("QUERY_SYNONYMS_ENABLED", "true").lower() == "true"

src/core/search/engine.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def __init__(self, indexer, embedder):
8383
self._fts5_lock = threading.Lock()
8484
self._bm25_df: Any = None
8585
self._tokenizer_re = re.compile(r"\W+")
86-
self._reranker = SearchResultReranker(bm25_weight=0.3, dense_weight=0.7)
86+
self._reranker = SearchResultReranker(
87+
bm25_weight=get_config().search.bm25_weight,
88+
dense_weight=get_config().search.dense_weight
89+
)
8790
self._multi_reranker: Optional[MultiProviderReranker] = None
8891
self._multi_reranker_initialized: bool = False
8992
self._multi_reranker_lock = asyncio.Lock()

src/mcp/server.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ def _cleanup_old_progress():
264264

265265

266266
def _check_sqlite_schema_health(conn) -> Optional[str]:
267-
"""Проверяет, что таблицы scoped_kv_store и workspaces существуют.
267+
"""Проверяет, что таблицы scoped_kv_store и workspaces существуют
268+
и содержат ключевые колонки.
268269
269270
Принимает уже открытое соединение — не вызывает _get_sqlite_connection()
270271
рекурсивно. Вызывается один раз при старте.
@@ -273,6 +274,8 @@ def _check_sqlite_schema_health(conn) -> Optional[str]:
273274
return "Zed SQLite DB недоступна — workspace-резолвинг будет degraded"
274275
try:
275276
cur = conn.cursor()
277+
278+
# Проверяем таблицы
276279
cur.execute(
277280
"SELECT name FROM sqlite_master WHERE type='table' AND name='scoped_kv_store'"
278281
)
@@ -283,6 +286,23 @@ def _check_sqlite_schema_health(conn) -> Optional[str]:
283286
)
284287
if cur.fetchone() is None:
285288
return "workspaces не найдена! workspace-резолвинг будет degraded"
289+
290+
# Проверяем ключевые колонки scoped_kv_store
291+
cur.execute("PRAGMA table_info(scoped_kv_store)")
292+
kv_columns = {row[1] for row in cur.fetchall()}
293+
required_kv = {"key", "value"}
294+
missing_kv = required_kv - kv_columns
295+
if missing_kv:
296+
return f"scoped_kv_store: отсутствуют колонки {missing_kv} — схема устарела"
297+
298+
# Проверяем ключевые колонки workspaces
299+
cur.execute("PRAGMA table_info(workspaces)")
300+
ws_columns = {row[1] for row in cur.fetchall()}
301+
required_ws = {"workspace", "data"}
302+
missing_ws = required_ws - ws_columns
303+
if missing_ws:
304+
return f"workspaces: отсутствуют колонки {missing_ws} — схема устарела"
305+
286306
return None
287307
except Exception as e:
288308
return f"Ошибка проверки схемы SQLite: {e}"

src/mcp/server_factory.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,22 @@ def is_parent_alive(self) -> bool:
5555
try:
5656
if sys.platform == "win32":
5757
import ctypes
58+
# Сбрасываем last error перед вызовом OpenProcess
59+
# (OpenProcess при успехе не сбрасывает GetLastError)
60+
ctypes.windll.kernel32.SetLastError(0)
5861
handle = ctypes.windll.kernel32.OpenProcess(0x0400, False, pid)
5962
if handle:
6063
ctypes.windll.kernel32.CloseHandle(handle)
6164
return True
62-
return ctypes.windll.kernel32.GetLastError() != 87
65+
# Проверяем ошибку ТОЛЬКО при handle == 0
66+
err = ctypes.windll.kernel32.GetLastError()
67+
return err != 87 # ERROR_INVALID_PARAMETER
6368
os.kill(pid, 0)
6469
return True
6570
except (ProcessLookupError, PermissionError, OSError):
6671
return False
6772
except Exception:
73+
# fail-open: не убиваем процесс из-за ошибки проверки
6874
return True
6975

7076
async def _monitor(self) -> None:

src/utils/zed_config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def _make_server_entry(existing: dict | None, executable: str, args: list, ext_d
256256
"""Build the merged server entry, preserving user customizations in `env`.
257257
258258
Authoritative keys (ours) are always set: enabled, command, args,
259-
PYTHONPATH, PROJECT_PATH. Optional keys (EMBEDDING_*) keep the user's
259+
PYTHONPATH, PROJECT_PATH, PYTHONUTF8. Optional keys (EMBEDDING_*) keep the user's
260260
value if present. Any other user-added keys are preserved.
261261
"""
262262
entry = dict(existing) if existing else {}
@@ -268,6 +268,9 @@ def _make_server_entry(existing: dict | None, executable: str, args: list, ext_d
268268
# Authoritative (ours):
269269
env["PYTHONPATH"] = str(ext_dir)
270270
env["PROJECT_PATH"] = "$ZED_WORKTREE_ROOT"
271+
# Windows: без PYTHONUTF8=1 Python читает исходники в cp1251,
272+
# ломая индексацию русских комментариев и имён (аудит Item 12/5).
273+
env["PYTHONUTF8"] = "1"
271274
# Optional: keep user value if present, else default.
272275
env.setdefault("EMBEDDING_PROVIDER", "e5_onnx")
273276
env.setdefault("EMBEDDING_DIMENSION", "768")

0 commit comments

Comments
 (0)