Skip to content

Daily Activity: automated end-of-day GitHub + Slack summary log#28

Open
therzka wants to merge 25 commits into
mainfrom
feature/daily-activity-log
Open

Daily Activity: automated end-of-day GitHub + Slack summary log#28
therzka wants to merge 25 commits into
mainfrom
feature/daily-activity-log

Conversation

@therzka

@therzka therzka commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

Adds an automated "Daily Activity" feature that appends an idempotent markdown block to each day's work log, combining:

  • GitHub activity — issues opened/closed, PRs opened/merged/closed, PR reviews (approved/changes-requested/commented), comments on issues and PRs, and commits. Uses the per-user GitHub Events API (GET /users/{username}/events), filtered to configured orgs and ignored repos, with a visible warning if GitHub's ~300-event pagination cap is hit before the full day is confirmed covered.
  • Slack activity — a per-channel, AI-generated narrative summary of the day's Slack messages (via gh-slack), with blank-line separation between channels and a permalink back to each channel's activity.

Triggering:

  • A "Daily activity" button in the work-log panel for on-demand generation.
  • An hourly scheduler check that auto-generates the log in the evening (configurable via dailyActivityHour), with an in-flight guard to avoid duplicate/overlapping runs.

Notable fixes along the way

  • Corrected GitHub's UTC-only search date qualifiers so activity is attributed to the correct Pacific-time calendar day.
  • Discovered and fixed a critical bug where the org-scoped Events API endpoint actually returns other org members' activity, not the authenticated user's own — switched to the correct per-user endpoint.
  • Tightened the pagination-cap detection so a genuine early-page 422 (e.g. auth failure) is not mistakenly swallowed as "truncated."
  • Replaced an unreliable Copilot-SDK/MCP-based Slack search with a deterministic gh-slack API call, keeping AI only for summarization (not tool use).

Test Plan

  • npm run build (includes TypeScript type check) passes clean
  • npm run lint passes clean
  • Live end-to-end verification against real data for multiple dates (today, yesterday), confirming correct GitHub activity scoping (no false positives from other users), correct Slack channel summaries with links, and correct truncation-warning behavior
  • Manual review of generated log output on a few real days once merged

therzka and others added 25 commits July 1, 2026 15:27
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevents a fully-failed org from being masked by another org's
empty-success result, which would clobber a good log block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…k the daily log

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…PI route; cap hourly retries

The API route's in-flight guard was module-local and never actually shared
with the scheduler, so a scheduled run and a manual trigger for the same
date could still race and clobber each other's write. Move the guard (keyed
by date) into generateDailyActivity() itself so every caller coalesces on
it. Also cap the scheduler's retry-on-failure to 3 attempts per day so a
persistent failure doesn't re-run the Slack call and git commit every hour.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A single-slot guard could be overwritten by a concurrent call for a
different date, causing it to stop protecting the original date's
still-running generation. A small Map keyed by date fixes this while
staying simple.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The daily-activity API call can take up to ~5 minutes. If the user
navigates to a different day while it's in flight, the server has
already written the correct day's log; skip the client-side merge/save
if currentDate no longer matches the date the request was made for, so
we never write mixed-day content back to disk. Also clear any pending
debounced autosave before the merge-save so a stale autosave can't land
afterward and overwrite the merged content.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…alue

The prior fix compared currentDate against itself within the same async
closure (always false — currentDate is a plain const frozen at click
time, so the guard never fired). Track the live date in a ref, updated
via effect, and compare against that instead so mid-request navigation
is actually detected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The copilot-slack-mcp plugin approach was unreliable: the SDK session
sometimes lacked slack_* tools entirely, and even when nominally
available, the model failed to find real public Slack activity that
existed on the test day (#world-cup-2026 messages were missed).

Replace the LLM-driven search with a deterministic call to
`gh slack api get search.messages -f query="from:me on:<date>"`,
filtered client-side to public channels and to messages whose
timestamp genuinely falls on the target date (America/Los_Angeles).
The AI model is now only used to summarize the already-gathered facts
per channel, not to search for them, which is both faster and far
more accurate.

Also:
- Show each commit's message as a sub-bullet in the GitHub section
  (previously only the SHA link was shown, grouped per repo)
- Fix missing blank line after the "_Summary_" heading
- Add SLACK_TEAM env var (falls back to first GITHUB_ORG entry) and
  update README prerequisites/env docs to describe the gh-slack setup
  instead of the abandoned copilot-slack-mcp plugin

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirrors the logging already done in the catch block for install/exec
failures, so a mis-scoped token or expired auth is distinguishable
from genuinely having no Slack activity that day.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ity key

Final full-branch review caught two issues:

Critical: GitHub's search date qualifiers (created:, author-date:) are
always interpreted in UTC, but the app's "date" is always a PT calendar
day. Passing the bare PT date string into these UTC-interpreted
qualifiers silently misattributed GitHub activity created roughly
4-8pm PT onward to the wrong day - exactly the window most likely to
contain real work by the time the evening auto-trigger runs. Fixed by
computing the explicit UTC instant range for the PT calendar day
(handling both PST/PDT offsets) and using GitHub's range query syntax
(created:start..end) instead. Also fixed the same bug in
fetchAgentActivity's date comparison, which naively sliced a UTC ISO
timestamp instead of converting it to the PT calendar date.

Minor: DAILY_ACTIVITY_KEY was duplicated as two independent string
literals (client and server) to avoid pulling server-only deps into
the client bundle. Moved it into the existing shared, dependency-free
constants.ts module instead, so both sides can never drift out of
sync on the managed-block marker.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per user feedback: the per-channel Slack summary now has a blank line
between each channel's entry for readability, and each entry links back
to that channel's first matching message so you can jump to the
original context. Post-processed deterministically after the model's
response rather than relying on prompt instructions, since models
don't reliably keep blank lines/links in free-form output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…erges

Replace the three Search-API-based queries (issues created, PRs created,
commits authored) with a single call to GitHub's per-user Events API
(`GET /users/{username}/events`), which captures far more of "my GitHub
activity": issues/PRs opened and closed, PRs merged, PR reviews given
(approved / requested changes / commented), and comments left on issues
and PRs — not just things I created.

Important correctness fix during implementation: the org-scoped events
endpoint (`/users/{username}/events/orgs/{org}`) initially used for this
turned out to be the user's *organization dashboard* feed — i.e. other
org members' activity, not events performed by this user (verified
empirically: 0/100 sampled events had actor.login matching the
authenticated user). Switched to the plain `/users/{username}/events`
endpoint, which correctly scopes to the authenticated user's own
actions, then filter results client-side to configured orgs
(GITHUB_ORG) and ignored repos.

Also added:
- Defensive handling for GitHub's ~300-event/3-page hard cap on this
  endpoint: pagination now stops gracefully (keeping partial results)
  instead of raising an uncaught error, and surfaces a visible
  "⚠️ ... may be missing" note in the log if the cap was hit before
  confirming full day coverage, rather than silently showing "no
  activity".
- PushEvent-based commit collection, deduped by SHA and filtered to
  `distinct: true` commits to avoid double-counting on force-pushes.

Updated `renderGitHubList` to render all the new activity types
(reviews grouped with human-readable state labels, comments grouped
by repo like commits already were), and updated README/CHANGELOG to
describe the expanded scope.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Code review caught that the previous condition (status===422 && page>1)
would swallow a 422 as young as page 2, which per the code's own stated
intent should instead propagate as a hard failure (auth error, etc.).
GitHub's ~300-event cap on this endpoint is only reachable once page 4+
is requested with per_page=100. Gate the swallow condition on having
already fetched >= 300 events (computed from page/PAGE_SIZE, so it
stays correct if PAGE_SIZE ever changes) instead of a hardcoded page
number.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@therzka
therzka force-pushed the feature/daily-activity-log branch from d5dc1b6 to 7ad0cf3 Compare July 1, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant