githubdw 0.2.3

Local SQLite data warehouse for GitHub repositories: sync PRs, reviews, and issues; query, metrics, fulltext search, and MCP server
Documentation
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.2.3] - 2026-08-16

### Fixed

- Every command that names an author printed the warehouse's internal entity
  key (`user:octocat`, `bot:dependabot`) instead of the login GitHub uses.
  `query` in all four modes (table, json, csv, count-by-author), `search
  --json`, the `metrics user` header, and the `query_pull_requests`, `search`
  and `get_metrics` MCP tools were all affected, so the only githubdw output
  that showed a usable login was the metrics leaderboards. A value copied off
  any other surface could not be pasted into a GitHub URL or API call without
  knowing to strip a namespace first, and the field carrying it was already
  named `author` or `login` — the bare spelling the schema itself uses for
  `dim_entities.login`. Identity fields now hold the bare login everywhere, and
  the type that the namespace used to convey travels in its own field:
  `author_type` on PR rows and search hits, `entity_type` on a user report. The
  key remains available where a caller genuinely needs to join back to the star
  schema, under a name that says so: `author_key` on PR rows and `entity_key`
  on a user report.
- A single display column could not tell a bot from the person who shares its
  login, since dropping the namespace drops that distinction. Bots are now
  marked visibly instead — `dependabot [bot]` in table output, in
  `count-by-author` group labels, and in the `metrics user` header — so the two
  identities stay distinguishable and remain separate groups in a breakdown.
- A leaderboard row for an entity with no display name fell back to printing
  its raw key. It now falls back to the bare login, with the same bot marker.

### Added

- Schema v4 constrains `dim_entities` to the identity invariants the ingest
  code already upholds: `login` is non-empty and never namespaced,
  `entity_key` is exactly `entity_type` + `:` + `login`, and `entity_type` is
  one of the namespaces ingestion mints. These were enforced by code alone, so
  a writer that bypassed the normalizer could store a row that no bare-login
  lookup would ever match while the write reported success. The upgrade is a
  no-op on a warehouse whose rows already conform, and refuses to complete on
  one that carries a violating row rather than migrating the problem forward.

### Changed

- The `csv` output of `query` gained an `author_type` column, so the bare
  author and its type are both present without the namespaced key. Callers
  parsing CSV by column position need to account for the new column.

## [0.2.2] - 2026-08-16

### Fixed

- Relative periods (`this-week`, `this-month`, `this-quarter`, `this-year`,
  `previous-*`, `last-N`) resolved against the UTC calendar date while every
  `*_date_key` column in the warehouse is built in the configured IANA
  timezone. During the hours when the two calendars disagree — up to 14 a day,
  7 for a US Pacific warehouse — this shifted every window by one day: a
  rolling window silently included a day still in progress and dropped a
  complete one, and at a quarter, month, or year boundary the wrong period was
  reported entirely (asking for `this-quarter` on the last evening of a quarter
  answered about the next one). "Today" is now resolved in the warehouse's own
  zone, DST-aware, everywhere it is used: period parsing in the CLI and MCP
  server, and `MetricsEngine`'s reference date, which drives partial-period
  truncation for period-over-period deltas. Explicit `--since` / `--until`
  dates are unchanged: a bare `YYYY-MM-DD` a person types already means their
  local day, which is the calendar the keys use.
- `synced_ranges` recorded coverage through the day that was still in
  progress, and computed that day in UTC. A range that claims a running day is
  a claim the data does not support: an item created later that same local day
  falls inside a window recorded as fully covered, so any reader that trusts
  the record would never fetch it. Coverage now ends at the last *completed*
  day in the configured zone, and is clamped again on read — so a database
  written by an earlier version is interpreted honestly, with the trailing day
  resurfacing as a gap rather than being silently skipped. No migration is
  needed; the repair happens in place on the next write.
- The incremental sync cursor stopped at `updated_at <= cursor`, which
  permanently dropped an item updated in the *same second* as the cursor but
  served after the previous run's last page. Only a further update — which
  moves its timestamp — could ever surface it again. The stop is now strict, so
  the boundary second is re-read; re-ingesting it costs nothing because the
  upserts are idempotent.
- The cursor advanced past items whose upsert *failed*, moving the watermark
  over data the warehouse does not hold. The next run's stop condition then
  fired before reaching the item, turning a single reported failure into a
  permanent hole with no way to self-heal. The cursor now advances only past
  items that actually persisted, so a failure is retried on the following run.

### Changed

- `Period::parse` is deprecated: it resolves relative periods against the UTC
  date, which is only correct for a warehouse configured to UTC. Use
  `Period::parse_in_zone` / `Period::parse_as_of`, or
  `Period::parse_with_reference` with `storage::time_dimension::today`.
- `--days` on `sync` is documented for what it actually does: it sets the
  recorded coverage window, not a bound on the fetch. The fetch is governed by
  the incremental cursor and pages until the source is exhausted.

### Added

- `storage::time_dimension::today` / `today_as_of` / `last_complete_day` /
  `last_complete_day_as_of` / `local_date_for`: the one rule relating an
  instant to a `date_key`, matching how stored facts are keyed.
- Instant-anchored twins of the coverage functions
  (`record_range_as_of`, `gaps_as_of`, `coverage_extent_as_of`) and
  `Syncer::as_of`, so completeness arithmetic is deterministic under test
  instead of reading the wall clock.
- `MetricsEngine::reference_date` accessor.

## [0.2.1] - 2026-08-15

### Fixed

- Restored the git hash in `--version` output. The 0.2.0 release notes
  document this feature, but the shipped 0.2.0 artifacts do not contain
  it: the summaries PR was squashed against a stale base and silently
  reverted it. `githubdw --version` once again prints
  `githubdw <version> (<short-hash>)`.

### Changed

- Linux release binaries are now statically linked (musl targets). The
  0.2.0 gnu binaries required glibc >= 2.39 and failed to load on older
  hosts; the musl binaries have no runtime library requirements.

## [0.2.0] - 2026-08-15

### Added

- Opt-in AI summaries for pull requests (`--features summaries`): agentic
  single-PR summarization storing a headline, what-changed / why-it-matters
  prose, a 0-10 notability score, and change-type / impact-area / complexity
  classification in a new `pr_summaries` table (migration 003). Ships two
  agent tools: `query_database` (read-only SQL, enforced at the prepared-
  statement level via `sqlite3_stmt_readonly`, with row caps applied during
  collection) and `write_pr_summary` (upsert that preserves `created_at`).
  Default builds compile none of the LLM dependencies.
- `githubdw --version` now embeds the build's short git hash, e.g.
  `githubdw 0.2.0 (03574c8)`, so installed binaries are auditable across
  machines. Builds outside a git checkout (crates.io tarballs) report
  `(unknown)`.
- Tagged releases now build and attach binary tarballs with sha256 checksums
  for Linux x86_64 / aarch64 and macOS arm64 / x86_64.

### Changed

- MCP server pinned to rmcp 0.12 with a hand-written tool router, keeping the
  crate resolvable from vendored and mirrored crates.io registries.

## [0.1.0] - 2026-07-24

### Added

- Star-schema SQLite warehouse: conformed date/time/entity/repository
  dimensions, PR-grain facts (reviews, review comments, conversation comments,
  check runs, file diffs), and issue tables (labels, milestones, assignees)
- Incremental, resumable sync via the `gh` CLI: synced-range merging with gap
  detection, `updated_at` cursor early-stop, stale-lock crash recovery, live
  `sync watch` progress, adaptive GraphQL page-size degradation for very
  large repositories
- Fluent `QueryBuilder` with author/reviewer/repo/org/group/state/label/period
  filters and table/CSV/JSON/count/count-by-* outputs
- Metrics engine: period-over-period deltas (user, repo, custom groups) with
  apples-to-apples partial-period truncation and ranked leaderboards with
  rank-movement tracking
- Fulltext search (SQLite FTS5, trigram tokenizer) over PR/issue titles,
  bodies, and comments with highlighted snippets
- MCP stdio server with five tools: `query_pull_requests`, `get_metrics`,
  `search`, `manage_monitors`, `trigger_sync`
- Custom user and repository groups
- Configurable timezone-aware date bucketing (DST-correct)

[Unreleased]: https://github.com/adlio/githubdw/compare/v0.2.3...HEAD
[0.2.3]: https://github.com/adlio/githubdw/releases/tag/v0.2.3
[0.2.2]: https://github.com/adlio/githubdw/releases/tag/v0.2.2
[0.1.0]: https://github.com/adlio/githubdw/releases/tag/v0.1.0