mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
## 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>
188 lines
5.6 KiB
JavaScript
188 lines
5.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
fetchProjectsWorkItems,
|
|
projectsWithWorkItemRepositories,
|
|
} from "./projectWorkItems.ts";
|
|
|
|
// ── Work-item deduplication ─────────────────────────────────────────────────
|
|
//
|
|
// NIP-MP §Multiple membership: a repository may belong to several projects.
|
|
// When it does, global issue/PR lists must produce exactly one row per work
|
|
// item — not one row per project membership. These tests call the exported
|
|
// production function with a stubbed fetchEvents to verify the dedup contract
|
|
// end-to-end, not just the filter algorithm in isolation.
|
|
|
|
const REPO_OWNER = "a".repeat(64);
|
|
const REPO_DTAG = "relay";
|
|
const REPO_ADDRESS = `30617:${REPO_OWNER}:${REPO_DTAG}`;
|
|
|
|
const ISSUE_ID = "i".repeat(64);
|
|
const PR_ID = "p".repeat(64);
|
|
const PR_ID_2 = "q".repeat(64);
|
|
|
|
// Two projects that both contain the same repository.
|
|
const projectA = {
|
|
repositories: [{ repoAddress: REPO_ADDRESS }],
|
|
};
|
|
const projectB = {
|
|
repositories: [{ repoAddress: REPO_ADDRESS }],
|
|
};
|
|
|
|
test("work-item scope keeps explicit and repository-only read models", () => {
|
|
const explicitProject = {
|
|
id: "explicit",
|
|
legacy: false,
|
|
repositories: [{ repoAddress: REPO_ADDRESS }],
|
|
};
|
|
const repositoryOnlyProject = {
|
|
id: "repository-only",
|
|
legacy: true,
|
|
repositories: [{ repoAddress: `30617:${REPO_OWNER}:standalone` }],
|
|
};
|
|
const emptyProject = { id: "empty", legacy: false, repositories: [] };
|
|
|
|
assert.deepEqual(
|
|
projectsWithWorkItemRepositories([
|
|
explicitProject,
|
|
repositoryOnlyProject,
|
|
emptyProject,
|
|
]).map((project) => project.id),
|
|
["explicit", "repository-only"],
|
|
);
|
|
});
|
|
|
|
// Minimal valid NIP-34 issue event for the shared repo.
|
|
function makeIssue(id, updatedAt = 100, repoAddress = REPO_ADDRESS) {
|
|
return {
|
|
id,
|
|
kind: 1621,
|
|
pubkey: REPO_OWNER,
|
|
created_at: updatedAt,
|
|
content: "An issue",
|
|
tags: [
|
|
["a", repoAddress],
|
|
["subject", "Fix the thing"],
|
|
],
|
|
};
|
|
}
|
|
|
|
test("fetchProjectsWorkItems accumulates issues from every project repository", async () => {
|
|
const secondAddress = `30617:${REPO_OWNER}:desktop`;
|
|
const project = {
|
|
repositories: [
|
|
{ repoAddress: REPO_ADDRESS },
|
|
{ repoAddress: secondAddress },
|
|
],
|
|
};
|
|
const result = await fetchProjectsWorkItems(
|
|
[project],
|
|
makeFetchEvents([
|
|
makeIssue(ISSUE_ID, 100, REPO_ADDRESS),
|
|
makeIssue("j".repeat(64), 90, secondAddress),
|
|
]),
|
|
);
|
|
|
|
assert.deepEqual(
|
|
result.issues.items.map(({ repository }) => repository.repoAddress).sort(),
|
|
[REPO_ADDRESS, secondAddress].sort(),
|
|
);
|
|
});
|
|
|
|
// Minimal valid NIP-34 pull request event for the shared repo.
|
|
function makePR(id, updatedAt = 100) {
|
|
return {
|
|
id,
|
|
kind: 1618, // KIND_GIT_PULL_REQUEST
|
|
pubkey: REPO_OWNER,
|
|
created_at: updatedAt,
|
|
content: "A PR",
|
|
tags: [
|
|
["a", REPO_ADDRESS],
|
|
["subject", "Add a feature"],
|
|
],
|
|
};
|
|
}
|
|
|
|
// fetchEvents stub: returns the given root events (issues + PRs) and empty
|
|
// arrays for all other query kinds (updates, comments, statuses).
|
|
function makeFetchEvents(rootEvents) {
|
|
return async (filter) => {
|
|
const { kinds } = filter;
|
|
// Root issues (kind 1621) + PRs (kind 1618)
|
|
if (kinds?.includes(1621) || kinds?.includes(1618)) {
|
|
return rootEvents.filter((e) => kinds.includes(e.kind));
|
|
}
|
|
// Everything else (PR updates, comments, statuses) — empty
|
|
return [];
|
|
};
|
|
}
|
|
|
|
test("fetchProjectsWorkItems deduplicates issues from a shared repository", async () => {
|
|
const issue = makeIssue(ISSUE_ID);
|
|
const fetchEvents = makeFetchEvents([issue]);
|
|
|
|
const result = await fetchProjectsWorkItems(
|
|
[projectA, projectB],
|
|
fetchEvents,
|
|
);
|
|
|
|
assert.equal(
|
|
result.issues.items.length,
|
|
1,
|
|
"duplicate issue from shared repo must collapse to one row",
|
|
);
|
|
assert.equal(result.issues.items[0].issue.id, ISSUE_ID);
|
|
});
|
|
|
|
test("fetchProjectsWorkItems deduplicates pull requests from a shared repository", async () => {
|
|
const pr1 = makePR(PR_ID, 100);
|
|
const pr2 = makePR(PR_ID_2, 90);
|
|
const fetchEvents = makeFetchEvents([pr1, pr2]);
|
|
|
|
const result = await fetchProjectsWorkItems(
|
|
[projectA, projectB],
|
|
fetchEvents,
|
|
);
|
|
|
|
assert.equal(
|
|
result.pullRequests.items.length,
|
|
2,
|
|
"distinct PRs must survive dedup; only exact-id duplicates collapse",
|
|
);
|
|
const ids = result.pullRequests.items.map((item) => item.pullRequest.id);
|
|
assert.ok(ids.includes(PR_ID), "first PR must be present");
|
|
assert.ok(ids.includes(PR_ID_2), "second PR must be present");
|
|
});
|
|
|
|
test("fetchProjectsWorkItems returns a single row for a PR present in both project contexts", async () => {
|
|
// Same PR id returned twice (once per project's relay query).
|
|
const pr = makePR(PR_ID, 100);
|
|
// The stub returns the same event for every root query, simulating
|
|
// the relay returning the same PR for both projects' repo addresses.
|
|
let callCount = 0;
|
|
const fetchEvents = async (filter) => {
|
|
if (filter.kinds?.includes(1618)) {
|
|
callCount += 1;
|
|
return [pr];
|
|
}
|
|
return [];
|
|
};
|
|
|
|
const result = await fetchProjectsWorkItems(
|
|
[projectA, projectB],
|
|
fetchEvents,
|
|
);
|
|
|
|
// The relay was queried once per unique repo address — but even if it
|
|
// returned the same id twice across the two project contexts, dedupe fires.
|
|
assert.equal(
|
|
result.pullRequests.items.length,
|
|
1,
|
|
"same PR id appearing in both project contexts must produce one row",
|
|
);
|
|
// Sanity: the stub was actually called (proves we ran the production path).
|
|
assert.ok(callCount >= 1, "fetchEvents must have been called");
|
|
});
|