feat(palier-1): journal d'événements, détail d'issue, vieillissement, WIP

- board_events append-only + log_event/get_events/column_entry_times/
  get_metrics (débit, temps de cycle); journalisation des moves,
  créations, fermetures/rouvertures (UI et réconciliation Gitea),
  commentaires; un reclassement intra-colonne ne journalise pas
- client Gitea: get_issue, get_issue_comments (paginés), create_comment
- API: GET /api/issues/{o}/{r}/{n} (404 propre), POST .../comments,
  GET /api/events, GET /api/metrics, page /activity
- config: KANBANTE_WIP_LIMITS (colonne:limite) et KANBANTE_AGING_DAYS;
  le board calcule le temps en colonne (journal, replis updated_at
  puis création de l'issue)
- reconcile_board_state retourne aussi les numéros fermés/purgés
This commit is contained in:
Spark
2026-10-11 10:05:51 -04:00
parent 2060ca949d
commit a6d2ed9eae
4 changed files with 372 additions and 4 deletions
+21 -1
View File
@@ -17,6 +17,12 @@ Environment variables:
KANBANTE_MAX_NOTE_CHARS Maximum size of a project note (default 20000).
KANBANTE_SYNC_GITEA "1" (default) = moving a card to/from "done"
closes / reopens the Gitea issue.
KANBANTE_WIP_LIMITS Comma list "column:limit" (e.g.
"in_progress:5,review:3"). Columns not
listed are unlimited. Soft limits: the UI
warns, moves are never blocked.
KANBANTE_AGING_DAYS Days in the same column before a card is
flagged as aging (default 7; "stale" at 2x).
"""
import os
@@ -25,7 +31,7 @@ from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
APP_VERSION = "0.2.0"
APP_VERSION = "0.3.0"
def _env_bool(name: str, default: bool) -> bool:
@@ -51,6 +57,16 @@ def _env_float(name: str, default: float) -> float:
return default
def _env_wip_limits(name: str) -> dict[str, int]:
limits: dict[str, int] = {}
for chunk in os.getenv(name, "").split(","):
column, sep, value = chunk.partition(":")
column = column.strip()
if sep and column and value.strip().isdigit():
limits[column] = int(value.strip())
return limits
@dataclass(frozen=True)
class Settings:
gitea_url: str = "https://git.dracodev.net"
@@ -63,6 +79,8 @@ class Settings:
cache_ttl: float = 30.0
max_note_chars: int = 20_000
sync_gitea: bool = True
wip_limits: dict | None = None
aging_days: int = 7
@property
def auth_enabled(self) -> bool:
@@ -84,4 +102,6 @@ def get_settings() -> Settings:
cache_ttl=_env_float("KANBANTE_CACHE_TTL", 30.0),
max_note_chars=_env_int("KANBANTE_MAX_NOTE_CHARS", 20_000),
sync_gitea=_env_bool("KANBANTE_SYNC_GITEA", True),
wip_limits=_env_wip_limits("KANBANTE_WIP_LIMITS"),
aging_days=_env_int("KANBANTE_AGING_DAYS", 7),
)