P1-5: lifecycle inference from the issue timeline (#5) #39

Merged
christian merged 2 commits from p1-5/lifecycle-inference into p2/monte-carlo 2026-07-08 23:41:26 +00:00
Owner

Stacked on #38#37. Retarget down the chain as each lands.

What

The board's Steeping and In-review columns were always empty (v0 could only place three columns honestly). This reads the real gitea issue timeline and fills all five — and derives the estimate-vs-actual working time the calibration fit (D3) will learn from.

Core (@commitea/core)

  • inferLifecycle(issue, events, asOf) — five-column inference: closed → done; open PR ref → review; commit ref → steeping; any triage signal (label/milestone/assign, or a current label) → triage; else diagnosis. Earliest event of each kind fixes the stage timestamp.
  • Derives actualWorkingDays (work-start → close — the actual calibration learns from) and steepingDays (first commit → now) for the age badge.
  • workingDaysBetween() — whole Mon–Fri days in [start, end), day-granular.
  • normalizeTimeline() + client.getIssueTimeline() — map gitea's raw timeline (label/milestone→triage, commit_ref→commit, pull_ref→pull, close, reopen), drop the rest. Paginated.

App

  • reconcile fetches every issue's timeline, returns it keyed by number; threaded through the bridge → useBacklog → board/focus.
  • issuesToBoardColumns + scheduleFocus run inferLifecycle: real Steeping/In-review columns, steeping-age days badge, focus-card steeping badge.

Live result

Against the real repo: Diagnosis 0 · Triage 17 · Steeping 0 · In-review 8 · Done 9. Done holds merged work; In-review holds issues with open PRs (#9 scheduler, #10 Monte Carlo — the live PRs).

Known refinements (later)

  • gitea's pull_ref fires on any PR mention, so an issue merely referenced in a PR body can read as In-review; distinguishing a closing ref from a mention needs the PR link's state.
  • Re-opening → multi-segment actuals deferred (v0 takes earliest work-start → close).

Verified

63 core tests green (15 lifecycle: workingDaysBetween + the five transitions + earliest-event + fallback) · desktop typecheck clean · 14 fixture e2e green · live spec asserts Done is populated from real events.

🤖 Generated with Claude Code

Stacked on #38 → #37. Retarget down the chain as each lands. ## What The board's **Steeping** and **In-review** columns were always empty (v0 could only place three columns honestly). This reads the real **gitea issue timeline** and fills all five — and derives the estimate-vs-actual working time the calibration fit (D3) will learn from. ## Core (`@commitea/core`) - **`inferLifecycle(issue, events, asOf)`** — five-column inference: closed → *done*; open PR ref → *review*; commit ref → *steeping*; any triage signal (label/milestone/assign, or a current label) → *triage*; else *diagnosis*. Earliest event of each kind fixes the stage timestamp. - Derives **`actualWorkingDays`** (work-start → close — the actual calibration learns from) and **`steepingDays`** (first commit → now) for the age badge. - **`workingDaysBetween()`** — whole Mon–Fri days in [start, end), day-granular. - **`normalizeTimeline()`** + **`client.getIssueTimeline()`** — map gitea's raw timeline (label/milestone→triage, commit_ref→commit, pull_ref→pull, close, reopen), drop the rest. Paginated. ## App - `reconcile` fetches every issue's timeline, returns it keyed by number; threaded through the bridge → `useBacklog` → board/focus. - `issuesToBoardColumns` + `scheduleFocus` run `inferLifecycle`: real Steeping/In-review columns, steeping-age `days` badge, focus-card steeping badge. ## Live result Against the real repo: **Diagnosis 0 · Triage 17 · Steeping 0 · In-review 8 · Done 9**. Done holds merged work; In-review holds issues with open PRs (#9 scheduler, #10 Monte Carlo — the live PRs). ## Known refinements (later) - gitea's `pull_ref` fires on **any** PR mention, so an issue merely referenced in a PR body can read as In-review; distinguishing a *closing* ref from a mention needs the PR link's state. - Re-opening → multi-segment actuals deferred (v0 takes earliest work-start → close). ## Verified 63 core tests green (15 lifecycle: `workingDaysBetween` + the five transitions + earliest-event + fallback) · desktop typecheck clean · 14 fixture e2e green · live spec asserts Done is populated from real events. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
christian added 1 commit 2026-07-08 23:32:24 +00:00
Fill the board's Steeping / In-review columns (and the calibration actuals)
from real gitea timeline events, replacing the three-column-only v0.

core (@commitea/core):
- inferLifecycle(issue, events, asOf): five-column inference — closed → done;
  open PR ref → review; commit ref → steeping; any triage signal → triage;
  else diagnosis. Earliest event of each kind fixes the stage timestamp.
- Derives actualWorkingDays (work-start → close) — the estimate-vs-actual the
  calibration fit (D3) learns from — and steepingDays (first commit → now) for
  the board age badge.
- workingDaysBetween(): whole Mon–Fri days in [start, end), day-granular.
- normalizeTimeline() + client.getIssueTimeline(): map gitea's raw timeline
  (label/milestone → triage, commit_ref → commit, pull_ref → pull, close,
  reopen), drop the rest. Paginated.

app:
- reconcile now fetches every issue's timeline and returns it keyed by number;
  threaded through the bridge → useBacklog → board/focus.
- issuesToBoardColumns + scheduleFocus run inferLifecycle: real Steeping/In-review
  columns, steeping-age `days` badge, focus-card steeping badge.

Known refinement: gitea's pull_ref fires on any PR mention, so an issue merely
referenced in a PR body can read as In-review; distinguishing closing refs from
mentions needs the PR link's state (later). Re-opening multi-segment actuals
also deferred.

Verified: 63 core tests green (15 lifecycle, incl. workingDaysBetween + the five
transitions), desktop typecheck clean, 14 fixture e2e green, live spec asserts
the board's Done column is populated from real events.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
christian added 1 commit 2026-07-08 23:35:37 +00:00
christian merged commit b95bb866c6 into p2/monte-carlo 2026-07-08 23:41:26 +00:00
Sign in to join this conversation.
No description provided.