| 1 | import * as Schema from "effect/Schema"; |
| 2 | |
| 3 | import { IsoDateTime, PositiveInt, TrimmedNonEmptyString } from "./baseSchemas.ts"; |
| 4 | import { ModelSelection } from "./modelSelection.ts"; |
| 5 | |
| 6 | /** |
| 7 | * A guided review is written for the maintainer deciding whether a change should land, not for |
| 8 | * someone checking it line by line. Review agents already read every line; what is left for a |
| 9 | * person is intent, design, and the calls an agent cannot make on their behalf. |
| 10 | * |
| 11 | * So a guide is a tour of stops in reading order, each building on the ones before (contract, then |
| 12 | * the service, then its callers), with the mechanical rest grouped at the end. Each stop shows only |
| 13 | * the code that carries its idea and names the one call a maintainer should make there, if any; |
| 14 | * that call is what tells the reader where to slow down. |
| 15 | * |
| 16 | * The model writes the tour; the server holds it to the change. Excerpts and notes are clamped to |
| 17 | * lines the change touched, and a changed file the model left out still lands in a closing skim |
| 18 | * stop, so every file is accounted for. |
| 19 | */ |
| 20 | |
| 21 | /** |
| 22 | * The one call a maintainer should make at a stop. Not a bug or a review nit; those belong to the |
| 23 | * review agents. The kind says what sort of judgment it asks for. |
| 24 | */ |
| 25 | export const PullRequestReviewGuideAttention = Schema.Struct({ |
| 26 | kind: Schema.Literals(["tradeoff", "contract", "product", "risk", "accessibility"]), |
| 27 | note: TrimmedNonEmptyString, |
| 28 | }); |
| 29 | export type PullRequestReviewGuideAttention = typeof PullRequestReviewGuideAttention.Type; |
| 30 | |
| 31 | /** |
| 32 | * Lines worth reading, always inside the change. `old` shows removed code, for a change that is |
| 33 | * mostly deletion; line numbers are on the side named. |
| 34 | */ |
| 35 | export const PullRequestReviewGuideExcerpt = Schema.Struct({ |
| 36 | path: TrimmedNonEmptyString, |
| 37 | side: Schema.Literals(["new", "old"]), |
| 38 | start: PositiveInt, |
| 39 | end: PositiveInt, |
| 40 | caption: Schema.String, |
| 41 | }); |
| 42 | export type PullRequestReviewGuideExcerpt = typeof PullRequestReviewGuideExcerpt.Type; |
| 43 | |
| 44 | /** |
| 45 | * What the code at one line does not say for itself: why it is shaped this way, what it connects |
| 46 | * to. An explanation, not a finding. Anchored inside one of its stop's excerpts. |
| 47 | */ |
| 48 | export const PullRequestReviewGuideNote = Schema.Struct({ |
| 49 | path: TrimmedNonEmptyString, |
| 50 | line: PositiveInt, |
| 51 | title: TrimmedNonEmptyString, |
| 52 | body: Schema.String, |
| 53 | }); |
| 54 | export type PullRequestReviewGuideNote = typeof PullRequestReviewGuideNote.Type; |
| 55 | |
| 56 | export const PullRequestReviewGuideStop = Schema.Struct({ |
| 57 | title: TrimmedNonEmptyString, |
| 58 | summary: Schema.String, |
| 59 | attention: Schema.NullOr(PullRequestReviewGuideAttention), |
| 60 | excerpts: Schema.Array(PullRequestReviewGuideExcerpt), |
| 61 | /** |
| 62 | * Changed files the tour never excerpts, each listed by one stop. A file the tour does show may |
| 63 | * be excerpted by several stops, each showing the part that stop is about. |
| 64 | */ |
| 65 | files: Schema.Array( |
| 66 | Schema.Struct({ |
| 67 | path: TrimmedNonEmptyString, |
| 68 | note: Schema.optionalKey(Schema.String), |
| 69 | }), |
| 70 | ), |
| 71 | notes: Schema.Array(PullRequestReviewGuideNote), |
| 72 | /** Questions about intent a reader might ask here, for a follow-up conversation. */ |
| 73 | questions: Schema.Array(TrimmedNonEmptyString), |
| 74 | }); |
| 75 | export type PullRequestReviewGuideStop = typeof PullRequestReviewGuideStop.Type; |
| 76 | |
| 77 | export const PullRequestReviewGuide = Schema.Struct({ |
| 78 | /** The head the guide was written against. A different head means the guide is stale. */ |
| 79 | headSha: TrimmedNonEmptyString, |
| 80 | generatedAt: IsoDateTime, |
| 81 | modelSelection: ModelSelection, |
| 82 | stops: Schema.Array(PullRequestReviewGuideStop), |
| 83 | }); |
| 84 | export type PullRequestReviewGuide = typeof PullRequestReviewGuide.Type; |
| 85 | |
| 86 | /** |
| 87 | * A guide run in progress or the last one that failed. A run reads the change in a checkout and |
| 88 | * takes minutes on a large one, so it runs on the server and the page watches this instead of |
| 89 | * holding a request open. A server restart drops a run in progress; nothing claims it. |
| 90 | */ |
| 91 | export const PullRequestReviewGuideRun = Schema.Union([ |
| 92 | Schema.TaggedStruct("Running", { |
| 93 | headSha: TrimmedNonEmptyString, |
| 94 | startedAt: IsoDateTime, |
| 95 | }), |
| 96 | Schema.TaggedStruct("Failed", { |
| 97 | headSha: TrimmedNonEmptyString, |
| 98 | failedAt: IsoDateTime, |
| 99 | detail: TrimmedNonEmptyString, |
| 100 | }), |
| 101 | ]); |
| 102 | export type PullRequestReviewGuideRun = typeof PullRequestReviewGuideRun.Type; |
| 103 | |
| 104 | /** |
| 105 | * The stored guide for a pull request and the head it is now at. `guide` is null when none was |
| 106 | * generated; a guide whose `headSha` differs from `currentHeadSha` was written before a push. |
| 107 | * `run` is the generation in progress, or the last failed one; a finished run leaves only `guide`. |
| 108 | */ |
| 109 | export const PullRequestReviewGuideResult = Schema.Struct({ |
| 110 | guide: Schema.NullOr(PullRequestReviewGuide), |
| 111 | run: Schema.NullOr(PullRequestReviewGuideRun), |
| 112 | /** Null where the host did not report a head with the detail. */ |
| 113 | currentHeadSha: Schema.NullOr(TrimmedNonEmptyString), |
| 114 | }); |
| 115 | export type PullRequestReviewGuideResult = typeof PullRequestReviewGuideResult.Type; |