thurview 0.8.2 → 0.9.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 +56 -19
- package/dist/cli.js +447 -106
- package/dist/cli.js.map +1 -1
- package/dist/forge/github.js +245 -0
- package/dist/forge/github.js.map +1 -0
- package/dist/forge/gitlab.js +226 -0
- package/dist/forge/gitlab.js.map +1 -0
- package/dist/forge/index.js +110 -0
- package/dist/forge/index.js.map +1 -0
- package/dist/forge/run.js +55 -0
- package/dist/forge/run.js.map +1 -0
- package/dist/forge/submission.js +55 -0
- package/dist/forge/submission.js.map +1 -0
- package/dist/forge/types.js +37 -0
- package/dist/forge/types.js.map +1 -0
- package/dist/presence.js +34 -0
- package/dist/presence.js.map +1 -0
- package/dist/server/server.js +4 -0
- package/dist/server/server.js.map +1 -1
- package/dist/store.js +10 -0
- package/dist/store.js.map +1 -1
- package/dist/thread-state.js +17 -0
- package/dist/thread-state.js.map +1 -0
- package/dist/threads.js +10 -9
- package/dist/threads.js.map +1 -1
- package/dist/ui/app.css +18 -0
- package/dist/ui/app.js +82 -24
- package/dist/ui/app.js.map +4 -4
- package/package.json +1 -1
- package/skills/forge-review/SKILL.md +276 -0
- package/skills/forge-review/references/forges.md +58 -0
- package/skills/forge-review/references/security-surfaces.md +54 -0
- package/skills/thurview/SKILL.md +16 -3
- package/skills/thurview/references/lifecycle.md +54 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thurview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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,276 @@
|
|
|
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.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Forges
|
|
2
|
+
|
|
3
|
+
`thurview forge` speaks to GitHub through `gh` and to GitLab through `glab`.
|
|
4
|
+
Both are driven through `gh api` and `glab api` rather than their porcelain,
|
|
5
|
+
because the REST and GraphQL payloads are a contract and the porcelain is not.
|
|
6
|
+
|
|
7
|
+
Which adapter owns a host is decided by name for `github.com` and
|
|
8
|
+
`gitlab.com`, and read off the machine for everything else - a host is owned
|
|
9
|
+
when `gh auth status --hostname <host>` or `glab auth status --hostname
|
|
10
|
+
<host>` succeeds, which is where the answer for a self-hosted instance already
|
|
11
|
+
lives. `GH_HOST` and `GITLAB_HOST` are honoured. A host no adapter claims is
|
|
12
|
+
**refused**, not guessed at; pass `--forge github|gitlab` to name it.
|
|
13
|
+
|
|
14
|
+
## What differs, and what the CLI does about it
|
|
15
|
+
|
|
16
|
+
| Behaviour | GitHub | GitLab |
|
|
17
|
+
| ---------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
18
|
+
| The noun | pull request | merge request |
|
|
19
|
+
| A pass | one atomic review | N discussions plus one note; no atomic form exists |
|
|
20
|
+
| Requesting changes | `CHANGES_REQUESTED`, which blocks a merge | no such state; the account's approval is removed and the summary says it |
|
|
21
|
+
| A comment's anchor | a line range, `start_line` to `line` | one line; a range is anchored at its last line and `notes` says so |
|
|
22
|
+
| Thread identity | a GraphQL node id | a discussion id |
|
|
23
|
+
| Resolving | `resolveReviewThread` | `PUT .../discussions/<id>?resolved=true` |
|
|
24
|
+
| Staleness of a thread | `isOutdated` from the forge | not reported; compare `threads[].atHead` against the current head instead |
|
|
25
|
+
| CI | check runs plus commit statuses | the latest pipeline's jobs, read from the pipeline's own project so a fork's jobs are found |
|
|
26
|
+
| Fetching a head | `refs/pull/<n>/head` | `refs/merge-requests/<n>/head` |
|
|
27
|
+
| A line range permalink | `#L10-L20` | `#L10-20` |
|
|
28
|
+
|
|
29
|
+
Consequences worth knowing before you post:
|
|
30
|
+
|
|
31
|
+
- On GitLab a pass that fails half way through has already posted the
|
|
32
|
+
comments it got to. The inline comments go first and the summary last, so a
|
|
33
|
+
partial pass is still one the author can read, but check `submitted.comments`
|
|
34
|
+
against what you sent.
|
|
35
|
+
- `request-changes` on GitLab does not block a merge. If blocking matters,
|
|
36
|
+
say so in the summary and leave it to the maintainer.
|
|
37
|
+
- A multi-line finding on GitLab loses its range. Put the range in the
|
|
38
|
+
comment's own permalink.
|
|
39
|
+
|
|
40
|
+
## What has been exercised
|
|
41
|
+
|
|
42
|
+
The GitHub adapter is driven by tests and against github.com. The GitLab
|
|
43
|
+
adapter is driven by the same tests - the calls it makes and the answers it
|
|
44
|
+
parses are asserted - but has not been run against a live GitLab instance. If
|
|
45
|
+
you are the first to point it at one, expect the friction to be in `glab api`'s
|
|
46
|
+
own flags rather than in the endpoints, and report what differed.
|
|
47
|
+
|
|
48
|
+
## Adding a forge
|
|
49
|
+
|
|
50
|
+
Implement `Forge` in `src/forge/types.ts` and register it in `BUILTIN` in
|
|
51
|
+
`src/forge/index.ts`. The interface is the whole contract - `get`, `fetchRef`,
|
|
52
|
+
`checks`, `baseline`, `prior`, `submit`, `reply`, `permalink`, plus `owns` and
|
|
53
|
+
`whoami`. There is deliberately no `merge`, `close` or `push`.
|
|
54
|
+
|
|
55
|
+
Then drive it from `test/forge.test.ts`. The tests put a fake CLI on PATH
|
|
56
|
+
under the adapter's own binary name and answer from a fixture table, so an
|
|
57
|
+
adapter is proved by the calls it makes and the answers it parses rather than
|
|
58
|
+
by being asserted to exist.
|
|
@@ -0,0 +1,54 @@
|
|
|
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.
|
package/skills/thurview/SKILL.md
CHANGED
|
@@ -19,6 +19,10 @@ 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.
|
|
25
|
+
|
|
22
26
|
The agent studies the change and writes a short document in which every claim
|
|
23
27
|
about code is anchored to an exact file and line range at a pinned commit.
|
|
24
28
|
thurview validates those anchors, seals a revision, and serves it in the
|
|
@@ -86,7 +90,7 @@ request:
|
|
|
86
90
|
|
|
87
91
|
```sh
|
|
88
92
|
thurview scaffold # current branch vs its trunk fork point
|
|
89
|
-
thurview scaffold --pr 123 # pull request (needs gh)
|
|
93
|
+
thurview scaffold --pr 123 # pull or merge request (needs gh or glab)
|
|
90
94
|
thurview scaffold --base <ref> --head <ref>
|
|
91
95
|
```
|
|
92
96
|
|
|
@@ -249,7 +253,9 @@ Tell the user, in a few lines and nothing more:
|
|
|
249
253
|
- which theme source you used: the user's request, the project's design
|
|
250
254
|
system (name the files), or the default skin
|
|
251
255
|
- when the review has no map, why not, in one clause
|
|
252
|
-
- that you are now waiting for their questions and their decision
|
|
256
|
+
- that you are now waiting for their questions and their decision, and that
|
|
257
|
+
a question asked after you stop waiting is queued rather than lost - the
|
|
258
|
+
page tells them which of the two it is
|
|
253
259
|
|
|
254
260
|
The page explains its own controls; do not describe them.
|
|
255
261
|
|
|
@@ -265,9 +271,16 @@ nothing: keep `--timeout` under that limit and run `wait` again on `timeout`.
|
|
|
265
271
|
When the tool can run a command in the background and wake you when it exits,
|
|
266
272
|
run `wait` that way, so the user has the terminal back while they read.
|
|
267
273
|
|
|
274
|
+
While `wait` runs the reader's page says an agent is listening, and says the
|
|
275
|
+
opposite within seconds of it returning. Do not loop it to look present: a
|
|
276
|
+
question asked with nobody waiting is queued, not lost, and `thurview`
|
|
277
|
+
reports it as `needsAgent` the next time you run any command in the
|
|
278
|
+
worktree.
|
|
279
|
+
|
|
268
280
|
`wait.reason` says what happened, with the threads that need you:
|
|
269
281
|
|
|
270
|
-
- `question`:
|
|
282
|
+
- `question`: a thread the reader sent to you. Answer each thread in
|
|
283
|
+
`threads` with
|
|
271
284
|
`thurview threads reply <threadId> --review <id> --body "<answer>"`. Do
|
|
272
285
|
not change the document for a question. Wait again.
|
|
273
286
|
- `awaiting-agent-updates`: the reader submitted with "Request changes".
|
|
@@ -27,7 +27,7 @@ warns when the branch moved past them.
|
|
|
27
27
|
| `accepted` | Terminal. Cannot be republished. |
|
|
28
28
|
| `closed` | Terminal. Ended without approval. Cannot be republished. |
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Asking the agent a question does not change the status. "Submit review" with "Request changes"
|
|
31
31
|
sets `awaiting-agent-updates`; with "Approve" sets `accepted`; with "Close"
|
|
32
32
|
sets `closed`.
|
|
33
33
|
|
|
@@ -36,21 +36,62 @@ Dismissal is separate: the reader removes the review from the active list and
|
|
|
36
36
|
|
|
37
37
|
## Threads
|
|
38
38
|
|
|
39
|
-
Two kinds, chosen by the reader when creating one
|
|
39
|
+
Two kinds, chosen by the reader when creating one. In the browser these are
|
|
40
|
+
the two buttons on the comment box, one click each:
|
|
40
41
|
|
|
41
|
-
- `ask` mode (a question):
|
|
42
|
-
Answer with `threads reply`.
|
|
43
|
-
|
|
44
|
-
- `review` mode (a comment): held as pending until the
|
|
45
|
-
`wait` returns `awaiting-agent-updates` with the
|
|
42
|
+
- `ask` mode (a question, "Send to the agent"): submitted on creation and
|
|
43
|
+
delivered at once. `wait` returns `question`. Answer with `threads reply`.
|
|
44
|
+
Open questions never block a republish.
|
|
45
|
+
- `review` mode (a comment, "Add to the review"): held as pending until the
|
|
46
|
+
reader submits. Then `wait` returns `awaiting-agent-updates` with the
|
|
47
|
+
submitted threads.
|
|
46
48
|
|
|
47
49
|
Targets: a document block (with an optional quoted selection), a file line
|
|
48
50
|
on the base or head side, a map node, or the whole review.
|
|
49
51
|
|
|
52
|
+
### Status, `submitted` and `needsAgent`
|
|
53
|
+
|
|
54
|
+
Two flags and one derived predicate decide whether a thread reaches you.
|
|
55
|
+
`needsAgent` is the whole queue: `wait` reports it, `threads list --open`
|
|
56
|
+
counts it, and a thread outside it will not be delivered to anyone.
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
needsAgent = status is open AND submitted AND the last message is the reader's
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Transition | status | submitted |
|
|
63
|
+
| ------------------------------ | -------------------- | ------------- |
|
|
64
|
+
| reader creates an `ask` thread | `open` | `true` |
|
|
65
|
+
| reader creates a `review` one | `open` | `false` |
|
|
66
|
+
| reader submits the review | unchanged | `true` (all) |
|
|
67
|
+
| **reader writes in a thread** | **forced to `open`** | `true` if ask |
|
|
68
|
+
| agent replies | unchanged | unchanged |
|
|
69
|
+
| `threads resolve` / Resolve | `resolved` | unchanged |
|
|
70
|
+
| Reopen | `open` | unchanged |
|
|
71
|
+
|
|
72
|
+
A message from the reader always reopens the thread. It has to: a reply that
|
|
73
|
+
left the thread resolved would sit at `needsAgent: false`, invisible to
|
|
74
|
+
`wait` and to `threads list --open`, and the reader would be writing to
|
|
75
|
+
nobody while the page still offered them a Reply button. Publishing a new
|
|
76
|
+
revision never touches a thread's status.
|
|
77
|
+
|
|
50
78
|
`publish` after the first revision requires zero open submitted comment
|
|
51
79
|
threads. Resolve a thread only when its requested change is present. Do not
|
|
52
80
|
rewrite or merge threads.
|
|
53
81
|
|
|
82
|
+
### Presence: what the reader is told
|
|
83
|
+
|
|
84
|
+
While `thurview wait` runs it writes a heartbeat to
|
|
85
|
+
`${THURVIEW_HOME:-~/.thurview}/agents/<reviewId>.json`, and the browser reads
|
|
86
|
+
it back as one of two sentences: an agent is listening now, or nothing is
|
|
87
|
+
listening and what you send is queued until one checks in. Nothing else
|
|
88
|
+
writes it, so presence is never inferred and never faked. A heartbeat older
|
|
89
|
+
than 15 seconds is a dead `wait`, not an agent.
|
|
90
|
+
|
|
91
|
+
That is why a question asked while you are away is not lost and does not need
|
|
92
|
+
you to sit in `wait`: it is queued, `thurview` reports it as `needsAgent` the
|
|
93
|
+
next time you run any command in the worktree, and you answer it then.
|
|
94
|
+
|
|
54
95
|
```sh
|
|
55
96
|
thurview threads list --review <id> [--open]
|
|
56
97
|
thurview threads get <threadId> --review <id>
|
|
@@ -64,6 +105,7 @@ thurview threads resolve <threadId> --review <id>
|
|
|
64
105
|
${THURVIEW_HOME:-~/.thurview}/
|
|
65
106
|
├── THURVIEW.md user guidance (optional)
|
|
66
107
|
├── server.json running server, if any
|
|
108
|
+
├── agents/<id>.json heartbeat of a running `wait`, removed when it ends
|
|
67
109
|
└── reviews/<id>/
|
|
68
110
|
├── review.md you edit
|
|
69
111
|
├── data.yaml you edit
|
|
@@ -90,3 +132,8 @@ again.
|
|
|
90
132
|
|
|
91
133
|
`thurview threads get <id>` truncates bodies over 1500 characters; pass
|
|
92
134
|
`--full` when the hint says so.
|
|
135
|
+
|
|
136
|
+
While `wait` runs, the reader's page says an agent is listening; when it
|
|
137
|
+
returns, the page says the opposite within seconds. Do not leave `wait`
|
|
138
|
+
running to look present when you are not going to answer, and do not loop it
|
|
139
|
+
to keep a queue drained: the queue survives you, and the reader is told so.
|