Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions apps/server/src/auth/RpcAuthorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export const RPC_REQUIRED_SCOPES = {
[WS_METHODS.pullRequestsPreview]: AuthOrchestrationReadScope,
[WS_METHODS.pullRequestsChecks]: AuthOrchestrationReadScope,
[WS_METHODS.pullRequestsActivity]: AuthOrchestrationReadScope,
[WS_METHODS.pullRequestsReviewGuide]: AuthOrchestrationReadScope,
[WS_METHODS.pullRequestsThreadComments]: AuthOrchestrationReadScope,
[WS_METHODS.pullRequestsDiffFileContents]: AuthOrchestrationReadScope,
[WS_METHODS.pullRequestsFilesViewed]: AuthOrchestrationReadScope,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/git/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ function createTextGeneration(
Effect.succeed({
title: "Update workflow",
}),
generateReviewGuide: () => Effect.succeed({ stops: [] }),
...overrides,
};

Expand Down Expand Up @@ -395,6 +396,7 @@ function createTextGeneration(
}),
),
),
generateReviewGuide: (input) => implementation.generateReviewGuide(input),
};
}

Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/observability/RpcInstrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ const RPC_AGGREGATES = {
[WS_METHODS.pullRequestsPreview]: "pull-requests",
[WS_METHODS.pullRequestsChecks]: "pull-requests",
[WS_METHODS.pullRequestsActivity]: "pull-requests",
[WS_METHODS.pullRequestsReviewGuide]: "pull-requests",
[WS_METHODS.pullRequestsGenerateReviewGuide]: "pull-requests",
[WS_METHODS.pullRequestsThreadComments]: "pull-requests",
[WS_METHODS.pullRequestsDiffFileContents]: "pull-requests",
[WS_METHODS.pullRequestsFilesViewed]: "pull-requests",
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/persistence/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,6 @@ export type ProviderSessionRuntimeRepositoryError = PersistenceSqlError | Persis
export type AuthPairingLinkRepositoryError = PersistenceSqlError | PersistenceDecodeError;
export type AuthSessionRepositoryError = PersistenceSqlError | PersistenceDecodeError;
export type PullRequestFilesViewedRepositoryError = PersistenceSqlError | PersistenceDecodeError;
export type PullRequestReviewGuideRepositoryError = PersistenceSqlError | PersistenceDecodeError;

export type ProjectionRepositoryError = PersistenceSqlError | PersistenceDecodeError;
2 changes: 2 additions & 0 deletions apps/server/src/persistence/Migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import Migration0057 from "./Migrations/057_ScheduledTaskWebhooks.ts";
import Migration0058 from "./Migrations/058_WebhookRelayDeliveries.ts";
import Migration0059 from "./Migrations/059_McpAppModelContext.ts";
import Migration0060 from "./Migrations/060_ThreadSnapshotWindowIndexes.ts";
import Migration0061 from "./Migrations/061_PullRequestReviewGuides.ts";

/**
* Migration loader with all migrations defined inline.
Expand Down Expand Up @@ -148,6 +149,7 @@ export const migrationEntries = [
[58, "WebhookRelayDeliveries", Migration0058],
[59, "McpAppModelContext", Migration0059],
[60, "ThreadSnapshotWindowIndexes", Migration0060],
[61, "PullRequestReviewGuides", Migration0061],
] as const;

export const migrationManifest = migrationEntries.map(([id, name]) => [id, name] as const);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ layer("055_OrchestrationV2", (it) => {
Effect.sync(() => {
assert.deepStrictEqual(
migrationEntries.map(([id]) => id),
Array.from({ length: 60 }, (_, index) => index + 1),
Array.from({ length: 61 }, (_, index) => index + 1),
);
}),
);
Expand All @@ -32,6 +32,7 @@ layer("055_OrchestrationV2", (it) => {
[58, "WebhookRelayDeliveries"],
[59, "McpAppModelContext"],
[60, "ThreadSnapshotWindowIndexes"],
[61, "PullRequestReviewGuides"],
]);
assert.deepStrictEqual(yield* runMigrations(), []);

Expand All @@ -58,6 +59,7 @@ layer("055_OrchestrationV2", (it) => {
{ migration_id: 58, name: "WebhookRelayDeliveries" },
{ migration_id: 59, name: "McpAppModelContext" },
{ migration_id: 60, name: "ThreadSnapshotWindowIndexes" },
{ migration_id: 61, name: "PullRequestReviewGuides" },
]);

const tables = yield* sql<{ readonly name: string }>`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as Effect from "effect/Effect";
import * as SqlClient from "effect/sql/SqlClient";

export default Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient;

// The latest guide per pull request, keyed by its host-level identity so every project that
// checks out the same repository shares one. Guides cost a model run, so a push leaves the old
// one in place for the page to call stale rather than deleting it.
yield* sql`
CREATE TABLE IF NOT EXISTS pull_request_review_guides (
host TEXT NOT NULL,
repository TEXT NOT NULL,
number INTEGER NOT NULL,
guide_json TEXT NOT NULL,
PRIMARY KEY (host, repository, number)
) WITHOUT ROWID
`;
});
84 changes: 84 additions & 0 deletions apps/server/src/persistence/PullRequestReviewGuides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as SqlClient from "effect/sql/SqlClient";
import * as SqlSchema from "effect/sql/SqlSchema";

import { PullRequestReviewGuide, ThreadPullRequestKey } from "@t3tools/contracts";

import {
PersistenceDecodeError,
PersistenceSqlError,
type PullRequestReviewGuideRepositoryError,
} from "./Errors.ts";

const GuideRow = Schema.Struct({
guide: Schema.fromJsonString(PullRequestReviewGuide),
});

/** The latest review guide per pull request, keyed by host-level identity. */
export class PullRequestReviewGuideRepository extends Context.Service<
PullRequestReviewGuideRepository,
{
readonly get: (
key: ThreadPullRequestKey,
) => Effect.Effect<
Option.Option<PullRequestReviewGuide>,
PullRequestReviewGuideRepositoryError
>;
readonly put: (
key: ThreadPullRequestKey,
guide: PullRequestReviewGuide,
) => Effect.Effect<void, PullRequestReviewGuideRepositoryError>;
}
>()("t3/persistence/PullRequestReviewGuides/PullRequestReviewGuideRepository") {}

const make = Effect.gen(function* () {
const sql = yield* SqlClient.SqlClient;

const findGuide = SqlSchema.findOneOption({
Request: ThreadPullRequestKey,
Result: GuideRow,
execute: ({ host, repository, number }) =>
sql`
SELECT guide_json AS "guide"
FROM pull_request_review_guides
WHERE host = ${host} AND repository = ${repository} AND number = ${number}
`,
});
const encodeGuide = Schema.encodeEffect(Schema.fromJsonString(PullRequestReviewGuide));

return PullRequestReviewGuideRepository.of({
get: (key) =>
findGuide(key).pipe(
Effect.map(Option.map((row) => row.guide)),
Effect.mapError((cause) =>
Schema.isSchemaError(cause)
? PersistenceDecodeError.fromSchemaError("PullRequestReviewGuide", cause)
: new PersistenceSqlError({ operation: "getPullRequestReviewGuide", cause }),
),
),
put: (key, guide) =>
encodeGuide(guide).pipe(
Effect.mapError((cause) =>
PersistenceDecodeError.fromSchemaError("PullRequestReviewGuide", cause),
),
Effect.flatMap((guideJson) =>
sql`
INSERT INTO pull_request_review_guides (host, repository, number, guide_json)
VALUES (${key.host}, ${key.repository}, ${key.number}, ${guideJson})
ON CONFLICT (host, repository, number) DO UPDATE SET guide_json = excluded.guide_json
`.pipe(
Effect.mapError(
(cause) => new PersistenceSqlError({ operation: "putPullRequestReviewGuide", cause }),
),
),
),
Effect.asVoid,
),
});
});

export const layer = Layer.effect(PullRequestReviewGuideRepository, make);
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe("V2 preview upgrade", () => {
[58, "WebhookRelayDeliveries"],
[59, "McpAppModelContext"],
[60, "ThreadSnapshotWindowIndexes"],
[61, "PullRequestReviewGuides"],
]);
assert.deepStrictEqual(yield* runMigrations(), []);
assert.deepStrictEqual(yield* sql`SELECT * FROM orchestration_v2_legacy_imports`, imports);
Expand Down Expand Up @@ -124,6 +125,7 @@ describe("V2 preview upgrade", () => {
[58, "WebhookRelayDeliveries"],
[59, "McpAppModelContext"],
[60, "ThreadSnapshotWindowIndexes"],
[61, "PullRequestReviewGuides"],
]);
}).pipe(Effect.provide(NodeSqliteClient.layer({ filename: ":memory:" }))),
);
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/provider/Drivers/CodexManagedProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ export const makeManagedCodexProvider = Effect.fn("makeManagedCodexProvider")(fu
protect("generateBranchName", nativeGeneration.generateBranchName(value)),
generateThreadTitle: (value) =>
protect("generateThreadTitle", nativeGeneration.generateThreadTitle(value)),
generateReviewGuide: (value) =>
protect("generateReviewGuide", nativeGeneration.generateReviewGuide(value)),
};
return {
instanceId,
Expand Down
Loading
Loading