thurview 0.9.0 → 0.11.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 +39 -10
- package/dist/cli.js +79 -40
- package/dist/cli.js.map +1 -1
- package/dist/coverage.js +5 -2
- package/dist/coverage.js.map +1 -1
- package/dist/graph.js +86 -21
- package/dist/graph.js.map +1 -1
- package/dist/interfaces.js +16 -3
- package/dist/interfaces.js.map +1 -1
- package/package.json +2 -1
- package/skills/review-fix/SKILL.md +165 -0
- package/skills/thurview/SKILL.md +4 -4
- package/skills/thurview/references/code-explainer.md +1 -1
- package/skills/forge-review/SKILL.md +0 -276
- package/skills/forge-review/references/security-surfaces.md +0 -54
- /package/skills/{forge-review → review-fix}/references/forges.md +0 -0
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: forge-review
|
|
3
|
-
description: Review a pull or merge request and post the review back to the forge - inline comments anchored to lines, a summary, a verdict, and a re-review that answers the previous pass point by point. Use when the user asks to review a PR or MR and post the comments, to re-review after a push, to approve or request changes on a pull or merge request, or invokes /forge-review. Built on thurview for the evidence, with the forge as the destination.
|
|
4
|
-
user-invocable: true
|
|
5
|
-
argument-hint: "[<number|url>] [--forge github|gitlab]"
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# forge-review
|
|
9
|
-
|
|
10
|
-
thurview authors the evidence; the forge is where the review lands. This skill
|
|
11
|
-
is the bridge between them, and it is the only one that posts.
|
|
12
|
-
|
|
13
|
-
```mermaid
|
|
14
|
-
flowchart LR
|
|
15
|
-
A[forge status: what CI really did] --> B[forge prior: the previous pass]
|
|
16
|
-
B --> C[scaffold + graph: the evidence]
|
|
17
|
-
C --> D[publish: the reader approves the review before it is posted]
|
|
18
|
-
D --> E[forge submit: comments, summary, verdict]
|
|
19
|
-
E --> F[forge reply --resolve: only what is verified]
|
|
20
|
-
F -->|author pushes| A
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Run the CLI as `thurview`, or `npx -y thurview` when it is not on PATH. Every
|
|
24
|
-
command prints TOON on stdout, errors are structured on stdout too, exit code
|
|
25
|
-
2 is a usage error. If a command answers `unknown command` or `unknown flag`
|
|
26
|
-
for something below, the installed CLI is older than this skill: run
|
|
27
|
-
`thurview update` and retry once.
|
|
28
|
-
|
|
29
|
-
## Request
|
|
30
|
-
|
|
31
|
-
$ARGUMENTS
|
|
32
|
-
|
|
33
|
-
A number or URL names the change request. Empty means the change request open
|
|
34
|
-
from the current branch - `thurview forge status` with no `--change` finds it
|
|
35
|
-
through the active review's binding, and when there is none, ask which one
|
|
36
|
-
rather than guessing.
|
|
37
|
-
|
|
38
|
-
## The word
|
|
39
|
-
|
|
40
|
-
GitHub calls it a pull request, GitLab a merge request. Everything here calls
|
|
41
|
-
it a **change request** and the CLI takes `--change <number|url>` on either
|
|
42
|
-
forge. Use the forge's own word only when you are writing to the author on
|
|
43
|
-
that forge.
|
|
44
|
-
|
|
45
|
-
Forge coverage, and what a third forge would need, is in
|
|
46
|
-
[Forges](references/forges.md). Read it when a call fails or the forge is not
|
|
47
|
-
github.com.
|
|
48
|
-
|
|
49
|
-
## What this never does
|
|
50
|
-
|
|
51
|
-
Never merge, never close, never push to the branch under review. You usually
|
|
52
|
-
cannot push to a fork at all, so **every finding is a comment**. Merging is
|
|
53
|
-
the maintainer's decision and the CLI has no command for it.
|
|
54
|
-
|
|
55
|
-
## Before you start
|
|
56
|
-
|
|
57
|
-
Read `~/.agent-rules/VOICE.md` **on every run**. Every comment, reply and
|
|
58
|
-
summary is published on the operator's account, and that file is the spec for
|
|
59
|
-
how they read - the sign-off line and its exact wording, the length budget per
|
|
60
|
-
comment, severity prefixes, and when to ask instead of assert. It changes; do
|
|
61
|
-
not work from memory of it, and do not copy it in here.
|
|
62
|
-
|
|
63
|
-
Two consequences of it shape everything below, so they are worth saying once:
|
|
64
|
-
a comment is a few lines and no more, and it ends with the sign-off as a plain
|
|
65
|
-
last line. A finding that will not fit is **two comments**, or one comment
|
|
66
|
-
carrying the claim and one suggestion with the evidence behind a permalink -
|
|
67
|
-
never a wall of prose on a line of someone's diff.
|
|
68
|
-
|
|
69
|
-
## Workflow
|
|
70
|
-
|
|
71
|
-
### 1. Ask the forge what it knows, before forming any opinion
|
|
72
|
-
|
|
73
|
-
```sh
|
|
74
|
-
thurview forge status --change <ref>
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Record `change.head`. That commit is what this pass reviews, and a later pass
|
|
78
|
-
diffs against it to find what moved. Note `change.fromFork` and
|
|
79
|
-
`change.author`: a change request from outside the organisation is the case
|
|
80
|
-
every rule here was learned on.
|
|
81
|
-
|
|
82
|
-
### 2. Establish what CI actually ran
|
|
83
|
-
|
|
84
|
-
`ci.verdict` is one sentence you can quote. `ci.trustworthy` is the only field
|
|
85
|
-
that means "the tests really passed here".
|
|
86
|
-
|
|
87
|
-
Two failures this answers, both of which shipped real bugs:
|
|
88
|
-
|
|
89
|
-
- **A fork change request has almost no CI.** `ci.baselineRan` is what the
|
|
90
|
-
target branch's own tip runs. One check here against twenty there means the
|
|
91
|
-
pipeline is not a gate on this change - the review is.
|
|
92
|
-
- **A green tick can mean "never ran".** `passed`, `failed`, `cancelled`,
|
|
93
|
-
`skipped` and `running` are counted separately because a cancelled job
|
|
94
|
-
asserted nothing while showing no failure. A title-gate failure that
|
|
95
|
-
cancels the test matrix leaves exactly that shape.
|
|
96
|
-
|
|
97
|
-
When `ci.trustworthy` is false, **say so in the summary comment in your own
|
|
98
|
-
words, with the counts**. An unstated gap is one the maintainer will assume
|
|
99
|
-
you checked.
|
|
100
|
-
|
|
101
|
-
### 3. Read the previous pass back, point by point
|
|
102
|
-
|
|
103
|
-
```sh
|
|
104
|
-
thurview forge prior --change <ref> # add --mine for this account's threads
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
`summary.passes` of 0 means this is a first pass; skip to step 4.
|
|
108
|
-
|
|
109
|
-
Otherwise this is a **re-review, and answering the prior pass is the most
|
|
110
|
-
important thing you will do here**. A point raised and then silently dropped
|
|
111
|
-
teaches the author that review is noise.
|
|
112
|
-
|
|
113
|
-
Go through every open thread and give it exactly one of three words:
|
|
114
|
-
|
|
115
|
-
| Word | What you do |
|
|
116
|
-
| ------------------- | --------------------------------------------------------------- |
|
|
117
|
-
| addressed | Verify it at the current head, then reply and resolve (step 8) |
|
|
118
|
-
| partially addressed | Reply saying which part is done and which is not; leave it open |
|
|
119
|
-
| untouched | Reply asking for it again, or say why you are dropping it |
|
|
120
|
-
|
|
121
|
-
`threads[].atHead` is false when the thread was written against an older
|
|
122
|
-
commit, and `outdated` when the code under it moved. Neither means the point
|
|
123
|
-
was fixed - only reading the code at the current head means that.
|
|
124
|
-
|
|
125
|
-
### 4. Diff only what moved
|
|
126
|
-
|
|
127
|
-
On a re-review, read the new work rather than the whole change again:
|
|
128
|
-
|
|
129
|
-
```sh
|
|
130
|
-
git range-diff <base>...<previous head> <base>...<current head>
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
The previous head is the one you recorded last pass, or `review.pinnedHead`
|
|
134
|
-
from `forge status`. Read the full diff only on a first pass.
|
|
135
|
-
|
|
136
|
-
### 5. Get the evidence from thurview
|
|
137
|
-
|
|
138
|
-
```sh
|
|
139
|
-
thurview scaffold --pr <ref> # pins base and head from the forge
|
|
140
|
-
thurview graph interfaces --review <id> # what the change moved in the visible surface
|
|
141
|
-
thurview graph impact --review <id> # what it reaches that the diff does not show
|
|
142
|
-
thurview graph callers <symbol> --review <id>
|
|
143
|
-
thurview graph tests-for <symbol> --review <id>
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
The thurview skill owns these in full - `thurview skill` prints the path to
|
|
147
|
-
its SKILL.md, and its references sit beside it. Read them when you author the
|
|
148
|
-
document in step 6; do not re-derive structure from hunks.
|
|
149
|
-
|
|
150
|
-
Read every line you are about to comment on **at the pinned head**, with
|
|
151
|
-
`git show <head>:<path>`, never from the working tree.
|
|
152
|
-
|
|
153
|
-
### 6. Decide who reads the review before the forge does
|
|
154
|
-
|
|
155
|
-
Default: **a human approves the pass before it is posted.** Author the
|
|
156
|
-
thurview document as the thurview skill describes, publish it, and wait:
|
|
157
|
-
|
|
158
|
-
```sh
|
|
159
|
-
thurview publish --review <id> --open
|
|
160
|
-
thurview wait --review <id> --timeout <seconds>
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
The reader sees every finding against its anchored code and approves or sends
|
|
164
|
-
it back; `wait.reason` of `accepted` is your signal to post. This is the whole
|
|
165
|
-
reason to go through thurview rather than straight to `gh`: comments land on
|
|
166
|
-
the operator's account, on a contributor's work, and are read as the
|
|
167
|
-
maintainer's word.
|
|
168
|
-
|
|
169
|
-
Post without that gate only when the user asked for an unattended run. Say in
|
|
170
|
-
the handover which of the two happened.
|
|
171
|
-
|
|
172
|
-
**Never put the thurview URL in a forge comment.** That server is local to
|
|
173
|
-
this machine; the author cannot open it, and the link leaks a path. Evidence
|
|
174
|
-
that must travel goes in a permalink - `status.permalink` shows the shape for
|
|
175
|
-
this forge, with the pinned head already in it.
|
|
176
|
-
|
|
177
|
-
### 7. Write the pass
|
|
178
|
-
|
|
179
|
-
One JSON file, which is also what a human can read before it is posted:
|
|
180
|
-
|
|
181
|
-
```json
|
|
182
|
-
{
|
|
183
|
-
"verdict": "request-changes",
|
|
184
|
-
"body": "<the summary, in VOICE.md's shape, ending with the sign-off>",
|
|
185
|
-
"comments": [
|
|
186
|
-
{
|
|
187
|
-
"path": "src/clip.rs",
|
|
188
|
-
"line": 44,
|
|
189
|
-
"startLine": 40,
|
|
190
|
-
"side": "head",
|
|
191
|
-
"body": "<one point, ending with the sign-off>"
|
|
192
|
-
}
|
|
193
|
-
]
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
`line` is the LAST line of the range and `startLine` the first. `side` is
|
|
198
|
-
`head` unless you are commenting on a line the change deleted. Both forges
|
|
199
|
-
refuse a comment on a line the diff does not touch, so anchor inside a hunk.
|
|
200
|
-
|
|
201
|
-
Per comment: one point, stated as a problem then a concrete suggestion,
|
|
202
|
-
within VOICE.md's length budget, sign-off last. **A finding you cannot
|
|
203
|
-
reproduce is a question, not an assertion** - give the mechanism and the
|
|
204
|
-
evidence, say what you could not reproduce, and ask the author to confirm.
|
|
205
|
-
That is the correct form, not a weaker one.
|
|
206
|
-
|
|
207
|
-
The summary body carries what has no line: what CI did and did not establish
|
|
208
|
-
(step 2), the security result (step 5 of
|
|
209
|
-
[Security surfaces](references/security-surfaces.md)), and who does what next.
|
|
210
|
-
|
|
211
|
-
Check it before it goes anywhere:
|
|
212
|
-
|
|
213
|
-
```sh
|
|
214
|
-
thurview forge submit --change <ref> --file pass.json --dry-run
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
`warnings` names every comment past the line budget. Split those, do not
|
|
218
|
-
shorten by deleting the suggestion.
|
|
219
|
-
|
|
220
|
-
### 8. Submit
|
|
221
|
-
|
|
222
|
-
```sh
|
|
223
|
-
thurview forge submit --change <ref> --file pass.json
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
`verdict` is `comment`, `request-changes` or `approve`.
|
|
227
|
-
|
|
228
|
-
**Approving is a state change with consequences, and you say them out loud
|
|
229
|
-
before you do it.** It dismisses a standing request for changes, which is what
|
|
230
|
-
makes the change request mergeable, and where auto-merge is armed it merges
|
|
231
|
-
the code with no further human read. The CLI refuses an approve without
|
|
232
|
-
`--confirm` for that reason; the flag is not a formality, it is the point at
|
|
233
|
-
which you have told the user.
|
|
234
|
-
|
|
235
|
-
Record `submitted.head`. That is the commit the next pass diffs against.
|
|
236
|
-
|
|
237
|
-
### 9. Resolve only what you verified
|
|
238
|
-
|
|
239
|
-
```sh
|
|
240
|
-
thurview forge reply <threadId> --change <ref> --body "<answer>" --resolve --at <head>
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
`--at` must be the current head, and the CLI refuses any other. Resolving a
|
|
244
|
-
thread tells the author a point was accepted; doing it without reading the
|
|
245
|
-
code at that head is worse than leaving it open. A thread deferred by
|
|
246
|
-
agreement may be resolved only when the agreement is written in the thread.
|
|
247
|
-
|
|
248
|
-
Reply without `--resolve` for a point that is partially addressed.
|
|
249
|
-
|
|
250
|
-
### 10. Hand over
|
|
251
|
-
|
|
252
|
-
Tell the user, in a few lines:
|
|
253
|
-
|
|
254
|
-
- the change request, its head, and the verdict you posted
|
|
255
|
-
- what CI established, in one clause, when `ci.trustworthy` was false
|
|
256
|
-
- how many comments, and how many prior threads you resolved
|
|
257
|
-
- whether a human approved the pass first, or it was unattended
|
|
258
|
-
- what you are waiting for now - the author's push, or the maintainer's merge
|
|
259
|
-
|
|
260
|
-
## Re-review after a push
|
|
261
|
-
|
|
262
|
-
Start at step 1 again. The head will have moved; `forge status` says so and
|
|
263
|
-
`review.pinnedHead` holds what you reviewed last. Re-pin with
|
|
264
|
-
`thurview scaffold --update --review <id>`, range-diff from the old head, and
|
|
265
|
-
answer the prior pass before reading anything new.
|
|
266
|
-
|
|
267
|
-
## Completion criteria
|
|
268
|
-
|
|
269
|
-
Report completion only when all of these hold:
|
|
270
|
-
|
|
271
|
-
- `forge status` was read and its CI verdict is reflected in the summary.
|
|
272
|
-
- Every prior thread is marked addressed, partially addressed or untouched.
|
|
273
|
-
- Security is stated explicitly, findings or none.
|
|
274
|
-
- The pass is posted, with a verdict, and its head is recorded.
|
|
275
|
-
- Every thread you resolved was verified at the current head.
|
|
276
|
-
- Nothing was merged, closed or pushed.
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# Security surfaces
|
|
2
|
-
|
|
3
|
-
A generic security pass produces generic findings. The checklist has to come
|
|
4
|
-
from what the change actually touches, so derive it from the diff and say what
|
|
5
|
-
you derived it from.
|
|
6
|
-
|
|
7
|
-
## How to derive it
|
|
8
|
-
|
|
9
|
-
1. List the surfaces the diff crosses. A surface is a place where the change
|
|
10
|
-
meets something it does not control - input it did not produce, a file
|
|
11
|
-
system, another process, a network peer, a platform API, a log.
|
|
12
|
-
2. For each surface, take its questions from the table below.
|
|
13
|
-
3. Ask each question against the code at the pinned head, not against the
|
|
14
|
-
hunk. A missing cleanup path is invisible in a diff that only adds lines.
|
|
15
|
-
4. Anchor every finding to the line that answers it.
|
|
16
|
-
5. **State the result explicitly, findings or none.** "No security findings"
|
|
17
|
-
is a result; an absent section is not one, and the maintainer cannot tell
|
|
18
|
-
the two apart.
|
|
19
|
-
|
|
20
|
-
## Surfaces and their questions
|
|
21
|
-
|
|
22
|
-
| The change touches | Ask |
|
|
23
|
-
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
-
| Text that reaches a model | Prompt injection - whose text is it, what can it make the agent do, what is quoted versus interpreted |
|
|
25
|
-
| Temporary files | Predictable name, permissions at creation, symlink races, TOCTOU between check and use, cleanup on every error path as well as the happy one |
|
|
26
|
-
| Attacker-controlled names | Path traversal, absolute paths, shell metacharacters, leading dashes read as flags, Unicode that normalises to something else |
|
|
27
|
-
| Reading untrusted bytes | Unbounded reads, allocation from a length the input chose, decompression ratios, what happens at the size limit rather than below it |
|
|
28
|
-
| Running another process | Argument construction, quoting per platform, whether a shell is involved at all, what the environment carries, working directory |
|
|
29
|
-
| Process or PTY lifetime | Orphans on error, exit status that can be forged or lost, signals, what happens when the child outlives the parent |
|
|
30
|
-
| Logs and telemetry | What reaches a log that should not - secrets, tokens, file contents, personal data - and whether log lines can be forged by input |
|
|
31
|
-
| Platform branches | Each branch checked separately; a guard that holds on Linux and not on Windows is the common shape |
|
|
32
|
-
| Authentication or identity | What is trusted, what is verified, what a caller can assert about itself |
|
|
33
|
-
| Serialised data | Deserialisation of attacker-controlled shapes, schema validation before use, defaults that silently accept |
|
|
34
|
-
|
|
35
|
-
## Two worked examples
|
|
36
|
-
|
|
37
|
-
**A clipboard copy through a temporary file.** Surfaces: text that reaches a
|
|
38
|
-
model, temporary files, attacker-controlled names, unbounded reads, running
|
|
39
|
-
another process, platform branches, logs. Which yields, concretely - is the
|
|
40
|
-
temp file name predictable; what mode is it created with; is there a window
|
|
41
|
-
between creating and writing it; is it removed when the copy fails and not
|
|
42
|
-
only when it succeeds; can the copied text be read back by another user; does
|
|
43
|
-
the platform helper get its argument as an argument or through a shell; does
|
|
44
|
-
the content reach a log.
|
|
45
|
-
|
|
46
|
-
**A window lifecycle change that spawns a process.** Surfaces: running another
|
|
47
|
-
process, process lifetime, attacker-controlled names, platform branches.
|
|
48
|
-
Which yields - how the command line is built and escaped on each platform;
|
|
49
|
-
whether a window name is interpolated into it; whether the child is reaped;
|
|
50
|
-
whether its exit status can be forged by something the child does not control;
|
|
51
|
-
what the code does when the platform branch it was not written for runs.
|
|
52
|
-
|
|
53
|
-
The pattern in both: the surfaces come from the diff, the questions come from
|
|
54
|
-
the surfaces, and the answers come from the code at head.
|
|
File without changes
|