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