thurview 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.js +141 -0
- package/dist/cli.js.map +1 -1
- package/dist/forge/github.js +41 -0
- package/dist/forge/github.js.map +1 -1
- package/dist/forge/gitlab.js +84 -23
- package/dist/forge/gitlab.js.map +1 -1
- package/dist/forge/types.js +4 -0
- package/dist/forge/types.js.map +1 -1
- package/dist/pr-review/categories.js +51 -0
- package/dist/pr-review/categories.js.map +1 -0
- package/dist/pr-review/follow.js +208 -0
- package/dist/pr-review/follow.js.map +1 -0
- package/dist/pr-review/format.js +227 -0
- package/dist/pr-review/format.js.map +1 -0
- package/package.json +1 -1
- package/skills/thurview-pr-review/SKILL.md +181 -0
- package/skills/thurview-pr-review/references/forges.md +47 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { AxiError } from "axi-sdk-js";
|
|
4
|
+
import { CATEGORIES, CATEGORY_IDS, SEVERITIES, } from "./categories.js";
|
|
5
|
+
/**
|
|
6
|
+
* What a change request review posts, and how it reads it back. Everything a
|
|
7
|
+
* follow loop needs to resume lives in two hidden markers on the forge - one
|
|
8
|
+
* on the summary, one on each finding's thread - so a restart reads the state
|
|
9
|
+
* back from the change request and never from this machine.
|
|
10
|
+
*/
|
|
11
|
+
/** The summary's word budget, the counts table and the marker excluded. */
|
|
12
|
+
export const SUMMARY_WORDS = 120;
|
|
13
|
+
/** A finding's visible lines, its suggestion block excluded and its sign-off included. */
|
|
14
|
+
export const FINDING_LINES = 5;
|
|
15
|
+
const SUMMARY_TAG = "thurview-pr-review";
|
|
16
|
+
const FINDING_TAG = "thurview-finding";
|
|
17
|
+
const Finding = z.object({
|
|
18
|
+
id: z
|
|
19
|
+
.string()
|
|
20
|
+
.regex(/^[a-z0-9-]+$/)
|
|
21
|
+
.optional(),
|
|
22
|
+
category: z.enum(CATEGORY_IDS),
|
|
23
|
+
severity: z.enum(SEVERITIES),
|
|
24
|
+
path: z.string().min(1),
|
|
25
|
+
line: z.number().int().positive(),
|
|
26
|
+
startLine: z.number().int().positive().optional(),
|
|
27
|
+
side: z.enum(["head", "base"]).optional(),
|
|
28
|
+
title: z.string().min(1),
|
|
29
|
+
body: z.string().default(""),
|
|
30
|
+
suggestion: z.string().optional(),
|
|
31
|
+
});
|
|
32
|
+
const PassSchema = z.object({
|
|
33
|
+
/** The head this pass reviewed; `sync` refuses it once the change request moved on. */
|
|
34
|
+
head: z.string().min(7),
|
|
35
|
+
confidence: z.number().int().min(1).max(5),
|
|
36
|
+
reason: z.string().min(1),
|
|
37
|
+
risk: z.array(z.string().min(1)).min(1).max(5),
|
|
38
|
+
change: z.string().min(1),
|
|
39
|
+
reviewUrl: z.string().url().optional(),
|
|
40
|
+
signoff: z.string().optional(),
|
|
41
|
+
findings: z.array(Finding).default([]),
|
|
42
|
+
fixed: z.array(z.string()).default([]),
|
|
43
|
+
});
|
|
44
|
+
export function parsePass(text, file) {
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = JSON.parse(text);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
throw new AxiError(`${file} is not valid JSON: ${e.message}`, "VALIDATION_ERROR", [
|
|
51
|
+
"Read `thurview pr-review --help` for the pass file's shape",
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
const parsed = PassSchema.safeParse(raw);
|
|
55
|
+
if (!parsed.success)
|
|
56
|
+
throw new AxiError(`${file} is not a pass: ${parsed.error.issues
|
|
57
|
+
.map((i) => `${i.path.join(".") || "(root)"} ${i.message}`)
|
|
58
|
+
.join("; ")}`, "VALIDATION_ERROR", [
|
|
59
|
+
`category is one of ${CATEGORY_IDS.join(", ")}; severity is one of ${SEVERITIES.join(", ")}`,
|
|
60
|
+
"confidence is 1-5; risk holds 1 to 5 bullets",
|
|
61
|
+
]);
|
|
62
|
+
checkPass(parsed.data);
|
|
63
|
+
return parsed.data;
|
|
64
|
+
}
|
|
65
|
+
/** The rules no schema says: one line per title, and the comment budget. */
|
|
66
|
+
export function checkPass(p) {
|
|
67
|
+
// A newline would let a line pass as a table row or the marker, and so
|
|
68
|
+
// slip past the word budget; every one of these is one line by design.
|
|
69
|
+
const oneLine = [
|
|
70
|
+
["reason", p.reason],
|
|
71
|
+
["change", p.change],
|
|
72
|
+
["signoff", p.signoff],
|
|
73
|
+
...p.risk.map((r, i) => [`risk[${i}]`, r]),
|
|
74
|
+
];
|
|
75
|
+
for (const [field, text] of oneLine)
|
|
76
|
+
if (text && /[\r\n]/.test(text.trim()))
|
|
77
|
+
throw new AxiError(`${field} is more than one line`, "VALIDATION_ERROR", [
|
|
78
|
+
"reason, each risk bullet, change and signoff are one line each",
|
|
79
|
+
]);
|
|
80
|
+
for (const f of p.findings ?? []) {
|
|
81
|
+
const at = `${f.path}:${f.line}`;
|
|
82
|
+
if (f.title.includes("\n"))
|
|
83
|
+
throw new AxiError(`${at}: the title is more than one line`, "VALIDATION_ERROR", [
|
|
84
|
+
"The title is the claim, in one line; the fix goes in body",
|
|
85
|
+
]);
|
|
86
|
+
if (f.startLine && f.startLine > f.line)
|
|
87
|
+
throw new AxiError(`${at}: startLine ${f.startLine} is after line`, "VALIDATION_ERROR", [
|
|
88
|
+
"line is the LAST line of the range and startLine the first",
|
|
89
|
+
]);
|
|
90
|
+
const lines = visibleLines(f, p.signoff);
|
|
91
|
+
if (lines > FINDING_LINES)
|
|
92
|
+
throw new AxiError(`${at}: the comment is ${lines} lines, over the ${FINDING_LINES}-line budget`, "VALIDATION_ERROR", ["One point per comment: cut it to the claim and one fix, or split it in two"]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function visibleLines(f, signoff) {
|
|
96
|
+
const body = f.body?.trim() ? f.body.trim().split("\n").length : 0;
|
|
97
|
+
return 1 + body + (signoff ? 1 : 0);
|
|
98
|
+
}
|
|
99
|
+
/** Stable across passes, so the same finding found twice is recognised as one. */
|
|
100
|
+
export function findingId(f) {
|
|
101
|
+
if (f.id)
|
|
102
|
+
return f.id;
|
|
103
|
+
const h = createHash("sha1").update(`${f.category}\n${f.path}\n${f.title.trim()}`);
|
|
104
|
+
return h.digest("hex").slice(0, 10);
|
|
105
|
+
}
|
|
106
|
+
function marker(tag, data) {
|
|
107
|
+
return `<!-- ${tag} ${JSON.stringify(data)} -->`;
|
|
108
|
+
}
|
|
109
|
+
function readMarker(tag, body) {
|
|
110
|
+
const m = new RegExp(`^<!-- ${tag} (\\{.*?\\}) -->`).exec(body);
|
|
111
|
+
if (!m)
|
|
112
|
+
return null;
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(m[1]);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export function readSummaryMarker(body) {
|
|
121
|
+
return readMarker(SUMMARY_TAG, body);
|
|
122
|
+
}
|
|
123
|
+
export function readFindingMarker(body) {
|
|
124
|
+
const m = readMarker(FINDING_TAG, body);
|
|
125
|
+
if (!m || !(m.category in CATEGORIES) || !SEVERITIES.includes(m.severity))
|
|
126
|
+
return null;
|
|
127
|
+
const heading = body.split("\n")[1] ?? "";
|
|
128
|
+
return { ...m, title: heading.replace(/^.*?:\*\* /, "") };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* One finding as an inline comment: the claim, the fix, a suggestion the
|
|
132
|
+
* author can apply. GitHub anchors a range and replaces all of it; GitLab
|
|
133
|
+
* anchors the range's last line, so its suggestion reaches back over the rest.
|
|
134
|
+
*/
|
|
135
|
+
export function renderFinding(f, signoff, forge) {
|
|
136
|
+
const label = CATEGORIES[f.category].label;
|
|
137
|
+
const head = f.severity === "nit"
|
|
138
|
+
? `nit: **${label}:** ${f.title.trim()}`
|
|
139
|
+
: `**${label} · ${f.severity}:** ${f.title.trim()}`;
|
|
140
|
+
const parts = [
|
|
141
|
+
marker(FINDING_TAG, { id: findingId(f), category: f.category, severity: f.severity }),
|
|
142
|
+
head,
|
|
143
|
+
];
|
|
144
|
+
if (f.body?.trim())
|
|
145
|
+
parts.push(f.body.trim());
|
|
146
|
+
if (f.suggestion !== undefined) {
|
|
147
|
+
const above = f.startLine && f.startLine < f.line ? f.line - f.startLine : 0;
|
|
148
|
+
const fence = forge === "gitlab" ? `\`\`\`suggestion:-${above}+0` : "```suggestion";
|
|
149
|
+
parts.push(`${fence}\n${f.suggestion.replace(/\n$/, "")}\n\`\`\``);
|
|
150
|
+
}
|
|
151
|
+
if (signoff)
|
|
152
|
+
parts.push(signoff);
|
|
153
|
+
return parts.join("\n");
|
|
154
|
+
}
|
|
155
|
+
function table(open) {
|
|
156
|
+
if (!open.length)
|
|
157
|
+
return "No open findings.";
|
|
158
|
+
const rows = CATEGORY_IDS.filter((c) => open.some((f) => f.category === c)).map((c) => {
|
|
159
|
+
const n = (s) => open.filter((f) => f.category === c && f.severity === s).length;
|
|
160
|
+
return `| ${CATEGORIES[c].label} | ${SEVERITIES.map(n).join(" | ")} |`;
|
|
161
|
+
});
|
|
162
|
+
return [
|
|
163
|
+
`| Open findings | ${SEVERITIES.join(" | ")} |`,
|
|
164
|
+
`| --- | ${SEVERITIES.map(() => "---").join(" | ")} |`,
|
|
165
|
+
...rows,
|
|
166
|
+
].join("\n");
|
|
167
|
+
}
|
|
168
|
+
function headline(p, author, open) {
|
|
169
|
+
const blocking = open.filter((f) => f.severity === "blocking").length;
|
|
170
|
+
if (blocking)
|
|
171
|
+
return `Next: @${author} — fix the ${blocking} blocking finding${blocking === 1 ? "" : "s"}.`;
|
|
172
|
+
if (p.confidence >= 4)
|
|
173
|
+
return "Next: merge";
|
|
174
|
+
return `Next: @${author} — answer the risk below.`;
|
|
175
|
+
}
|
|
176
|
+
/** The line a stopped or finished review opens with instead of a next step. */
|
|
177
|
+
export function statusLine(m) {
|
|
178
|
+
const at = m.head.slice(0, 7);
|
|
179
|
+
if (m.state === "stopped")
|
|
180
|
+
return `Review stopped at ${at}. Remove the \`thurview:stop\` label and run \`thurview pr-review start\` to resume.`;
|
|
181
|
+
if (m.state === "merged" || m.state === "closed")
|
|
182
|
+
return `Review ended: ${m.state} at ${at}.`;
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
/** Words a reader reads: the marker and the counts table are not prose. */
|
|
186
|
+
export function summaryWords(body) {
|
|
187
|
+
return body
|
|
188
|
+
.split("\n")
|
|
189
|
+
.filter((l) => !l.startsWith("<!--") && !l.startsWith("|"))
|
|
190
|
+
.join(" ")
|
|
191
|
+
.split(/\s+/)
|
|
192
|
+
.filter((w) => /[\p{L}\p{N}]/u.test(w)).length;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The one summary a change request carries: the next step, the confidence
|
|
196
|
+
* with its reason, the risk, the change in short, the open findings by
|
|
197
|
+
* category and severity, and a link to the full review when there is one.
|
|
198
|
+
*/
|
|
199
|
+
export function renderSummary(p, m, author, open) {
|
|
200
|
+
const tail = [`Reviewed \`${m.head.slice(0, 7)}\``];
|
|
201
|
+
if (p.reviewUrl)
|
|
202
|
+
tail.push(`[Full review](${p.reviewUrl})`);
|
|
203
|
+
const parts = [
|
|
204
|
+
`${marker(SUMMARY_TAG, m)}\n${statusLine(m) ?? headline(p, author, open)}`,
|
|
205
|
+
`**Confidence ${p.confidence}/5:** ${p.reason.trim()}`,
|
|
206
|
+
`**Risk**\n${p.risk.map((r) => `- ${r.trim()}`).join("\n")}`,
|
|
207
|
+
`**Change:** ${p.change.trim()}`,
|
|
208
|
+
table(open),
|
|
209
|
+
tail.join(" · "),
|
|
210
|
+
];
|
|
211
|
+
if (p.signoff)
|
|
212
|
+
parts.push(p.signoff);
|
|
213
|
+
const body = parts.join("\n\n");
|
|
214
|
+
const words = summaryWords(body);
|
|
215
|
+
if (words > SUMMARY_WORDS)
|
|
216
|
+
throw new AxiError(`the summary is ${words} words, over the ${SUMMARY_WORDS}-word budget`, "VALIDATION_ERROR", ["Cut reason, risk and change to what the author acts on; the full review holds the rest"]);
|
|
217
|
+
return body;
|
|
218
|
+
}
|
|
219
|
+
/** The same summary under a new marker and opening line; the rest is kept as written. */
|
|
220
|
+
export function restate(body, m, opening) {
|
|
221
|
+
const lines = body.split("\n");
|
|
222
|
+
return [marker(SUMMARY_TAG, m), opening, ...lines.slice(2)].join("\n");
|
|
223
|
+
}
|
|
224
|
+
export function bareSummary(m, opening) {
|
|
225
|
+
return `${marker(SUMMARY_TAG, m)}\n${opening}`;
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/pr-review/format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,UAAU,EACV,YAAY,EACZ,UAAU,GAGX,MAAM,iBAAiB,CAAC;AAEzB;;;;;GAKG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AACjC,0FAA0F;AAC1F,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC;AAE/B,MAAM,WAAW,GAAG,oBAAoB,CAAC;AACzC,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAkBvC,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC;IACvB,EAAE,EAAE,CAAC;SACF,MAAM,EAAE;SACR,KAAK,CAAC,cAAc,CAAC;SACrB,QAAQ,EAAE;IACb,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC;IAC9B,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;IAC5B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACjC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACjD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE;IACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1B,uFAAuF;IACvF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACtC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACvC,CAAC,CAAC;AAKH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAY;IAClD,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,QAAQ,CAAC,GAAG,IAAI,uBAAwB,CAAW,CAAC,OAAO,EAAE,EAAE,kBAAkB,EAAE;YAC3F,4DAA4D;SAC7D,CAAC,CAAC;IACL,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM,CAAC,OAAO;QACjB,MAAM,IAAI,QAAQ,CAChB,GAAG,IAAI,mBAAmB,MAAM,CAAC,KAAK,CAAC,MAAM;aAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;aAC1D,IAAI,CAAC,IAAI,CAAC,EAAE,EACf,kBAAkB,EAClB;YACE,sBAAsB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAC5F,8CAA8C;SAC/C,CACF,CAAC;IACJ,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,CAAO;IAC/B,uEAAuE;IACvE,uEAAuE;IACvE,MAAM,OAAO,GAAmC;QAC9C,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC;QACtB,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAoB,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;KAC7D,CAAC;IACF,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,OAAO;QACjC,IAAI,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACpC,MAAM,IAAI,QAAQ,CAAC,GAAG,KAAK,wBAAwB,EAAE,kBAAkB,EAAE;gBACvE,gEAAgE;aACjE,CAAC,CAAC;IACP,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACjC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YACxB,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,mCAAmC,EAAE,kBAAkB,EAAE;gBAC/E,2DAA2D;aAC5D,CAAC,CAAC;QACL,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI;YACrC,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,SAAS,gBAAgB,EAAE,kBAAkB,EAAE;gBACtF,4DAA4D;aAC7D,CAAC,CAAC;QACL,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,KAAK,GAAG,aAAa;YACvB,MAAM,IAAI,QAAQ,CAChB,GAAG,EAAE,oBAAoB,KAAK,oBAAoB,aAAa,cAAc,EAC7E,kBAAkB,EAClB,CAAC,4EAA4E,CAAC,CAC/E,CAAC;IACN,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,CAAU,EAAE,OAAgB;IAChD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,SAAS,CAAC,CAAU;IAClC,IAAI,CAAC,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IACtB,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACnF,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,MAAM,CAAC,GAAW,EAAE,IAAY;IACvC,OAAO,QAAQ,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;AACnD,CAAC;AAED,SAAS,UAAU,CAAI,GAAW,EAAE,IAAY;IAC9C,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,SAAS,GAAG,kBAAkB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAE,CAAM,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,UAAU,CAAgB,WAAW,EAAE,IAAI,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,CAAC,GAAG,UAAU,CAAgB,WAAW,EAAE,IAAI,CAAC,CAAC;IACvD,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACvF,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,OAAO,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,EAAE,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,CAAU,EAAE,OAA2B,EAAE,KAAa;IAClF,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC;IAC3C,MAAM,IAAI,GACR,CAAC,CAAC,QAAQ,KAAK,KAAK;QAClB,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE;QACxC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,QAAQ,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;IACxD,MAAM,KAAK,GAAG;QACZ,MAAM,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QACrF,IAAI;KACL,CAAC;IACF,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7E,MAAM,KAAK,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,qBAAqB,KAAK,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC;QACpF,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAOD,SAAS,KAAK,CAAC,IAAmB;IAChC,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,mBAAmB,CAAC;IAC7C,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACpF,MAAM,CAAC,GAAG,CAAC,CAAW,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QAC3F,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzE,CAAC,CAAC,CAAC;IACH,OAAO;QACL,qBAAqB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;QAC/C,WAAW,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;QACtD,GAAG,IAAI;KACR,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,CAAO,EAAE,MAAc,EAAE,IAAmB;IAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;IACtE,IAAI,QAAQ;QACV,OAAO,UAAU,MAAM,cAAc,QAAQ,oBAAoB,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;IAChG,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC;QAAE,OAAO,aAAa,CAAC;IAC5C,OAAO,UAAU,MAAM,2BAA2B,CAAC;AACrD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,UAAU,CAAC,CAAgB;IACzC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QACvB,OAAO,qBAAqB,EAAE,sFAAsF,CAAC;IACvH,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,iBAAiB,CAAC,CAAC,KAAK,OAAO,EAAE,GAAG,CAAC;IAC9F,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI;SACR,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;SAC1D,IAAI,CAAC,GAAG,CAAC;SACT,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,CAAO,EACP,CAAgB,EAChB,MAAc,EACd,IAAmB;IAEnB,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACpD,IAAI,CAAC,CAAC,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG;QACZ,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAC1E,gBAAgB,CAAC,CAAC,UAAU,SAAS,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE;QACtD,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC5D,eAAe,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE;QAChC,KAAK,CAAC,IAAI,CAAC;QACX,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;KACjB,CAAC;IACF,IAAI,CAAC,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,KAAK,GAAG,aAAa;QACvB,MAAM,IAAI,QAAQ,CAChB,kBAAkB,KAAK,oBAAoB,aAAa,cAAc,EACtE,kBAAkB,EAClB,CAAC,wFAAwF,CAAC,CAC3F,CAAC;IACJ,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,CAAgB,EAAE,OAAe;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,CAAgB,EAAE,OAAe;IAC3D,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;AACjD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thurview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Guided, evidence-anchored reviews of agent-written code. A coding agent authors the review; you read, ask, comment and decide in the browser.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: thurview-pr-review
|
|
3
|
+
description: Review a pull or merge request on the forge itself and follow it until it merges - one short summary with a confidence score that is edited in place, one resolvable inline thread per finding in a fixed set of categories, threads resolved as their findings are fixed, and only what each push changed re-reviewed. Use when the user asks to review a PR or MR and post the review on it, to watch or follow a PR until it lands, for a Greptile-style or bot-style review, or invokes /thurview-pr-review. Not for a review the reader opens in the browser, which is the thurview skill, nor for fixing what a review finds, which is thurview-fix.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
argument-hint: "<PR or MR number or URL> [--once] [--stop]"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# thurview-pr-review
|
|
9
|
+
|
|
10
|
+
Review a change request where its author already is, and keep the review true
|
|
11
|
+
until the change request is merged or closed. The forge holds all the state:
|
|
12
|
+
one summary note with a hidden marker, one thread per finding with a hidden
|
|
13
|
+
marker. Nothing is kept on this machine, so the loop can die and resume
|
|
14
|
+
anywhere.
|
|
15
|
+
|
|
16
|
+
```mermaid
|
|
17
|
+
flowchart LR
|
|
18
|
+
W[wait] -->|push| R[review only what moved]
|
|
19
|
+
R --> S[sync: new threads, resolve fixed, edit summary]
|
|
20
|
+
S --> W
|
|
21
|
+
W -->|merged, closed, stopped| E[summary says so; stop]
|
|
22
|
+
W -->|none| W
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run the CLI as `thurview`, or `npx -y thurview` when it is not on PATH. It
|
|
26
|
+
prints TOON, and exit code 2 is a usage error. If it answers `unknown flag` or
|
|
27
|
+
`unknown command` for something below, run `thurview update` and retry once.
|
|
28
|
+
|
|
29
|
+
## Request
|
|
30
|
+
|
|
31
|
+
$ARGUMENTS
|
|
32
|
+
|
|
33
|
+
`--once` posts one pass and stops there. `--stop` runs
|
|
34
|
+
`thurview pr-review stop --change <ref>` and nothing else.
|
|
35
|
+
|
|
36
|
+
## Never
|
|
37
|
+
|
|
38
|
+
- Never approve, merge, close or push to the change request. The verdict is
|
|
39
|
+
the summary's confidence. The command has no verb for any of those, and you
|
|
40
|
+
do not reach around it with `gh` or `glab`.
|
|
41
|
+
- Never post a second summary. `sync` edits the one it finds.
|
|
42
|
+
- Never post a finding you are not sure of. Leave it out and say in a risk
|
|
43
|
+
bullet that something was not confirmed.
|
|
44
|
+
- Never post what you would not sign. Follow the user's own rules for text
|
|
45
|
+
posted in their name when they keep any - a sign-off line goes in `signoff`.
|
|
46
|
+
|
|
47
|
+
## 1. Where the review stands
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
thurview pr-review status --change <ref>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
It prints the change request's head and base, the head the last pass reviewed
|
|
54
|
+
(`review.reviewedHead`), every open finding with its `id`, and the categories
|
|
55
|
+
and confidence scale below. A stopped review stays stopped: do not sync it.
|
|
56
|
+
|
|
57
|
+
## 2. Review only what moved
|
|
58
|
+
|
|
59
|
+
- No summary yet: review `git diff <base> <head>`, the whole change.
|
|
60
|
+
- A summary at an older head: review `git diff <reviewedHead> <head>`. When
|
|
61
|
+
`git merge-base --is-ancestor <reviewedHead> <head>` fails, the branch was
|
|
62
|
+
rewritten: review the whole change again.
|
|
63
|
+
|
|
64
|
+
Fetch the head first (`gh pr checkout <n>` or `glab mr checkout <n>`, or
|
|
65
|
+
`git fetch origin <fetchRef>`), and search at the pinned commits with the
|
|
66
|
+
recipes in the `thurview` skill's Searching the code reference
|
|
67
|
+
(`thurview skill` prints its path). The evidence rules are `thurview-fix`'s:
|
|
68
|
+
a problem just as present at the base is not this change's finding, and a
|
|
69
|
+
finding that rests on "no callers" says which search found none.
|
|
70
|
+
|
|
71
|
+
Then decide each open finding against the new head: **fixed**, or **still
|
|
72
|
+
valid** (do nothing - its thread stays open and is not posted again).
|
|
73
|
+
|
|
74
|
+
## 3. Findings
|
|
75
|
+
|
|
76
|
+
One point per finding, on a line the diff touches - the forge refuses any
|
|
77
|
+
other line. Prefer one real defect over many nits.
|
|
78
|
+
|
|
79
|
+
| Category | Definition |
|
|
80
|
+
| ----------------- | ------------------------------------------------------------------------------ |
|
|
81
|
+
| `bug` | The code does the wrong thing for an input it accepts. |
|
|
82
|
+
| `security` | An attacker gains access, data or execution they should not have. |
|
|
83
|
+
| `performance` | Time, memory or calls grow worse than the change needs. |
|
|
84
|
+
| `reliability` | A failure, retry, timeout or race is handled wrongly or not at all. |
|
|
85
|
+
| `compatibility` | A public API, schema, config, flag or data format breaks its callers. |
|
|
86
|
+
| `maintainability` | The next change here is harder: duplication, dead code, a misleading name. |
|
|
87
|
+
| `tests` | A behaviour the change adds or alters is not tested, or a test proves nothing. |
|
|
88
|
+
| `docs` | A comment, README or doc now says something the code does not do. |
|
|
89
|
+
|
|
90
|
+
Severity is `blocking` (must be fixed before merge), `non-blocking` (worth
|
|
91
|
+
fixing) or `nit` (taste, and the comment says `nit:`).
|
|
92
|
+
|
|
93
|
+
Confidence that the change is safe to merge:
|
|
94
|
+
|
|
95
|
+
| Score | Meaning |
|
|
96
|
+
| ----- | ----------------------------------------------------------- |
|
|
97
|
+
| 5 | Safe to merge; nothing open beyond nits. |
|
|
98
|
+
| 4 | Safe to merge; non-blocking findings are worth a look. |
|
|
99
|
+
| 3 | Unsure: a risk is named that the review could not rule out. |
|
|
100
|
+
| 2 | Not yet: a blocking finding is open. |
|
|
101
|
+
| 1 | Do not merge: it breaks something that works today. |
|
|
102
|
+
|
|
103
|
+
An open blocking finding caps confidence at 2; `sync` refuses more.
|
|
104
|
+
|
|
105
|
+
## 4. Sync
|
|
106
|
+
|
|
107
|
+
Write `pass.json` for this head. `findings` holds only what is new; `fixed`
|
|
108
|
+
holds the ids of open findings this push fixed:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"head": "<the full sha you reviewed>",
|
|
113
|
+
"confidence": 2,
|
|
114
|
+
"reason": "Safe once the retry loop stops on a 4xx.",
|
|
115
|
+
"risk": ["Every upload goes through the changed retry path.", "No test covers a 4xx."],
|
|
116
|
+
"change": "Retries a failed upload three times with exponential backoff.",
|
|
117
|
+
"reviewUrl": "https://reviews.example.com/pr-7/",
|
|
118
|
+
"signoff": "<the user's sign-off line, when they keep one>",
|
|
119
|
+
"findings": [
|
|
120
|
+
{
|
|
121
|
+
"category": "bug",
|
|
122
|
+
"severity": "blocking",
|
|
123
|
+
"path": "src/upload.ts",
|
|
124
|
+
"line": 42,
|
|
125
|
+
"startLine": 40,
|
|
126
|
+
"title": "The retry loop never stops on a 4xx.",
|
|
127
|
+
"body": "Return the response on any status below 500.",
|
|
128
|
+
"suggestion": "if (res.status < 500) return res;"
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
"fixed": ["3f2a9c1b07"]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- `head` is the commit you reviewed. `sync` refuses the pass when the change
|
|
136
|
+
request moved on since: review what the new push added, then sync again.
|
|
137
|
+
- `reason`, `change`, `signoff` and each `risk` bullet are one line each;
|
|
138
|
+
`sync` refuses a line break in any of them. `reason` is one sentence;
|
|
139
|
+
`risk` is one to five bullets naming what could break, the blast radius, and
|
|
140
|
+
anything touching security, data, infra or a public API; `change` is two or
|
|
141
|
+
three sentences.
|
|
142
|
+
- The summary is capped at 120 words, the counts table aside. `sync` refuses
|
|
143
|
+
more: cut to what the author acts on.
|
|
144
|
+
- A finding's `title` is the claim in one line, `body` the fix. Title, body
|
|
145
|
+
and sign-off together fit five lines. `suggestion` replaces the lines from
|
|
146
|
+
`startLine` to `line` and is optional.
|
|
147
|
+
- `reviewUrl` links the full rendered review. When the user has a publish
|
|
148
|
+
target - `thurview export --out <folder>` into a Pages folder or a static
|
|
149
|
+
host they serve - publish there and pass its URL. With none, leave it out.
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
thurview pr-review sync --change <ref> --file pass.json --dry-run
|
|
153
|
+
thurview pr-review sync --change <ref> --file pass.json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The dry run prints the summary as it will read. A finding already open is
|
|
157
|
+
reported under `duplicates` and not posted twice.
|
|
158
|
+
|
|
159
|
+
## 5. Follow
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
thurview pr-review wait --change <ref> # --interval 120 --timeout 540 by default
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
It blocks until there is something to do and prints one `event`:
|
|
166
|
+
|
|
167
|
+
| Event | Do |
|
|
168
|
+
| ----------------------------- | -------------------------------------------- |
|
|
169
|
+
| `push` | back to step 2, with `since` as the old head |
|
|
170
|
+
| `none` | run `wait` again |
|
|
171
|
+
| `merged`, `closed`, `stopped` | stop: the summary already says so |
|
|
172
|
+
|
|
173
|
+
With `--once`, stop after the first sync.
|
|
174
|
+
|
|
175
|
+
A reader stops the loop with the `thurview:stop` label or a comment that
|
|
176
|
+
starts `/thurview stop`; you stop it with `thurview pr-review stop`. A stopped
|
|
177
|
+
review resumes with `thurview pr-review start --change <ref>` once the label
|
|
178
|
+
is gone. Report what you posted and the summary's link when the loop ends.
|
|
179
|
+
|
|
180
|
+
How GitHub and GitLab differ, and what each supports, is in
|
|
181
|
+
[Forges](references/forges.md).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Forges
|
|
2
|
+
|
|
3
|
+
`thurview pr-review` drives the same seam as `thurview forge`: `gh api` for
|
|
4
|
+
GitHub and `glab api` for GitLab, a self-hosted host owned by whichever CLI is
|
|
5
|
+
authenticated for it. The seam has no merge, close or push, so neither does
|
|
6
|
+
this command.
|
|
7
|
+
|
|
8
|
+
## What each forge does
|
|
9
|
+
|
|
10
|
+
| Step | GitHub | GitLab |
|
|
11
|
+
| ----------------- | ------------------------------------------ | --------------------------------------------------- |
|
|
12
|
+
| The summary | one issue comment, `PATCH`ed in place | one merge request note, `PUT` in place |
|
|
13
|
+
| A finding | one review comment, a resolvable thread | one diff discussion, a resolvable thread |
|
|
14
|
+
| A line range | anchored as the range | anchored at its last line |
|
|
15
|
+
| A suggestion | ` ```suggestion ` over the range | ` ```suggestion:-N+0 ` reaching back over the range |
|
|
16
|
+
| Resolving a fixed | a reply, then `resolveReviewThread` | a reply, then `PUT .../discussions/<id>` resolved |
|
|
17
|
+
| The stop label | `thurview:stop` among the pull's labels | `thurview:stop` among the merge request's labels |
|
|
18
|
+
| The stop command | an issue comment starting `/thurview stop` | a top-level note starting `/thurview stop` |
|
|
19
|
+
| Merged or closed | `merged`, or `state: closed` | `state: merged` or `closed` |
|
|
20
|
+
|
|
21
|
+
## Where the state lives
|
|
22
|
+
|
|
23
|
+
The summary carries
|
|
24
|
+
`<!-- thurview-pr-review {"head":"<sha>","state":"active","seen":"<note id>"} -->`
|
|
25
|
+
and each finding's first comment
|
|
26
|
+
`<!-- thurview-finding {"id":"<id>","category":"bug","severity":"blocking"} -->`.
|
|
27
|
+
`state` is `active`, `stopped`, `merged` or `closed`. `seen` is the newest
|
|
28
|
+
note already read, so a `/thurview stop` posted before a `start` stops nothing.
|
|
29
|
+
Only a summary posted by the account the CLI is logged in as counts, so a
|
|
30
|
+
pasted marker changes nothing. A finding's id is the `id` given in the pass, or a hash of its category, path
|
|
31
|
+
and title, which is how the same finding found on the next push is recognised.
|
|
32
|
+
|
|
33
|
+
## Gaps
|
|
34
|
+
|
|
35
|
+
- No forge approve. A forge approve can arm an auto-merge, and the verdict
|
|
36
|
+
lives in the summary; `thurview forge submit` is the command for one.
|
|
37
|
+
- GitHub reads the newest 100 review threads (`reviewThreads(last:100)`), the
|
|
38
|
+
same limit `thurview forge prior` has; GitLab reads every page.
|
|
39
|
+
- Every call asks the forge who it is (`gh api user`, `glab api user`) to
|
|
40
|
+
tell its own markers from pasted ones, so it needs a token that can read its
|
|
41
|
+
own user; a GitHub App installation token cannot.
|
|
42
|
+
- A finding must sit on a line the diff touches. A finding on an untouched
|
|
43
|
+
caller goes in a risk bullet.
|
|
44
|
+
- GitLab reports no per-thread staleness, and its adapter is driven by stub
|
|
45
|
+
tests but has not been run against a live instance.
|
|
46
|
+
- The full-review link is whatever URL the pass names; this command publishes
|
|
47
|
+
nothing itself.
|