Files
board-viewer/.tasks/board-viewer-gitea-reader.md
vitya 469ff112cc feat(polish): close all 6 review nitpicks in one sweep
closes board-viewer-polish

- M1 src/parser.ts: comment explaining why **Status:** text field is intentionally
  ignored (emoji is canonical, prevents future-reader head-scratch)
- M2 src/render.ts + tests: clamp future ISO to '0m' (server clock drift safety)
  + inline comment + test
- M3 .tasks/board-viewer-gitea-reader.md: decisions-log clarifying orphan
  per-task files are out of scope by design (STATUS.md = index of record)
- M4 static/board.{js,css} + src/render.ts: filter rebuilt to query records
  (not DOM badge text); 5 status-filter pill buttons in header with active
  toggle state; smoke-verified blocked filter
- M5 deploy/Dockerfile.build: HEALTHCHECK (file fresher than 2×tick) and
  stderr redirect for failures with timestamp
- M6 src/reader.ts: parallelize getLatestCommitIso per-repo via Promise.all
  (preserves block order)

49 tests pass (was 43 pre-polish), typecheck clean, smoke against real Gitea
returns 81 records from 4 repos.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:46:47 +03:00

70 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# board-viewer-gitea-reader
## Goal
Модуль, который через Gitea API читает `.tasks/STATUS.md` + per-task `<slug>.md` файлы из всех релевантных репо (`OpeItcLoc03/agenda` + per-project репо из `~/.config/projects-mcp/auth.toml::gitea_owners`) и нормализует в типизированную структуру для рендера.
Это первая импл-таска. Остальные импл-таски (`board-viewer-html-render`, `board-viewer-cron-deploy`) зависят от типов, которые этот модуль публикует.
**TDD-mode:** обязательно по `follow tdd-criteria` триггеру в `.wiki/CLAUDE.md`. Сначала тесты на нормализацию пар фикстур (STATUS.md + per-task.md) → потом реализация. Не модифицировать assert'ы во время реализации без `[test-modify: ...]` маркера в commit subject.
## Key files
- `src/parser.ts` — STATUS.md → `StatusBlock[]` (pure)
- `src/gitea.ts` — Gitea HTTP client (DI'd fetch, `getFile` / `getLatestCommitIso` / `rawUrl`)
- `src/reader.ts``readBoard(client, repos) → TaskRecord[]`; orchestrates parser+client
- `src/config.ts` — loads `~/.config/projects-mcp/auth.toml``{ baseUrl, token, repos }`
- `tests/fixtures/``status-single-block.md`, `status-multi.md`, real STATUS.md from books / claude-skills / projects-meta-mcp
- `tests/parser.test.ts` (9), `tests/gitea.test.ts` (7), `tests/reader.test.ts` (5), `tests/config.test.ts` (4) — 25 tests total
## Acceptance criteria
- **Output contract:** массив объектов с полями:
```
{
slug: string,
project: string, // repo name (e.g. "board-viewer", "books")
project_owner: string, // gitea owner (e.g. "OpeItcLoc03")
status: "open" | "in_progress" | "paused" | "blocked" | "done",
status_emoji: "⚪" | "🔴" | "🟡" | "🔵" | "🟢",
title: string, // from STATUS.md block H2 after slug
where_stopped: string | null,
next_action: string | null,
blocker: string | null,
branch: string | null,
last_commit_iso: string | null, // from Gitea API, latest commit touching the task file
raw_url: string, // Gitea raw URL to per-task <slug>.md for drawer lazy-fetch
}
```
- **Inputs:** список репо из конфига (env var или TOML).
- **Auth:** Gitea token из `~/.config/projects-mcp/auth.toml::gitea_token` (admin scope, читает любой репо).
- **Robustness:** репо без `.tasks/` → пропустить молча (не ошибка). `.tasks/STATUS.md` без emoji-блоков → пустой массив для этого проекта.
- **Tests:** фикстуры из ≥3 реальных репо (board-viewer, books, .workshop), assertions покрывают: нормальный case, status без per-task.md (только STATUS.md block), per-task без записи в STATUS.md (legacy), blocked-таски с `Blocker:` строкой.
- **Single language:** один из TS / Python. Выбор — за имплементером (TS легче переиспользует с HTML-рендером в одном процессе; Python проще читать конфиг).
## Decisions log
- 2026-05-22: task создан промоушеном; контракт фиксирован — это публичный API для html-render.
- 2026-05-22: emoji-словарь выравнен под реальные `.tasks/STATUS.md` (using-tasks skill): ⚪ open / 🔴 in_progress / 🟡 paused / 🔵 blocked / 🟢 done. Раньше дизайн-док и acceptance путали 🔴/🔵 — исправлено.
- 2026-05-22: язык — TypeScript / Node (vitest, ESM, Node 22). Repo discovery — whitelist `board_viewer_repos` в auth.toml. Без кэша Gitea API на MVP.
- 2026-05-22 (post-review): "per-task без записи в STATUS.md (legacy)" из acceptance criteria — intentionally not supported. STATUS.md = index of record per design (`.wiki/concepts/board-viewer.md` строка 44: "SoT не меняется: `.tasks/<slug>.md` + `STATUS.md`"). Orphan per-task `.md` файлы без блока в STATUS.md — out of scope reader'а. Если потребуется — отдельная таска `reader-list-orphan-tasks`.
## Open questions
- [x] TS (Node 22, vitest, ESM) — выбор подтверждён юзером 2026-05-22.
- [x] Whitelist `board_viewer_repos` в `auth.toml` — выбран как явный и контролируемый.
- [x] Без кэширования на MVP — Gitea на том же VDS, latency negligible.
## Completed steps
- [x] парсер STATUS.md (`src/parser.ts`) с маппингом ⚪🔴🟡🔵🟢 → open/in_progress/paused/blocked/done
- [x] Gitea HTTP client (`src/gitea.ts`): getFile / getLatestCommitIso / rawUrl, DI fetch
- [x] reader orchestrator (`src/reader.ts`): объединяет parser+client → TaskRecord[]
- [x] TOML config loader (`src/config.ts`) с whitelist `board_viewer_repos`
- [x] 25 тестов: 9 парсера (включая 3 real-repo фикстуры), 7 gitea, 5 reader, 4 config
- [x] typecheck clean
## Notes
NB: исходный буфер упоминал `mcp__projects-meta__tasks_aggregate` как возможный источник — **это ложная развилка**, MCP недоступен HTTP-сервису. Только прямой Gitea API. См. `.wiki/concepts/board-viewer.md` секцию «Implementation note».