fix(api): unconditional /git/file window cap; defer Telegram sends after commit; docs/map periodic re-index

_compute_file_range now caps any resolved window at _FILE_MAX_LINES
instead of only the exact whole-file shape, closing the near-whole-file
bypass. Telegram sends ride a generalized after-commit outbox
(defer_after_commit over the F107 machinery) so a slow Bot API can no
longer hold the caller's transaction open; TelegramClient grows an
abstract close(). The KB update loop iterates AUTO_INDEX_DIRS so
docs/map edits re-index without a restart. PR-label application
catches all exceptions per its never-raises contract, and pr_merge's
CEO-only message names the resolved branch.
This commit is contained in:
Renn F
2026-07-15 08:25:20 +02:00
parent 85ac6422ff
commit f1ff149b70
9 changed files with 337 additions and 102 deletions
+37
View File
@@ -714,6 +714,43 @@ async def test_ensure_base_falls_back_to_default_when_create_fails() -> None:
assert out == "master"
# ---------------------------------------------------------------------------
# _apply_pr_labels / _ensure_label_exists: never raise, even on a non-httpx
# error — labeling must not surface as a spurious tool failure after the PR
# itself was already created.
# ---------------------------------------------------------------------------
def _non_httpx_raising_client() -> MagicMock:
"""An AsyncClient whose POST raises a plain (non-httpx) exception."""
fake_client = MagicMock()
fake_client.__aenter__ = AsyncMock(return_value=fake_client)
fake_client.__aexit__ = AsyncMock(return_value=False)
fake_client.post = AsyncMock(side_effect=RuntimeError("boom"))
return fake_client
@pytest.mark.asyncio
async def test_ensure_label_exists_swallows_non_httpx_error() -> None:
svc = _service()
with patch(
"roboco.services.git.httpx.AsyncClient",
return_value=_non_httpx_raising_client(),
):
await svc._ensure_label_exists("acme", "repo", "tok", "cell/backend")
@pytest.mark.asyncio
async def test_apply_pr_labels_swallows_non_httpx_error() -> None:
svc = _service()
_bind(svc, "_ensure_label_exists", AsyncMock())
with patch(
"roboco.services.git.httpx.AsyncClient",
return_value=_non_httpx_raising_client(),
):
await svc._apply_pr_labels("acme", "repo", "tok", 11, ["cell/backend"])
# ---------------------------------------------------------------------------
# pr_merge: returns merge commit dict
# ---------------------------------------------------------------------------
@@ -9,11 +9,15 @@ branch is needed — every docs/map/*.md rides index_documentation like docs/rag
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock, patch
import pytest
from roboco.services.optimal import IndexingReport, OptimalService
if TYPE_CHECKING:
from pathlib import Path
@pytest.mark.asyncio
async def test_auto_index_docs_includes_map_subdir() -> None:
@@ -30,3 +34,28 @@ async def test_auto_index_docs_includes_map_subdir() -> None:
names = [c.args[1] for c in mock.call_args_list]
assert "rag" in names
assert "map" in names
@pytest.mark.asyncio
async def test_periodic_check_reindexes_modified_map_file(tmp_path: Path) -> None:
"""The periodic loop must watch docs/map too, not just docs/rag — a
modified docs/map file with an already-tracked (stale) mtime is picked
up by _check_for_updates and routed through _index_doc_file with the
"map" dir name, exactly like the one-shot path resolves it."""
docs_root = tmp_path / "docs"
(docs_root / "rag").mkdir(parents=True)
(docs_root / "map").mkdir(parents=True)
map_file = docs_root / "map" / "CLAUDE.md"
map_file.write_text("# initial")
svc = object.__new__(OptimalService)
svc._docs_root = docs_root
# Simulate this file was already indexed at startup with a stale mtime,
# so the next scan sees it as modified.
svc._file_mtimes = {str(map_file): 0.0}
mock = AsyncMock()
with patch.object(svc, "_index_doc_file", new=mock):
await svc._check_for_updates()
mock.assert_awaited_once_with(map_file, "map")