thurview 0.9.0 → 0.10.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.
@@ -0,0 +1,165 @@
1
+ ---
2
+ name: review-fix
3
+ description: Review a change and fix what the review finds - bugs, regressions for callers, missing or broken tests, security issues - with thurview's code graph showing the callers and tests a diff does not. Commits only the fixes that pass the repository's own tests and lint, and reports the rest. Use when the user asks to review and fix a branch, a commit range or a pull or merge request, to find and fix bugs in a change, or invokes /review-fix.
4
+ user-invocable: true
5
+ argument-hint: "[<branch> | <base>..<head> | <PR or MR number or URL>] [--post]"
6
+ ---
7
+
8
+ # review-fix
9
+
10
+ Review a change, fix what you are sure of, report the rest. The diff shows what
11
+ changed; thurview's code graph shows what depends on it, which is where a
12
+ change breaks code the diff never shows.
13
+
14
+ ```mermaid
15
+ flowchart LR
16
+ A[Scope: pin base and head] --> B[Graph: who reaches the change]
17
+ B --> C[Findings]
18
+ C --> D[Fix, then the repo's tests and lint]
19
+ D -->|green| E[One fix commit]
20
+ D -->|red| F[Undo that fix, report why]
21
+ E --> G[Report]
22
+ F --> G
23
+ G -.->|--post| H[Unfixed findings as inline comments]
24
+ ```
25
+
26
+ Run the CLI as `thurview`, or `npx -y thurview` when it is not on PATH. It
27
+ prints TOON, and exit code 2 is a usage error. If it answers `unknown flag` for
28
+ something below, run `thurview update` and retry once.
29
+
30
+ ## Request
31
+
32
+ $ARGUMENTS
33
+
34
+ ## Never
35
+
36
+ - Never push, force-push or post unless this request asks for it. `--post`
37
+ asks to post comments; pushing the fix commit needs its own ask.
38
+ - Never rewrite the branch's history. Fixes are a new commit on top.
39
+ - Never report style. A finding is a bug, a regression for a caller, a missing
40
+ or broken test, or a security issue.
41
+
42
+ ## 1. Scope
43
+
44
+ Start from a clean tree (`git status --porcelain` prints nothing), otherwise
45
+ ask: the fixes get committed. Check out the head, then pin both commits with
46
+ one graph call:
47
+
48
+ | Request | Check out | Pin with |
49
+ | ------------------ | ---------------------------------------------- | --------------------------------------------------------------------------------- |
50
+ | empty | the current branch | `thurview graph impact --head HEAD` |
51
+ | a branch | `git switch <branch>` | `thurview graph impact --head HEAD` |
52
+ | `<base>..<head>` | the branch at `<head>` | `thurview graph impact --base <base> --head HEAD` |
53
+ | a PR or MR ref/URL | `gh pr checkout <n>` or `glab mr checkout <n>` | `thurview graph impact --base $(git merge-base origin/<target> HEAD) --head HEAD` |
54
+
55
+ With `--head` alone the base is where head forked from trunk. For a change
56
+ request, `thurview forge status --change <ref>` names `<target>` as
57
+ `change.baseBranch`.
58
+
59
+ The output's `base` and `head` are the pins. Pass `--base <base> --head <head>`
60
+ to every later graph command, as its `help` lines do, so your fix commit does
61
+ not move what you are reviewing.
62
+
63
+ ## 2. Ask the graph
64
+
65
+ ```sh
66
+ thurview graph impact --base <base> --head <head> # changed symbols, who reaches them, tests
67
+ thurview graph interfaces --base <base> --head <head> # exports and signatures that moved
68
+ thurview graph callers <name> --base <base> --head <head> # every call site; --graph base for before
69
+ thurview graph tests-for <name> --base <base> --head <head>
70
+ ```
71
+
72
+ What to take from them:
73
+
74
+ - **`impact.reach`** lists code that calls a changed symbol and was not changed
75
+ itself - what the author may have forgotten. `at` is the line of the call,
76
+ `via` the changed symbol it reaches. Read each call site against the new
77
+ behaviour: this is the finding a diff cannot give you.
78
+ - **`reach[].tested: false`**: no test reaches that caller, so nothing catches
79
+ a break there.
80
+ - **`interfaces`** rows `changed` or `removed`: run `callers` on each, with
81
+ `--graph base` for a removed one, since head no longer has its callers.
82
+ - **`impact.untested`**: changed symbols no test reaches.
83
+ - **`unresolved`, `truncated`**: references the graph could not resolve and
84
+ files past its cap. "No callers" is only as true as those allow; say so when
85
+ a finding rests on it.
86
+
87
+ Then read the diff (`git diff <base> <head>`) and every call site the rows name.
88
+
89
+ ## 3. Findings
90
+
91
+ For each one, record:
92
+
93
+ - `file:line` at `<head>`, now, before a fix moves lines
94
+ - severity: `high` (wrong behaviour on a normal path, data loss, security),
95
+ `medium` (a likely bug, or a risky path no test reaches), `low` (real but
96
+ narrow)
97
+ - why, in one line
98
+ - the graph evidence when there is some, e.g.
99
+ `reach: checkout src/cart.js:5 via discount, tested false`
100
+
101
+ Security means input crossing a trust boundary: a shell command, query or path
102
+ built from it, a secret reaching a log, an authorization check the change
103
+ skips.
104
+
105
+ A problem that is just as present at `<base>` is not this change's finding.
106
+ Leave it unfixed and list it after the report's table.
107
+
108
+ ## 4. Fix
109
+
110
+ Find the repository's own test and lint commands - `CONTRIBUTING.md`,
111
+ `AGENTS.md`, the package manifest's scripts, a `Makefile`, the CI config - and
112
+ run them once before editing. What is already red at head is not yours: note
113
+ it, and judge each fix by not making it worse.
114
+
115
+ For each finding whose fix is local and whose right behaviour is unambiguous:
116
+
117
+ 1. For a bug, first add or extend a test and run it: it must fail, for the
118
+ reason the finding gives.
119
+ 2. Edit, then run the tests and lint.
120
+ 3. Green: keep it. Red: undo only that fix (`git restore <files>`, and delete
121
+ files it created) and mark the finding unfixed, with the failure as why.
122
+
123
+ Leave a finding unfixed when it needs a decision the code cannot make, changes
124
+ an interface used outside this repository, or no test can show it.
125
+
126
+ Once every kept fix passes together, commit them as one. Follow the
127
+ repository's commit convention when it has one; otherwise:
128
+
129
+ ```sh
130
+ git add <files>
131
+ git commit -m "fix: address review findings" -m "<one line per fix: file:line - why>"
132
+ ```
133
+
134
+ ## 5. Report
135
+
136
+ One table - severity, `file:line`, the finding, its graph evidence, and the fix
137
+ commit or why it is unfixed - then the tests and lint after the commit, and
138
+ what the graph could not see. Do not push; offer to.
139
+
140
+ ## 6. Post, with `--post` only
141
+
142
+ Only on a change request, only the unfixed findings, and before the fix commit
143
+ is pushed, so the lines still match the forge's head. Write `pass.json`:
144
+
145
+ ```json
146
+ {
147
+ "verdict": "comment",
148
+ "body": "<what was fixed locally, what is left and why>",
149
+ "comments": [{ "path": "src/cart.js", "line": 5, "body": "<the problem, then a suggestion>" }]
150
+ }
151
+ ```
152
+
153
+ A comment must sit on a line the change request's diff touches, or the forge
154
+ refuses it; a finding on an untouched caller goes in `body`, with a link in the
155
+ `permalink` shape `forge status` prints. Keep each comment to a few lines, and
156
+ follow the user's own rules for text posted in their name when they keep any.
157
+ Check, then post:
158
+
159
+ ```sh
160
+ thurview forge submit --change <ref> --file pass.json --dry-run
161
+ thurview forge submit --change <ref> --file pass.json
162
+ ```
163
+
164
+ The verdict stays `comment`: approving is the maintainer's call. How GitHub and
165
+ GitLab differ is in [Forges](references/forges.md).
@@ -19,9 +19,9 @@ request asks for.
19
19
  states what it did not examine. Read
20
20
  [Code explainer](references/code-explainer.md) and follow that instead.
21
21
 
22
- Posting a review back to a forge - inline comments on a pull or merge request,
23
- a verdict, a re-review that answers the previous one - is the `forge-review`
24
- skill, not this one. This skill's document is read in the browser.
22
+ Reviewing a change and fixing what the review finds - and posting what stays
23
+ unfixed to a pull or merge request - is the `review-fix` skill, not this one.
24
+ This skill's document is read in the browser.
25
25
 
26
26
  The agent studies the change and writes a short document in which every claim
27
27
  about code is anchored to an exact file and line range at a pinned commit.
@@ -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.