thurview 0.11.0 → 0.12.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 +104 -77
- package/dist/cli.js +83 -24
- package/dist/cli.js.map +1 -1
- package/dist/forge/gitlab.js +18 -17
- package/dist/forge/gitlab.js.map +1 -1
- package/dist/forge/submission.js +123 -0
- package/dist/forge/submission.js.map +1 -1
- package/dist/store.js +11 -0
- package/dist/store.js.map +1 -1
- package/dist/thread-state.js +23 -0
- package/dist/thread-state.js.map +1 -1
- package/dist/ui/app.js.map +1 -1
- package/package.json +1 -1
- package/skills/thurview/references/lifecycle.md +26 -0
package/README.md
CHANGED
|
@@ -1,83 +1,45 @@
|
|
|
1
1
|
# thurview
|
|
2
2
|
|
|
3
|
-
Guided, evidence-anchored reviews of agent-written code.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
and serves it in your browser: the walkthrough, live code peeks, the diff,
|
|
9
|
-
commits, and a software map. You ask questions, leave anchored comments, and
|
|
10
|
-
approve or request changes. The agent answers and republishes.
|
|
11
|
-
|
|
12
|
-
It also explains a codebase. A **code explainer** is the same document over a
|
|
13
|
-
different unit: one pinned commit instead of a range, so a reader can see the
|
|
14
|
-
architecture well enough to spot design problems themselves. It has no diff and
|
|
15
|
-
nothing to approve, it surfaces structure rather than grading it, and it states
|
|
16
|
-
what it did not examine.
|
|
17
|
-
|
|
18
|
-
It does not review the code for you. It helps you understand it fast enough
|
|
19
|
-
to review it yourself.
|
|
20
|
-
|
|
21
|
-

|
|
23
|
-
|
|
24
|
-
The clip is the `/thurview` skill's loop end to end: `thurview publish`, a
|
|
25
|
-
question arriving in `thurview wait`, the reply, then the reader following an
|
|
26
|
-
anchor into the code, commenting on a line range in the diff, reading the
|
|
27
|
-
answer in the threads panel and requesting changes.
|
|
28
|
-
|
|
29
|
-
## What the reader sees
|
|
30
|
-
|
|
31
|
-
Prose with every claim anchored to code, opened beside the text:
|
|
32
|
-
|
|
33
|
-

|
|
35
|
-
|
|
36
|
-
The diff at the pinned commits, commenting on a selected line range:
|
|
37
|
-
|
|
38
|
-

|
|
40
|
-
|
|
41
|
-
The software map: where the change landed in the system, and what sits next to
|
|
42
|
-
it — the question the diff cannot answer:
|
|
43
|
-
|
|
44
|
-

|
|
46
|
-
|
|
47
|
-
Threads: a question the agent already answered, and a comment held for the
|
|
48
|
-
decision:
|
|
49
|
-
|
|
50
|
-

|
|
52
|
-
|
|
53
|
-
Approve, or send it back with the comments:
|
|
54
|
-
|
|
55
|
-

|
|
57
|
-
|
|
58
|
-
```mermaid
|
|
59
|
-
flowchart LR
|
|
60
|
-
A[Branch, PR or range] --> B[Agent pins base and head]
|
|
61
|
-
B --> C[Agent authors review.md + data.yaml + map.yaml]
|
|
62
|
-
C --> D[thurview publish: validate, seal revision]
|
|
63
|
-
D --> E[You read, ask, comment in the browser]
|
|
64
|
-
E -->|Request changes| C
|
|
65
|
-
E -->|Approve| F[Done]
|
|
66
|
-
```
|
|
3
|
+
Guided, evidence-anchored reviews of agent-written code. A coding agent studies
|
|
4
|
+
a branch, pull request or commit range and writes a short document in which
|
|
5
|
+
every claim is anchored to an exact file and line range at a pinned commit. You
|
|
6
|
+
read it in your browser, ask the agent questions, comment on the code, and
|
|
7
|
+
approve or send it back.
|
|
67
8
|
|
|
68
9
|
## Install
|
|
69
10
|
|
|
70
11
|
Give your agent the skill, with the [skills](https://github.com/vercel-labs/skills)
|
|
71
|
-
CLI
|
|
12
|
+
CLI - it works with Claude Code, Codex, Cursor, OpenCode and every agent that
|
|
72
13
|
reads the Agent Skills format:
|
|
73
14
|
|
|
74
15
|
```sh
|
|
75
16
|
npx skills@latest add https://github.com/Thurbeen/thurview \
|
|
76
17
|
--skill thurview --agent universal claude-code --global --yes
|
|
77
|
-
npx skills@latest add https://github.com/Thurbeen/thurview \
|
|
78
|
-
--skill review-fix --agent universal claude-code --global --yes # optional, see below
|
|
79
18
|
```
|
|
80
19
|
|
|
20
|
+
The skill reaches the `thurview` command through `npx`, so the requirements
|
|
21
|
+
are Node 22 or later and git (`gh` for pull requests, `glab` for merge
|
|
22
|
+
requests). Then, in any repository, ask your agent:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
Use the thurview skill to review my current branch against up-to-date main
|
|
26
|
+
and open it.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+

|
|
31
|
+
|
|
32
|
+
The clip is the reader's half, the agent working off camera: following an anchor
|
|
33
|
+
from the prose into the code, opening a call stack frame, commenting on a line
|
|
34
|
+
range of the diff, seeing on the map what the change added, reading in the
|
|
35
|
+
threads panel the answer to a question already asked, and sending the review
|
|
36
|
+
back with changes requested.
|
|
37
|
+
|
|
38
|
+
<details>
|
|
39
|
+
<summary><b>Other ways to install</b> - pin the skill to a release, install the
|
|
40
|
+
command from npm, run from a checkout, session hooks, the review-fix
|
|
41
|
+
skill</summary>
|
|
42
|
+
|
|
81
43
|
`--global` installs for your user, so one install covers every repository.
|
|
82
44
|
`universal` puts the one real copy in `~/.agents/skills/thurview`, the directory
|
|
83
45
|
no single agent owns, and every other agent you name gets a symlink to it, such
|
|
@@ -103,13 +65,11 @@ The npm package ships the same skill, so `thurview setup skill` links the copy
|
|
|
103
65
|
that matches the command you have installed. Use that when you want the two to
|
|
104
66
|
move together.
|
|
105
67
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
the skill reach it through `npx`:
|
|
68
|
+
Install the command itself, rather than leaving the skill to reach it through
|
|
69
|
+
`npx` on every run:
|
|
109
70
|
|
|
110
71
|
```sh
|
|
111
72
|
npm install -g thurview # or: pnpm add -g thurview
|
|
112
|
-
npx -y thurview # no install; the skill falls back to this
|
|
113
73
|
```
|
|
114
74
|
|
|
115
75
|
The review reasons over a code graph thurview builds itself from the pinned
|
|
@@ -132,14 +92,72 @@ with the reviews of its working directory. `thurview setup skill` links the
|
|
|
132
92
|
skill from this checkout instead of the `skills` CLI copy; use one or the
|
|
133
93
|
other.
|
|
134
94
|
|
|
135
|
-
|
|
95
|
+
Also available: `review-fix`, the browserless companion skill described
|
|
96
|
+
[below](#review-and-fix).
|
|
136
97
|
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
|
|
98
|
+
```sh
|
|
99
|
+
npx skills@latest add https://github.com/Thurbeen/thurview \
|
|
100
|
+
--skill review-fix --agent universal claude-code --global --yes
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
</details>
|
|
104
|
+
|
|
105
|
+
## How it works
|
|
106
|
+
|
|
107
|
+
thurview validates every anchor against the pinned commits, seals a revision,
|
|
108
|
+
and serves it in your browser: the walkthrough, live code peeks, the diff,
|
|
109
|
+
commits, and a software map. You ask questions, leave anchored comments, and
|
|
110
|
+
approve or request changes. The agent answers and republishes.
|
|
111
|
+
|
|
112
|
+
It also explains a codebase. A **code explainer** is the same document over a
|
|
113
|
+
different unit: one pinned commit instead of a range, so a reader can see the
|
|
114
|
+
architecture well enough to spot design problems themselves. It has no diff and
|
|
115
|
+
nothing to approve, it surfaces structure rather than grading it, and it states
|
|
116
|
+
what it did not examine.
|
|
117
|
+
|
|
118
|
+
It does not review the code for you. It helps you understand it fast enough
|
|
119
|
+
to review it yourself.
|
|
120
|
+
|
|
121
|
+
```mermaid
|
|
122
|
+
flowchart LR
|
|
123
|
+
A[Branch, PR or range] --> B[Agent pins base and head]
|
|
124
|
+
B --> C[Agent authors review.md + data.yaml + map.yaml]
|
|
125
|
+
C --> D[thurview publish: validate, seal revision]
|
|
126
|
+
D --> E[You read, ask, comment in the browser]
|
|
127
|
+
E -->|Request changes| C
|
|
128
|
+
E -->|Approve| F[Done]
|
|
140
129
|
```
|
|
141
130
|
|
|
142
|
-
## What the reader
|
|
131
|
+
## What the reader sees
|
|
132
|
+
|
|
133
|
+
Prose with every claim anchored to code, opened beside the text:
|
|
134
|
+
|
|
135
|
+

|
|
137
|
+
|
|
138
|
+
The diff at the pinned commits, commenting on a selected line range:
|
|
139
|
+
|
|
140
|
+

|
|
142
|
+
|
|
143
|
+
The software map: where the change landed in the system, and what sits next to
|
|
144
|
+
it — the question the diff cannot answer:
|
|
145
|
+
|
|
146
|
+

|
|
148
|
+
|
|
149
|
+
Threads: a question the agent already answered, and a comment held for the
|
|
150
|
+
decision:
|
|
151
|
+
|
|
152
|
+

|
|
154
|
+
|
|
155
|
+
Approve, or send it back with the comments:
|
|
156
|
+
|
|
157
|
+

|
|
159
|
+
|
|
160
|
+
## What's in a review
|
|
143
161
|
|
|
144
162
|
- **Interface delta**: above the document, what the change added to, changed
|
|
145
163
|
in or removed from the surfaces other code can reach - exported functions
|
|
@@ -193,7 +211,7 @@ bar.
|
|
|
193
211
|
| `thurview wait --review ID [--timeout S]` | Block until the reader needs the agent |
|
|
194
212
|
| `thurview threads list\|get\|reply\|resolve` | Read and answer threads |
|
|
195
213
|
| `thurview graph interfaces\|impact\|callers\|tests-for\|architecture` | Ask the code graph at a review's pins, or at `--base`/`--head` |
|
|
196
|
-
| `thurview forge status\|prior\|submit\|reply`
|
|
214
|
+
| `thurview forge status\|prior\|pass\|submit\|reply` | Read a change request through its forge, and post the review back |
|
|
197
215
|
| `thurview serve` / `thurview stop` | Run the server in the foreground / stop the background one |
|
|
198
216
|
| `thurview setup hooks\|skill\|status` | Session hooks, agent skill, install state |
|
|
199
217
|
| `thurview update` | Self-update from npm |
|
|
@@ -232,6 +250,7 @@ on the change request, through `thurview forge`:
|
|
|
232
250
|
```sh
|
|
233
251
|
thurview forge status --change 123 # what CI actually did, and whether it is a gate at all
|
|
234
252
|
thurview forge prior --change 123 # the previous pass, thread by thread
|
|
253
|
+
thurview forge pass --review <id> # the reader's submitted threads, as the file below takes
|
|
235
254
|
thurview forge submit --change 123 --file pass.json --dry-run
|
|
236
255
|
thurview forge reply <threadId> --change 123 --body "<answer>" --resolve --at <head>
|
|
237
256
|
```
|
|
@@ -242,6 +261,14 @@ a change request from a fork typically runs a fraction of them, and a
|
|
|
242
261
|
cancelled job shows no failure while asserting nothing. `ci.trustworthy` is
|
|
243
262
|
the only field that means the tests really passed.
|
|
244
263
|
|
|
264
|
+
`pass` goes the other way, from a review a reader submitted in the browser to
|
|
265
|
+
that same file: one inline comment per thread anchored to a line, questions and
|
|
266
|
+
resolved threads left out, and threads with no line - a document block, a map
|
|
267
|
+
node, a whole file - gathered into the summary and named in the output, so
|
|
268
|
+
nobody assumes their comment was posted where they wrote it. The verdict comes
|
|
269
|
+
from the reader's decision, `close` becomes a `comment`, and an approve with
|
|
270
|
+
threads still open is refused rather than quietly downgraded.
|
|
271
|
+
|
|
245
272
|
`submit` takes one JSON file so a human can read the pass before it is posted,
|
|
246
273
|
refuses an `approve` without `--confirm`, and warns about comments too long to
|
|
247
274
|
be read. `reply --resolve` takes `--at <sha>` and refuses any commit but the
|
package/dist/cli.js
CHANGED
|
@@ -9,17 +9,18 @@ import { join, dirname, resolve } from "node:path";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import open from "open";
|
|
11
11
|
import * as g from "./git.js";
|
|
12
|
-
import { SCHEMA, home, newId, now, readReview, writeReview, listReviews, reviewsFor, reviewDir, revisionDir, readThreads, readText, writeText, writeJson, readJson, serverStateFile, deleteReview, kindOf, } from "./store.js";
|
|
12
|
+
import { SCHEMA, home, newId, now, readReview, writeReview, listReviews, reviewsFor, reviewDir, revisionDir, passFile, readThreads, readText, writeText, writeJson, readJson, serverStateFile, deleteReview, kindOf, } from "./store.js";
|
|
13
13
|
import { compileDocument, compileMap, globToRegExp } from "./document/compile.js";
|
|
14
14
|
import { computeCoverage, scopeGlob, scopeGraph, scopeTruncated, } from "./coverage.js";
|
|
15
15
|
import { parseTheme, compileTheme } from "./theme.js";
|
|
16
16
|
import { registerTheme } from "./highlight.js";
|
|
17
17
|
import { replyThread, setThreadStatus, needsAgent } from "./threads.js";
|
|
18
|
+
import { targetLabel, truncate } from "./thread-state.js";
|
|
18
19
|
import { attach } from "./presence.js";
|
|
19
20
|
import { startServer } from "./server/server.js";
|
|
20
21
|
import { parseFlags, helpFor, str, bool } from "./flags.js";
|
|
21
22
|
import { forgeFor, repoOf, summariseCi, } from "./forge/index.js";
|
|
22
|
-
import { parseSubmission, longComments } from "./forge/submission.js";
|
|
23
|
+
import { parseSubmission, longComments, buildPass } from "./forge/submission.js";
|
|
23
24
|
import { VERSION } from "./version.js";
|
|
24
25
|
const execFileP = promisify(execFile);
|
|
25
26
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
@@ -30,24 +31,6 @@ function note(msg) {
|
|
|
30
31
|
function short(id) {
|
|
31
32
|
return id.slice(0, 8);
|
|
32
33
|
}
|
|
33
|
-
function truncate(text, max) {
|
|
34
|
-
return text.length > max
|
|
35
|
-
? `${text.slice(0, max)}... (truncated, ${text.length} chars total)`
|
|
36
|
-
: text;
|
|
37
|
-
}
|
|
38
|
-
function targetLabel(t) {
|
|
39
|
-
if (t.type === "document")
|
|
40
|
-
return `document${t.quote ? ` "${truncate(t.quote, 40)}"` : ""}`;
|
|
41
|
-
if (t.type === "file") {
|
|
42
|
-
if (!t.line)
|
|
43
|
-
return `${t.path} (file)`;
|
|
44
|
-
const range = t.endLine && t.endLine > t.line ? `${t.line}-${t.endLine}` : String(t.line);
|
|
45
|
-
return `${t.path}:${range}${t.side === "base" ? " (base)" : ""}`;
|
|
46
|
-
}
|
|
47
|
-
if (t.type === "map")
|
|
48
|
-
return `map ${t.node}`;
|
|
49
|
-
return "review";
|
|
50
|
-
}
|
|
51
34
|
/** `PR #12` or `MR !12`, because the forge's own word is what the reader knows. */
|
|
52
35
|
function bindingLabel(b) {
|
|
53
36
|
if (b.kind !== "pr")
|
|
@@ -77,7 +60,13 @@ async function worktreeOf(cwd) {
|
|
|
77
60
|
return null;
|
|
78
61
|
}
|
|
79
62
|
}
|
|
80
|
-
|
|
63
|
+
/**
|
|
64
|
+
* `terminal` keeps an approved or closed review in the search. `forge pass`
|
|
65
|
+
* needs it: approve and close are two of the three decisions it carries, and
|
|
66
|
+
* both of them end the review, so without it the command cannot find the very
|
|
67
|
+
* review it was asked about unless the id is spelled out.
|
|
68
|
+
*/
|
|
69
|
+
async function resolveReview(idOpt, opts = {}) {
|
|
81
70
|
if (idOpt) {
|
|
82
71
|
const r = await readReview(idOpt);
|
|
83
72
|
if (r)
|
|
@@ -92,7 +81,7 @@ async function resolveReview(idOpt) {
|
|
|
92
81
|
throw new AxiError("not inside a git repository", "VALIDATION_ERROR", [
|
|
93
82
|
"Run inside the source worktree, or pass --review <id>",
|
|
94
83
|
]);
|
|
95
|
-
const mine = (await reviewsFor(worktree)).filter((r) => !r.dismissed && r.status !== "accepted" && r.status !== "closed");
|
|
84
|
+
const mine = (await reviewsFor(worktree)).filter((r) => !r.dismissed && (opts.terminal || (r.status !== "accepted" && r.status !== "closed")));
|
|
96
85
|
if (mine.length === 1)
|
|
97
86
|
return mine[0];
|
|
98
87
|
if (mine.length)
|
|
@@ -478,7 +467,7 @@ const SPECS = {
|
|
|
478
467
|
},
|
|
479
468
|
forge: {
|
|
480
469
|
description: "Read a pull or merge request through its forge, and post the review back to it",
|
|
481
|
-
args: 'status|prior|submit --file <path>|reply <threadId> --body "<text>"',
|
|
470
|
+
args: 'status|prior|pass|submit --file <path>|reply <threadId> --body "<text>"',
|
|
482
471
|
flags: {
|
|
483
472
|
review: { kind: "string", help: "review id prefix; its binding names the change request" },
|
|
484
473
|
change: { kind: "string", help: "change request number or URL, instead of a review binding" },
|
|
@@ -488,6 +477,10 @@ const SPECS = {
|
|
|
488
477
|
},
|
|
489
478
|
repo: { kind: "string", help: "host/path, when `origin` is not the repository to post to" },
|
|
490
479
|
file: { kind: "string", help: "submit: the JSON submission to post" },
|
|
480
|
+
out: {
|
|
481
|
+
kind: "string",
|
|
482
|
+
help: "pass: where to write the submission (default: ~/.thurview/passes/<id>.json)",
|
|
483
|
+
},
|
|
491
484
|
body: { kind: "string", help: "reply: the answer text" },
|
|
492
485
|
resolve: { kind: "boolean", help: "reply: resolve the thread as well as answering it" },
|
|
493
486
|
at: { kind: "string", help: "reply --resolve: the commit the point was verified at" },
|
|
@@ -504,6 +497,7 @@ const SPECS = {
|
|
|
504
497
|
examples: [
|
|
505
498
|
"thurview forge status --change 123",
|
|
506
499
|
"thurview forge prior --change 123 --mine",
|
|
500
|
+
"thurview forge pass --review <id>",
|
|
507
501
|
"thurview forge submit --file pass.json --dry-run",
|
|
508
502
|
'thurview forge reply <threadId> --body "<answer>" --resolve --at <sha>',
|
|
509
503
|
],
|
|
@@ -1500,10 +1494,11 @@ const commands = {
|
|
|
1500
1494
|
const usage = [
|
|
1501
1495
|
"thurview forge status [--change <ref>] [--review <id>]",
|
|
1502
1496
|
"thurview forge prior [--change <ref>] [--mine] [--full]",
|
|
1497
|
+
"thurview forge pass [--review <id>] [--out <path>]",
|
|
1503
1498
|
"thurview forge submit --file <path> [--dry-run] [--confirm]",
|
|
1504
1499
|
'thurview forge reply <threadId> --body "<text>" [--resolve --at <sha>]',
|
|
1505
1500
|
];
|
|
1506
|
-
if (!sub || !["status", "prior", "submit", "reply"].includes(sub))
|
|
1501
|
+
if (!sub || !["status", "prior", "pass", "submit", "reply"].includes(sub))
|
|
1507
1502
|
throw new AxiError(`unknown forge command${sub ? ` ${sub}` : ""}`, "VALIDATION_ERROR", usage);
|
|
1508
1503
|
const common = {
|
|
1509
1504
|
review: s["review"],
|
|
@@ -1618,6 +1613,70 @@ const commands = {
|
|
|
1618
1613
|
: ["This is the first pass; there is no prior review to answer"],
|
|
1619
1614
|
};
|
|
1620
1615
|
}
|
|
1616
|
+
if (sub === "pass") {
|
|
1617
|
+
const p = parseFlags("forge pass", rest, { review: s["review"], out: s["out"] });
|
|
1618
|
+
// Approve and close both end the review, so a pass has to be able to
|
|
1619
|
+
// reach a finished one - but only when no active review answers first,
|
|
1620
|
+
// or a review the reader finished last week shadows the one in hand.
|
|
1621
|
+
const review = await resolveReview(str(p, "review")).catch((e) => {
|
|
1622
|
+
if (e instanceof AxiError && e.code === "NOT_FOUND")
|
|
1623
|
+
return resolveReview(str(p, "review"), { terminal: true });
|
|
1624
|
+
throw e;
|
|
1625
|
+
});
|
|
1626
|
+
const t = await readThreads(review.id);
|
|
1627
|
+
const id = short(review.id);
|
|
1628
|
+
const decision = t.decisions[t.decisions.length - 1];
|
|
1629
|
+
if (!decision)
|
|
1630
|
+
throw new AxiError(`review ${id} has no decision to carry to the forge`, "NOT_FOUND", [
|
|
1631
|
+
"The reader decides in the browser; nothing is posted before they submit",
|
|
1632
|
+
`Run \`thurview wait --review ${id}\` to block until they do`,
|
|
1633
|
+
]);
|
|
1634
|
+
const plan = buildPass(t.threads, decision);
|
|
1635
|
+
const out = resolve(process.cwd(), str(p, "out") ?? passFile(review.id));
|
|
1636
|
+
const text = JSON.stringify(plan.submission, null, 2) + "\n";
|
|
1637
|
+
// Checked by the parser `submit` reads it with, so a file this wrote is
|
|
1638
|
+
// never one that command refuses.
|
|
1639
|
+
parseSubmission(text, out);
|
|
1640
|
+
await writeText(out, text);
|
|
1641
|
+
return {
|
|
1642
|
+
pass: {
|
|
1643
|
+
review: id,
|
|
1644
|
+
file: out,
|
|
1645
|
+
decision: plan.decision,
|
|
1646
|
+
verdict: plan.submission.verdict,
|
|
1647
|
+
...(plan.verdictReason ? { why: plan.verdictReason } : {}),
|
|
1648
|
+
inline: plan.inline.length,
|
|
1649
|
+
summary: plan.summary.length,
|
|
1650
|
+
skipped: plan.skipped.length,
|
|
1651
|
+
anchoredAt: review.pins.head,
|
|
1652
|
+
},
|
|
1653
|
+
comments: plan.inline.length
|
|
1654
|
+
? plan.inline.map((c) => ({ thread: c.thread, at: c.at, side: c.side }))
|
|
1655
|
+
: "0 (a summary-only pass)",
|
|
1656
|
+
summary: plan.summary.length
|
|
1657
|
+
? plan.summary.map((x) => ({ thread: x.thread, target: x.target, why: x.why }))
|
|
1658
|
+
: "0 (every comment is anchored to a line)",
|
|
1659
|
+
skipped: plan.skipped.length
|
|
1660
|
+
? plan.skipped.map((x) => ({ thread: x.thread, target: x.target, why: x.why }))
|
|
1661
|
+
: "0 (no question, resolved or held thread to leave out)",
|
|
1662
|
+
help: [
|
|
1663
|
+
`Read ${out} before it is posted; the file is the thing a human checks`,
|
|
1664
|
+
`Every anchor is a line at ${review.pins.head.slice(0, 12)}; run \`thurview forge status --review ${id}\` first, because a forge refuses a comment on a line its current head does not have`,
|
|
1665
|
+
`Run \`thurview forge submit --review ${id} --file ${out} --dry-run\` to see what would reach the change request`,
|
|
1666
|
+
"Tell the reader which comments went to the summary, and why they are not on their line",
|
|
1667
|
+
...(decision.revision === review.revision
|
|
1668
|
+
? []
|
|
1669
|
+
: [
|
|
1670
|
+
`The decision is the reader's on revision ${decision.revision} and the review is at ${review.revision}; they have not judged what you published since`,
|
|
1671
|
+
]),
|
|
1672
|
+
...(review.binding.kind === "pr"
|
|
1673
|
+
? []
|
|
1674
|
+
: [
|
|
1675
|
+
`This review is bound to ${review.binding.name}, not to a change request; \`forge submit\` will need --change <ref>`,
|
|
1676
|
+
]),
|
|
1677
|
+
],
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1621
1680
|
if (sub === "submit") {
|
|
1622
1681
|
const p = parseFlags("forge submit", rest, {
|
|
1623
1682
|
...common,
|