feat(palier-3): recherche globale, mes issues, briefing ntfy, MCP lecture

- client: search_issues (/repos/issues/search, type=issues,
  assigned_by), delete_label + forget_label_cache; models: kv_store
- /search groupe par projet avec colonne Kanban; /my = issues
  ouvertes assignées à l'utilisateur du jeton (get_user)
- briefing: _briefing_data (focus, âgées, milestones <= 7 j,
  activité journal), page /briefing, /api/briefing, envoi ntfy
  manuel + planificateur lifespan à KANBANTE_BRIEFING_TIME
  (kv briefing_last_sent, pas de doublon); app/briefing.py =
  format texte + envoi ntfy (en-têtes ASCII)
- MCP: POST /mcp JSON-RPC (initialize, tools/list, tools/call,
  ping, notifications 202), 7 outils en lecture seule, GET /mcp
  de découverte; erreurs -32601/-32700, isError pour les outils
- focus refactoré en _collect_focus_groups réutilisé par le
  briefing et l'outil MCP get_focus
This commit is contained in:
Spark
2026-10-11 11:00:53 -04:00
parent 2d89db8213
commit a788db7b1f
5 changed files with 718 additions and 6 deletions
+31
View File
@@ -269,6 +269,37 @@ class GiteaClient:
)
r.raise_for_status()
async def delete_label(self, owner: str, repo: str, label_id: int) -> None:
"""Delete a label definition from the repo (all issues lose it)."""
r = await self.client.delete(f"/repos/{owner}/{repo}/labels/{label_id}")
r.raise_for_status()
self.forget_label_cache(owner, repo)
def forget_label_cache(self, owner: str, repo: str) -> None:
cache = self.__dict__.get("_label_cache")
if cache:
cache.pop(f"{owner}/{repo}", None)
# ── Issue search (global + assigned) ───────────────────
async def search_issues(
self,
q: str = "",
state: str = "open",
assigned_by: str | None = None,
) -> list[dict]:
"""Search issues across every repo visible to the token.
Uses Gitea's /repos/issues/search (type=issues excludes PRs).
Results carry their repository in issue["repository"].
"""
params: dict[str, Any] = {"state": state, "type": "issues"}
if q:
params["q"] = q
if assigned_by:
params["assigned_by"] = assigned_by
return await self._get_all_pages("/repos/issues/search", **params)
# ── Milestones ─────────────────────────────────────────
async def get_milestones(