# 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.4] - 2026-08-17
### Added
- `sync user <login>` and user coverage in `sync all`. `monitor add-user` has
registered user sources since 0.1.0 and `monitor list` has shown them, but
sync only ever served repositories — so a monitored user's `last_sync_at`
never moved and no command could answer "what did this person do". This
release makes sync serve them: for each enabled user it searches the pull
requests they authored (`author:`) and the ones they reviewed
(`reviewed-by:`), and upserts every result through the same entity, fact, and
time-dimension pipeline `sync repo` uses. A PR reachable from both paths lands
identically either way.
- Coverage, locking, and job records for user sources under the `user:<login>`
entity key, so `sync watch`, stale-lock recovery, and the recorded date-range
merge all behave as they do for repositories. The two roles keep separate
`updated_at` watermarks (`user` and `user_reviews` in `sync_metadata`) because
they walk disjoint result sets — one shared cursor would let the busier side
advance past items the other has not fetched. As on the repository path, a
watermark only ever moves past an item that actually persisted, so a failed
upsert is retried on the next run instead of becoming a permanent hole.
- Honest handling of the two limits search imposes. GitHub serves at most 1,000
results per query however many it reports matching, so a backfill is
partitioned into calendar-month `created:` windows and any window still
reporting more than the cap is bisected until each part fits — the over-cap
query is abandoned before its results are spent, not truncated silently. When
a window is already a single day and still over the cap, the run reports the
shortfall rather than recording coverage it did not fetch. An incremental run
is bounded by its watermark instead of by the window, so there the cap is only
a problem if pagination runs out before the watermark is reached: that case is
reported with the `--days` command that closes the gap, while a walk that
reaches its watermark stays quiet no matter how many lifetime matches the
query reports. Search's request budget is 30/minute, an order of magnitude
tighter than the 5,000 points/hour the repository path spends, so search calls
are paced on their own clock independent of the observed `rateLimit` block.
### Changed
- `sync all` now iterates every supported source type — repositories first, then
users — and prints a line for each enabled source it will not serve. A source
type this build does not implement, and a row that is present but disabled,
are both named on stdout. Previously the source list was filtered to
repositories and everything else disappeared without a word, which read as
"there was nothing to do".
- `sync user` covers pull requests and reviews. `--issues-only` has no
user-scoped counterpart in this release; passing it reports that instead of
returning zero, which would read as "this user has no issues".
- Repositories discovered through a user sync are stored as dimensions but are
**not** added to `monitored_repos`. Monitoring a person is a statement about
that person, not an instruction to start tracking every repository they
contributed to.
## [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