Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions apps/server/src/git/Layers/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,38 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
}),
);

it.effect("status reuses cached PR lookup results for the same branch context", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("okcode-git-manager-");
yield* initRepo(repoDir);
yield* runGit(repoDir, ["checkout", "-b", "feature/status-cache"]);

const { manager, ghCalls } = yield* makeManager({
ghScenario: {
prListSequence: [
JSON.stringify([
{
number: 91,
title: "Cached PR",
url: "https://github.com/pingdotgg/codething-mvp/pull/91",
baseRefName: "main",
headRefName: "feature/status-cache",
state: "OPEN",
updatedAt: "2026-03-10T07:00:00Z",
},
]),
],
},
});

const first = yield* manager.status({ cwd: repoDir });
const second = yield* manager.status({ cwd: repoDir });

expect(first.pr).toEqual(second.pr);
expect(ghCalls.filter((call) => call.startsWith("pr list "))).toHaveLength(1);
}),
);

it.effect("creates a commit when working tree is dirty", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("okcode-git-manager-");
Expand Down
24 changes: 20 additions & 4 deletions apps/server/src/git/Layers/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ interface PullRequestInfo extends OpenPrInfo {
updatedAt: string | null;
}

interface LatestPrLookupCacheEntry {
expiresAtMs: number;
pr: PullRequestInfo | null;
}

interface ResolvedPullRequest {
number: number;
title: string;
Expand Down Expand Up @@ -369,6 +374,8 @@ export const makeGitManager = Effect.gen(function* () {
const gitCore = yield* GitCore;
const gitHubCli = yield* GitHubCli;
const textGeneration = yield* TextGeneration;
const latestPrLookupCache = new Map<string, LatestPrLookupCacheEntry>();
const LATEST_PR_LOOKUP_CACHE_TTL_MS = 15_000;

const createProgressEmitter = (
input: { cwd: string; action: "commit" | "commit_push" | "commit_push_pr" },
Expand Down Expand Up @@ -619,6 +626,13 @@ export const makeGitManager = Effect.gen(function* () {
const findLatestPr = (cwd: string, details: { branch: string; upstreamRef: string | null }) =>
Effect.gen(function* () {
const headContext = yield* resolveBranchHeadContext(cwd, details);
const cacheKey = [cwd, headContext.headSelectors.join("\u0000")].join("\u0001");
const now = Date.now();
const cached = latestPrLookupCache.get(cacheKey);
if (cached && cached.expiresAtMs > now) {
return cached.pr;
}

const parsedByNumber = new Map<number, PullRequestInfo>();

for (const headSelector of headContext.headSelectors) {
Expand Down Expand Up @@ -663,10 +677,12 @@ export const makeGitManager = Effect.gen(function* () {
});

const latestOpenPr = parsed.find((pr) => pr.state === "open");
if (latestOpenPr) {
return latestOpenPr;
}
return parsed[0] ?? null;
const resolved = latestOpenPr ?? parsed[0] ?? null;
latestPrLookupCache.set(cacheKey, {
expiresAtMs: now + LATEST_PR_LOOKUP_CACHE_TTL_MS,
pr: resolved,
});
return resolved;
});

const resolveBaseBranch = (
Expand Down
22 changes: 22 additions & 0 deletions apps/web/src/pullRequestProjectMatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,26 @@ describe("findProjectMatchingPullRequestReference", () => {
),
).toBeNull();
});

it("returns null when multiple projects match the same repository slug", () => {
const projects = [
makeProject({
id: "project-1" as Project["id"],
name: "Psi Claw",
cwd: "/Users/buns/projects/psi-claw",
}),
makeProject({
id: "project-2" as Project["id"],
name: "Psi Claw",
cwd: "/Users/buns/projects/psi-claw-copy",
}),
];

expect(
findProjectMatchingPullRequestReference(
projects,
"https://github.com/OpenKnots/psi-claw/pull/137",
),
).toBeNull();
});
});
12 changes: 8 additions & 4 deletions apps/web/src/pullRequestProjectMatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function normalizeRepositorySlug(input: string): string {
}

function projectRepositoryCandidates(project: Project): string[] {
const candidates = [project.name, lastPathSegment(project.cwd)]
const candidates = [lastPathSegment(project.cwd), project.name]
.map(normalizeRepositorySlug)
.filter((candidate) => candidate.length > 0);

Expand All @@ -37,8 +37,12 @@ export function findProjectMatchingPullRequestReference(
return null;
}

return (
projects.find((project) => projectRepositoryCandidates(project).includes(targetRepository)) ??
null
const matches = projects.filter((project) =>
projectRepositoryCandidates(project).includes(targetRepository),
);
if (matches.length !== 1) {
return null;
}

return matches[0] ?? null;
}
Loading