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
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