Add bw recap: unblocked events, recap parser, tree/JSON renderers, cross-repo fan-out#122
Merged
Conversation
f1c0bb6 to
142e597
Compare
3 tasks
jallum
pushed a commit
that referenced
this pull request
May 7, 2026
## Summary
CI is currently red on `main` because `cmd/bw` fails the `staticcheck`
step. This PR makes it green.
- **Compile error fix.** `prime_test.go`'s two newer subtests still
called the pre-config `cmdPrime` signature. Updated both to match
the current `(*issue.Store, []string, Writer, *config.Config)
(*config.Config, error)` shape used by every other call site.
- **Drop five U1000 helpers** that have no production callers, plus
the tests written solely to exercise them:
- `(*Command).valueFlags` — never had a caller; commands all pass
hardcoded flag lists straight to `ParseArgs`
- `validateDate` — replaced by `resolveDateNow` in #99
- `toRFC3339Date` / `fromRFC3339Date` — `bd`-compat padding removed
in #112 when `defer_until` / `due` switched to native YYYY-MM-DD
- `relativeTime` (string wrapper) — only the pre-rewrite recap
called it; the re-land in #122 uses `relativeTimeSince` directly
with parsed `time.Time`
- **Inline three `validateDate` checks** in real defer-behavior tests
(`TestCmdDeferRelativeDate`, `…Tomorrow`, `…NextMonday`) with
`time.Parse("2006-01-02", ...)`.
- **Drop now-unused `"strings"` import** in `export.go`.
- **Keep `relativeTimeSince` (and its test)** behind
`//lint:ignore U1000` referencing #122, which wires it back into
`bw recap`. Once #122 lands, that directive can be removed.
Net: **+8 / −184** across 9 files, all in `cmd/bw/`.
## Test plan
- [x] `go build ./...`
- [x] `staticcheck ./...` (clean)
- [x] `go test ./...` (full suite passes)
409d3e0 to
050fc7f
Compare
Host-local JSON store tracking beadwork-enabled repos: - Load/Save with atomic temp-file rename for safe concurrent writes - Touch/TouchAndSave for registering repos with timestamps - AdvanceCursorAndSave for incremental recap cursors - Prune for removing stale entries by predicate - Unknown-field preservation across load/save cycles - Schema version check refusing newer-than-supported - Home resolution: BEADWORK_HOME > ~/.beadwork (os.UserHomeDir honors $HOME) - CanonicalRepoPath resolving worktrees to their main repo root Also adds test scaffolding used throughout the series: - newBwEnv() sets BEADWORK_HOME to an isolated temp dir - newMultiRepoEnv(t, n) creates n repos sharing a registry dir - seedRegistry() / registryContents() for registry state setup/assertion - compareGolden(t, name, got) generalized golden-file comparison - bwNow() returns the deterministic clock as time.Time - bwFail() for testing expected-error paths
Auto-registration: - touchRegistry() runs after every successful command, registering the current repo in the host-local registry - Resolves via FindRepoAt (not NeedsStore) so it fires for all commands - Uses CanonicalRepoPath to deduplicate worktrees to their main repo - Gated on IsInitialized() so non-beadwork git repos aren't registered - Respects BW_CLOCK for deterministic timestamps in tests - Silently ignores errors (registryErrorOnce warns once on stderr) - BEADWORK_HOME env var isolates tests from the real registry bw registry subcommand: - list: TTY + plain + JSON modes, MISSING flag for deleted repos, live prefix read from repo, sorted by path - prune: --yes/-y for non-interactive, TTY stdin check, half-removal warning when >50% of entries would be removed - Appears in bw --help under Cross-Repo & Activity - Nested dispatch routes registry <sub> with --help support CLI tests (cli_test.go) now set BEADWORK_HOME to t.TempDir() via a shared bwTestEnv() helper, so shelling out the bw binary doesn't pollute the user's real registry.
The registry should be a path+timestamp index, not a cache of repo metadata. Always read the prefix from .bwconfig via the repo package to eliminate drift between the two sources.
Replace the JSON-based registry (schema version, Entry structs with timestamps/cursors) with a simple text file containing one repo path per line. The registry is now a pure path index — no metadata caching. - DefaultPath() returns ~/.bw (or $BEADWORK_HOME) as a file, not dir - Registry API: Load/Save/Add/Remove/Paths - touchRegistry() just calls reg.Add(path) - All tests updated for new plain-text format
Matches the BW_CLOCK convention and accurately describes what the env var points to — a registry file, not a home directory.
CanonicalRepoPath duplicated the worktree→main-repo resolution already done by repo.FindRepoAt. Since touchRegistry already has a *Repo from FindRepoAt, r.RepoDir() gives the canonical root directly.
Move registry.repos into the YAML config file, eliminating the internal/registry package entirely. Auto-registration writes to config; registry list/prune read from config. Save failures from auto-registration are now silently ignored so commands still run when the config path is unwritable.
Reintroduce internal/registry as a thin layer over config providing Paths, Repos (with live prefix lookup), Resolve (prefix→path), and Register (immutable add). autoRegister and registry commands now use these helpers, giving the upcoming recap --all a clean API to fan out across registered repos.
Auto-registration now only fires when registry.auto is true in the global config, and reuses the repo already resolved by the store instead of looking it up again.
Stamp "unblocked <id>" lines into the close commit message for each newly unblocked dependent, and expose them via bw history. This feeds the recap command's activity view. - Close path scans ready-state transitions and writes unblocked events atomically with the close transition - Retry on ref-update contention (shared beadwork branch can race) - history command renders unblocked events alongside other events - Distinguishes "unblocked" text in a reason from a real stamped event
internal/recap package: - Data model for per-issue activity windows (events + metadata) - Parser reading beadwork commit log for transitions - Window resolution for today/yesterday/since-last/--hours bw recap command: - Single-repo: tree renderer (grouped by issue) and --json renderer - --all: cross-repo fan-out across the registry, aggregating by repo - Registered under "Cross-Repo & Activity" in bw --help
- Condensed default view with duration tokens (15m, 1h, 3h30m, 2d, 1w) - Colorize both condensed and verbose tree renderers - Fix incremental recap (since-last) window bookkeeping - Truncate long titles; suppress TTY hints when piped - Stamp last_recap_at on each run so the "since last recap" label updates monotonically - Honor local timezone for today/yesterday windows
Explicit-window recaps (--since / today / 1h / etc.) previously stamped the cursor to HEAD via AllCommits, even when the window started after the current cursor. Commits between cursor_time and window.Start — never rendered — would be marked seen and permanently skipped by future implicit recaps. Now: when the window starts after the cursor, the recap renders the window as before but neither `cursor` nor `last_recap_at` is stamped, and a stderr notice reports the count of unrendered gap commits. No-gap explicit recaps and bare cursor-driven recaps keep their existing stamping behavior. --dry-run still skips all stamping. See .spec/decisions/recap-explicit-window-conditional-advance.md.
containsStr and contains were orphaned when "Record unblocked events" swapped the CAS conflict assertion to errors.Is(err, ErrRefMoved). Staticcheck flags them as U1000.
Store the recap cursor as a local git ref instead of in the registry. The ref file's mtime provides LastRecapAt. This keeps per-user recap state local to each repo clone (never replicated via push/fetch). - Add Repo.RecapCursor/SetRecapCursor/LastRecapAt methods - Rewrite recap.go to use repo-based cursor instead of registry - Update acceptance tests to check git ref instead of registry JSON
registryContents/registryDir/BEADWORK_HOME were artifacts of the old file-based registry; the new design stores repos in BW_CONFIG (YAML) and the recap cursor in refs/beadwork/recap-cursor. - TestRecapDryRun: check git ref instead of registry file contents - TestRecapTodayLocalTimezone: BW_CONFIG= replaces BEADWORK_HOME= - TestRecapNotInRepo: remove registryDir field, add isolated BW_CONFIG - TestRecapAllWarnsOnMissing: seedRegistry() replaces file write
050fc7f to
ee8cdd5
Compare
3fee8ff to
756a217
Compare
All conflicts were additive: recap command registration, bwNow() helper, and recap/close-unblocked test helpers and test cases added in the PR vs. absent in main.
The "since last recap (Xh ago)" header derives from os.Stat ModTime of the cursor ref file. Before this change, mtime only moved when SetRecapCursor wrote a new hash, so a no-event recap left the timestamp unchanged and the header lagged across quiet stretches: Mon 09:00 bw recap → "since last recap (24h ago)" Tue 09:00 bw recap → "no activity" (mtime untouched) Wed 09:00 bw recap → "since last recap (2d ago)" Add TouchRecapCursor (os.Chtimes-only) and call it from runRecapSingle and cmdRecapAll when the recap completes without new commits and a cursor already exists. The gap-window early-return still skips both SetRecapCursor and TouchRecapCursor, preserving the gapped-explicit behavior from ac817d5. Update TestRecapStampsLastRecapAtWithNoCommits to actually assert what its name claims: backdate the cursor mtime via os.Chtimes, run a no-event recap, verify mtime advances. Sanity-checked: removing the fix makes the test fail with "cursor mtime did not advance".
iautom8things
approved these changes
May 8, 2026
iautom8things
left a comment
Collaborator
There was a problem hiding this comment.
Added a fix for 'last recap' tracking. Ready for your approval @jallum 🚀 🚀 🚀 when ready
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
bw recapshows recent beadwork activity. Cursor-driven incremental by default (24h backfill on first run), with explicit window support (recap today,--since). Gap detection refuses to advance the cursor when an explicit window would skip unseen commits.--allfans out across registered repos. Cursor lives in a local git ref (refs/beadwork/recap-cursor).