Files
buzz/desktop/src/features/projects/lib/projectsActivityDigest.ts
thomaspblock de188ebf06 feat(projects): add agent and CLI project-home support (#6590)
## Summary
- inject bounded project-home identity and repository context into
managed agent sessions
- add project-aware CLI flows for creating projects, repositories,
issues, and related channels
- match project homes through the existing relay query surface, then
filter channel metadata client-side

This is Part 1 of the channel-first Projects stack. Part 2 contains
project creation and model foundations.

## Testing
- `cargo fmt --all -- --check`
- `cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings`
- `cargo test -p buzz-cli -p buzz-acp` — 1,184 tests passed, 1 doc test
ignored
- full pre-push gate passed

## Post-Deploy Monitoring & Validation
- validate project-home agent context and project-aware CLI commands
against a staging relay
- healthy signals: project context matches the active channel, explicit
repo coordinates remain stable, and normal channels receive no project
block
- failure signals: cross-channel project context, duplicate project
creation, or commands targeting an unrelated repository; mitigate by
reverting this PR

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz>
2026-08-26 12:09:06 -07:00

89 lines
2.5 KiB
TypeScript

import type {
Project,
ProjectActivitySummary,
ProjectIssueListItem,
ProjectPullRequestListItem,
ProjectRepoSnapshot,
} from "@/features/projects/hooks";
const WEEK_SECONDS = 7 * 24 * 60 * 60;
export type ProjectsActivityDigest = {
highlights: string[];
prefix: string;
suffix: string;
};
function plural(count: number, singular: string, pluralForm = `${singular}s`) {
return `${count} ${count === 1 ? singular : pluralForm}`;
}
/** Builds a short, deterministic sentence from the currently loaded activity. */
export function buildProjectsActivityDigest({
issues,
nowSeconds,
projects,
pullRequests,
snapshots,
summaries,
}: {
issues: ProjectIssueListItem[];
nowSeconds: number;
projects: Project[];
pullRequests: ProjectPullRequestListItem[];
snapshots?: Record<string, ProjectRepoSnapshot>;
summaries?: Record<string, ProjectActivitySummary>;
}): ProjectsActivityDigest {
const since = nowSeconds - WEEK_SECONDS;
const activeProjectIds = new Set<string>();
let commitCount = 0;
for (const [projectId, snapshot] of Object.entries(snapshots ?? {})) {
const recent = snapshot.commits.filter(
(commit) => commit.timestamp >= since,
).length;
commitCount += recent;
if (recent > 0) activeProjectIds.add(projectId);
}
const taskCount = issues.filter(({ issue, project }) => {
const recent = issue.createdAt >= since;
if (recent) activeProjectIds.add(project.id);
return recent;
}).length;
const reviewCount = pullRequests.filter(({ project, pullRequest }) => {
const recent = pullRequest.createdAt >= since;
if (recent) activeProjectIds.add(project.id);
return recent;
}).length;
const highlights = [
commitCount > 0 ? `${plural(commitCount, "new commit")}` : null,
taskCount > 0 ? `${plural(taskCount, "task")} opened` : null,
reviewCount > 0 ? `${plural(reviewCount, "review")} opened` : null,
].filter((value): value is string => value !== null);
if (highlights.length > 0) {
highlights.push(`${plural(activeProjectIds.size, "active project")}`);
return {
highlights,
prefix: "This week:",
suffix: ".",
};
}
const totals = Object.values(summaries ?? {}).reduce(
(result, summary) => ({
reviews: result.reviews + summary.prCount,
tasks: result.tasks + summary.issueCount,
}),
{ reviews: 0, tasks: 0 },
);
return {
highlights: [
plural(projects.length, "project"),
plural(totals.tasks, "task"),
plural(totals.reviews, "review"),
],
prefix: "Currently tracking",
suffix: ".",
};
}