thurview 0.14.0 → 0.16.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 CHANGED
@@ -1,82 +1,123 @@
1
1
  # thurview
2
2
 
3
- Guided, evidence-anchored reviews of agent-written code. A coding agent studies
4
- a branch, pull request or commit range and writes a short document in which
5
- every claim is anchored to an exact file and line range at a pinned commit. You
6
- read it in your browser, ask the agent questions, comment on the code, and
7
- approve or send it back.
3
+ **Your coding agent writes the review; you read it in the browser, anchored to
4
+ the code, and approve it or send it back.**
5
+
6
+ [![CI](https://github.com/Thurbeen/thurview/actions/workflows/ci.yml/badge.svg)](https://github.com/Thurbeen/thurview/actions/workflows/ci.yml)
7
+ [![npm](https://img.shields.io/npm/v/thurview)](https://www.npmjs.com/package/thurview)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ![thurview demo: the reader follows an anchor into the code, comments on a line
11
+ range in the diff, reads the agent's answer and requests changes](./media/thurview-demo.gif)
12
+
13
+ _The reader's half of it; the agent is working off camera._
14
+
15
+ - **Every claim is anchored to code.** The prose links to an exact file and
16
+ line range at a pinned commit, and the reader opens that code beside the
17
+ text — no hunting for what a sentence is about.
18
+ - **You read it in your browser, not in a comment thread.** An argument in the
19
+ order somebody chose to make it, with the code, the diff between the pinned
20
+ commits and the system around it one click away — instead of forty remarks
21
+ in the order they happened to be written.
22
+ - **You ask, and the agent answers in the document.** A question goes to the
23
+ agent — at once if one is listening, queued if not — and the answer lands
24
+ in the same thread; a comment waits for your decision. Then you approve the
25
+ change or send it back with the comments attached.
26
+ - **A code graph answers what the diff cannot.** Who calls the symbol that
27
+ moved, which tests reach it, where the change landed in the system and what
28
+ sits next to it. thurview builds the graph itself with tree-sitter from the
29
+ pinned commits, for TypeScript, JavaScript, Python, Go, Rust, Java and Elixir.
30
+ - **The evidence is checked, not taken on trust.** An anchor whose lines
31
+ do not exist at the pinned commit, a call stack frame asserting a call the
32
+ diff does not show, an interface annotation for a symbol the change never
33
+ moved, a trust boundary crossing that resolves to nothing — publishing
34
+ rejects each one rather than rendering it.
8
35
 
9
36
  ## Install
10
37
 
11
- Give your agent the skill, with the [skills](https://github.com/vercel-labs/skills)
12
- CLI - it works with Claude Code, Codex, Cursor, OpenCode and every agent that
13
- reads the Agent Skills format:
38
+ One command gives your agent every skill this repository ships, through the
39
+ [skills](https://github.com/vercel-labs/skills) CLI - it works with Claude
40
+ Code, Codex, Cursor, OpenCode and every agent that reads the Agent Skills
41
+ format:
14
42
 
15
43
  ```sh
16
44
  npx skills@latest add https://github.com/Thurbeen/thurview \
17
- --skill thurview --agent universal claude-code --global --yes
45
+ --skill '*' --agent universal claude-code --global --yes
18
46
  ```
19
47
 
20
- The skill reaches the `thurview` command through `npx`, so the requirements
21
- are Node 22 or later and git (`gh` for pull requests, `glab` for merge
22
- requests). Then, in any repository, ask your agent:
48
+ `--skill '*'` takes all four; quote the star so your shell does not expand it
49
+ against the current directory. The skills reach the `thurview` command through
50
+ `npx`, so the requirements are Node 22 or later and git (`gh` for pull
51
+ requests, `glab` for merge requests). Then, in any repository, ask your agent:
23
52
 
24
53
  ```text
25
54
  Use the thurview skill to review my current branch against up-to-date main
26
55
  and open it.
27
56
  ```
28
57
 
29
- ![thurview demo: the reader follows an anchor into the code, comments on a line
30
- range in the diff, reads the agent's answer and requests changes](./media/thurview-demo.gif)
31
-
32
- The clip is the reader's half, the agent working off camera: following an anchor
33
- from the prose into the code, opening a call stack frame, commenting on a line
34
- range of the diff, seeing on the map what the change added, reading in the
35
- threads panel the answer to a question already asked, and sending the review
36
- back with changes requested.
58
+ ## The four skills
59
+
60
+ Three kinds of document, and one companion that writes none:
61
+
62
+ - **`thurview`** - a change that is already written: a branch, a pull request,
63
+ a commit range. The document carries the diff, the commits and the interface
64
+ delta, and the reader approves it or sends it back.
65
+ - **`thurview-explain`** - a codebase, or one subsystem of it, at a single
66
+ pinned commit. No diff and nothing to approve: it surfaces the architecture
67
+ well enough for the reader to spot design problems, and states what it did
68
+ not examine.
69
+ - **`thurview-design`** - a change that is not written yet: a design, an
70
+ architecture proposal, an implementation plan. It anchors on the code as it
71
+ stands and declares what it would build as proposals, each attached to the
72
+ code it lands in today.
73
+ - **`thurview-fix`** - no browser and no reader. It reviews, fixes what it is
74
+ sure of behind the repository's own tests and lint, and reports the rest -
75
+ optionally as inline comments on the change request.
37
76
 
38
77
  <details>
39
- <summary><b>Other ways to install</b> - pin the skill to a release, install the
40
- command from npm, run from a checkout, session hooks, the thurview-design and
41
- thurview-fix skills</summary>
78
+ <summary><b>Other ways to install</b> - one skill at a time, pin to a release,
79
+ install the command from npm, run from a checkout, session hooks, and coming
80
+ from an older install</summary>
42
81
 
43
82
  `--global` installs for your user, so one install covers every repository.
44
- `universal` puts the one real copy in `~/.agents/skills/thurview`, the directory
45
- no single agent owns, and every other agent you name gets a symlink to it, such
46
- as `~/.claude/skills/thurview` → `../../.agents/skills/thurview`, so an update
47
- lands everywhere at once. Swap `claude-code` for any agent the skills CLI
48
- supports, but keep `universal` and at least one more: with `--yes` and a single
49
- target, the CLI copies instead of linking.
50
-
51
- That form tracks this repository's default branch: `skills update` takes
52
- whatever `main` holds, which can be ahead of the released command. To pin the
53
- skill to a release instead, install it from the tag, which the skill lock
54
- records and later updates keep:
83
+ `universal` puts the one real copy of each skill under `~/.agents/skills/`,
84
+ the directory no single agent owns, and every other agent you name gets a
85
+ symlink to it - `~/.claude/skills/thurview` →
86
+ `../../.agents/skills/thurview`, and the same for the other three - so an
87
+ update lands everywhere at once. Swap `claude-code` for any agent the skills
88
+ CLI supports, but keep `universal` and at least one more: with `--yes` and a
89
+ single target, the CLI copies instead of linking.
90
+
91
+ `--skill` also takes names, one or several, when you do not want all four:
92
+ `--skill thurview`, or `--skill thurview thurview-fix`.
93
+
94
+ The untagged URL above tracks this repository's default branch:
95
+ `skills update` takes whatever `main` holds, which can be ahead of the
96
+ released command. To pin the skills to a release instead, install from the
97
+ tag, which the skill lock records and later updates keep:
55
98
 
56
99
  ```sh
57
- npx skills@latest add https://github.com/Thurbeen/thurview/tree/v0.10.0/skills/thurview \
58
- --agent universal claude-code --global --yes
100
+ npx skills@latest add https://github.com/Thurbeen/thurview/tree/v0.15.0 \
101
+ --skill '*' --agent universal claude-code --global --yes
59
102
  ```
60
103
 
61
104
  Releases tag without committing, so nothing moves the tag above: swap in the
62
105
  [latest release](https://github.com/Thurbeen/thurview/releases/latest).
63
106
 
64
- The npm package ships the same skill, so `thurview setup skill` links the copy
65
- that matches the command you have installed. Use that when you want the two to
66
- move together.
107
+ The npm package ships the same skills, so `thurview setup skill` links the
108
+ copies that match the command you have installed. Use that when you want the
109
+ two to move together.
67
110
 
68
- Install the command itself, rather than leaving the skill to reach it through
111
+ Install the command itself, rather than leaving the skills to reach it through
69
112
  `npx` on every run:
70
113
 
71
114
  ```sh
72
115
  npm install -g thurview # or: pnpm add -g thurview
73
116
  ```
74
117
 
75
- The review reasons over a code graph thurview builds itself from the pinned
76
- commits with tree-sitter, so nothing else needs installing. `thurview graph`
77
- answers which interfaces the change moved, what it reaches, who calls a
78
- symbol, what tests cover it and how files cluster, for TypeScript,
79
- JavaScript, Python, Go, Rust, Java and Elixir.
118
+ Nothing else needs installing: the code graph is built from the pinned commits
119
+ with tree-sitter. `thurview graph` answers which interfaces the change moved,
120
+ what it reaches, who calls a symbol, what tests cover it and how files cluster.
80
121
 
81
122
  To run from a checkout instead:
82
123
 
@@ -89,30 +130,27 @@ npm link # puts `thurview` on PATH
89
130
  Optional, for ambient context: `thurview setup hooks` installs a
90
131
  SessionStart hook for Claude Code, Codex and OpenCode, so every session opens
91
132
  with the reviews of its working directory. `thurview setup skill` links the
92
- skill from this checkout instead of the `skills` CLI copy; use one or the
133
+ skills from this checkout instead of the `skills` CLI copies; use one or the
93
134
  other.
94
135
 
95
- Also available: `thurview-design`, which authors the design kind described
96
- [below](#how-it-works), and `thurview-fix`, the browserless companion skill
97
- described [below](#review-and-fix).
136
+ **Coming from an older install.** A skill name is an address, so nothing
137
+ renames or splits one in place - the command above adds what is missing, and
138
+ what is stale has to go.
98
139
 
99
- ```sh
100
- npx skills@latest add https://github.com/Thurbeen/thurview \
101
- --skill thurview-design --agent universal claude-code --global --yes
102
- npx skills@latest add https://github.com/Thurbeen/thurview \
103
- --skill thurview-fix --agent universal claude-code --global --yes
104
- ```
140
+ `thurview-explain` used to be a second kind inside the `thurview` skill, so an
141
+ install made before the split does not have it. Under the `skills` CLI,
142
+ updating `thurview` adds no second skill; run the install command above, which
143
+ takes all four. Under `thurview setup skill`, update the command first -
144
+ `thurview update`, or pull and rebuild the checkout you linked from - and run
145
+ `thurview setup skill` again: it links every skill the installed command
146
+ carries, so it picks the new one up on its own.
105
147
 
106
- `thurview-fix` was called `review-fix`. A skill name is an address, so nothing
107
- updates the old one in place: an install made before the rename keeps answering
108
- to `/review-fix` from a copy that will never change again, and one made with
109
- `thurview setup skill` leaves a symlink pointing at a directory this repository
110
- no longer ships. Remove it and add the skill under its new name:
148
+ `thurview-fix` was called `review-fix`, and an install made before the rename
149
+ keeps answering to `/review-fix` from a copy that will never change again.
150
+ Remove it:
111
151
 
112
152
  ```sh
113
153
  npx skills@latest remove --global review-fix
114
- npx skills@latest add https://github.com/Thurbeen/thurview \
115
- --skill thurview-fix --agent universal claude-code --global --yes
116
154
  ```
117
155
 
118
156
  For a `thurview setup skill` install, delete the stale link -
@@ -128,20 +166,14 @@ and serves it in your browser: the walkthrough, live code peeks, the diff,
128
166
  commits, and a software map. You ask questions, leave anchored comments, and
129
167
  approve or request changes. The agent answers and republishes.
130
168
 
131
- It also explains a codebase. A **code explainer** is the same document over a
132
- different unit: one pinned commit instead of a range, so a reader can see the
133
- architecture well enough to spot design problems themselves. It has no diff and
134
- nothing to approve, it surfaces structure rather than grading it, and it states
135
- what it did not examine.
136
-
137
- And it reads a plan. A **design** is the same document over a change that is
138
- not written yet: one pinned commit — the code it argues from — anchors on the
139
- code as it stands, and what it would build declared as proposals, each attached
140
- to the code it lands in today. An anchor never points at code that does not
141
- exist; the reader approves the design or sends it back.
169
+ An explainer and a design run the same loop over a different unit. An
170
+ explainer pins one commit instead of a range, so it has no diff and nothing to
171
+ approve. A design pins the commit it argues from: its anchors land on the code
172
+ as it stands, never on code that does not exist yet, and what it would build
173
+ rides beside them as proposals.
142
174
 
143
- It does not review the code for you. It helps you understand it fast enough
144
- to review it yourself.
175
+ The document does not review the code for you. It helps you understand the
176
+ code fast enough to judge it yourself.
145
177
 
146
178
  ```mermaid
147
179
  flowchart LR
@@ -155,6 +187,16 @@ flowchart LR
155
187
 
156
188
  ## What the reader sees
157
189
 
190
+ The home page is a queue: every review, explainer and design, grouped by
191
+ repository and ordered by whose turn it is - a decision not yet posted to its
192
+ change request first, then documents waiting for your reading, then ones whose
193
+ change request moved past the pin, then ones waiting for the agent, then ones
194
+ not published yet. A row bound to a change request also says whether the pin is
195
+ still the head, what you decided and whether it reached the forge, and whether
196
+ CI is a real gate there, as of the last `forge` command and with that age on
197
+ screen. The browser never calls the forge; explainers and designs carry those
198
+ columns empty.
199
+
158
200
  Prose with every claim anchored to code, opened beside the text:
159
201
 
160
202
  ![The review document, with a call stack diff, a storage view and an anchored
@@ -317,33 +359,44 @@ The agent writes three files in `~/.thurview/reviews/<id>/`:
317
359
  `## Heading {collapsed}` folds a section by default.
318
360
  - `data.yaml`: typed inputs: `actors`, `anchors` (file, from, to, graph),
319
361
  `stores`, `interfaces` (a capability line per derived entry, plus the
320
- interfaces the graph cannot see).
362
+ interfaces the graph cannot see), and `security`, where a review says where
363
+ the change lets input cross a trust boundary.
321
364
  - `map.yaml`: the software map at head, optionally at base. In an explainer it
322
365
  carries the breadth the prose has no room for, and a node's `files` globs are
323
366
  what let a file count as placed rather than not examined.
324
367
  - `theme.yaml`: the look, derived from the reviewed project's own design
325
368
  system (tokens, fonts, shape, code palette). Empty means the default skin.
326
369
 
327
- An explainer writes the same files, minus `interfaces`: there is no change to
328
- derive a delta from, and `graph: base` on an anchor is an error because there
329
- is one commit.
370
+ `security` is a review's own dimension, shown to the reader under the interface
371
+ delta rather than left to a section the agent might not write. It is
372
+ `security: none` when the change crosses no trust boundary, or one entry per
373
+ place it does — a sentence and the head anchor the reader opens. Left out, it
374
+ publishes as "not assessed", so a review that has not looked and one that looked
375
+ and found nothing are never the same page. What counts as a trust boundary is
376
+ defined in one place — the `thurview-fix` skill's finding rules — and
377
+ nothing else restates it.
378
+
379
+ An explainer writes the same files, minus `interfaces` and `security`: there is
380
+ no change to derive a delta from or to carry input across a boundary, and
381
+ `graph: base` on an anchor is an error because there is one commit.
330
382
 
331
383
  A design writes the same files, and `interfaces` means something else in it:
332
384
  each entry is a **proposal** — what the design would add, change or remove,
333
385
  with the anchor of the code that proposal lands in, replaces or plugs into
334
- today. `graph: base` is an error for the same reason as in an explainer, a
335
- `symbol:` entry is an error because no diff derived one, and a design that
336
- proposes nothing is refused: that document is an explainer. In its `map.yaml`,
337
- `base` is the structure as it stands and `nodes` the structure it proposes, so
338
- a proposed part may own files that do not exist yet while a `base` node may
339
- not.
386
+ today. `graph: base` and `security` are errors for the same reason as in an
387
+ explainer, a `symbol:` entry is an error because no diff derived one, and a
388
+ design that proposes nothing is refused: that document is an explainer. In its
389
+ `map.yaml`, `base` is the structure as it stands and `nodes` the structure it
390
+ proposes, so a proposed part may own files that do not exist yet while a `base`
391
+ node may not.
340
392
 
341
393
  `thurview publish` rejects an anchor whose file or lines do not exist at the
342
394
  pinned commit, a call stack frame that claims an added or removed call the
343
395
  diff does not show, a storage operation on an unknown field, a map edge
344
396
  to an unknown node, an interface annotation for a symbol the change did not
345
- move, a declared interface whose anchor holds no added or deleted line, and an
346
- explainer that anchors nothing at all, and a design that proposes nothing or
397
+ move, a declared interface whose anchor holds no added or deleted line, a trust
398
+ boundary crossing whose anchor resolves to nothing or reads the base commit, and
399
+ an explainer that anchors nothing at all, and a design that proposes nothing or
347
400
  whose proposal names no site in the code as it stands. The full format is in
348
401
  [skills/thurview/references](skills/thurview/references).
349
402
 
package/dist/cli.js CHANGED
@@ -21,6 +21,7 @@ import { startServer } from "./server/server.js";
21
21
  import { parseFlags, helpFor, str, bool } from "./flags.js";
22
22
  import { forgeFor, repoOf, summariseCi, } from "./forge/index.js";
23
23
  import { parseSubmission, longComments, buildPass } from "./forge/submission.js";
24
+ import { recordForgeFacts, ciFacts } from "./queue.js";
24
25
  import { VERSION } from "./version.js";
25
26
  const execFileP = promisify(execFile);
26
27
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -273,6 +274,17 @@ const TEMPLATE_DATA = `# Typed inputs for review.md: actors, anchors and stores.
273
274
  #
274
275
  # interfaces holds one capability line per interface the change moved. thurview
275
276
  # derives the list itself; run \`thurview graph interfaces\` for the ids.
277
+ #
278
+ # security says where this change lets input cross a trust boundary. Leave it
279
+ # out (or write \`security: pending\`) until you have looked and the document says
280
+ # so; then write \`security: none\`, or list what it crosses:
281
+ #
282
+ # security:
283
+ # - boundary: The --shell flag reaches execFile's argv unquoted.
284
+ # anchor: spawn
285
+ #
286
+ # What counts as a trust boundary is defined once, in the thurview-fix skill's
287
+ # SKILL.md under "Findings". Read it there rather than deciding again.
276
288
  actors: {}
277
289
  anchors: {}
278
290
  stores: {}
@@ -745,6 +757,7 @@ const commands = {
745
757
  let base;
746
758
  let head;
747
759
  let title = str(p, "title") ?? "";
760
+ let pinned = null;
748
761
  const b = existing?.binding;
749
762
  const pr = str(p, "pr");
750
763
  if (pr || b?.kind === "pr") {
@@ -768,6 +781,7 @@ const commands = {
768
781
  base = await g.mergeBase(worktree, baseRef, head);
769
782
  binding = { kind: "pr", name: cr.number, url: cr.url, forge: forge.id };
770
783
  title ||= cr.title;
784
+ pinned = { repo, cr };
771
785
  }
772
786
  else if (str(p, "base") || str(p, "head") || b?.kind === "range") {
773
787
  const [bb, hh] = b?.kind === "range" && !str(p, "base") && !str(p, "head")
@@ -846,6 +860,8 @@ const commands = {
846
860
  await writeReview(review);
847
861
  }
848
862
  }
863
+ if (pinned)
864
+ await recordForgeFacts(review, pinned.repo, pinned.cr);
849
865
  const stat = await g.shortStat(worktree, base, head);
850
866
  const dir = reviewDir(review.id);
851
867
  return {
@@ -1177,7 +1193,10 @@ const commands = {
1177
1193
  ? { coverage: coverage ? coverage.verdict : "(unavailable)" }
1178
1194
  : kind === "design"
1179
1195
  ? { proposes: doc.document.interfaces?.verdict ?? "(unavailable)" }
1180
- : { interfaces: doc.document.interfaces?.verdict ?? "(unavailable)" }),
1196
+ : {
1197
+ interfaces: doc.document.interfaces?.verdict ?? "(unavailable)",
1198
+ security: doc.document.security?.verdict ?? "(unavailable)",
1199
+ }),
1181
1200
  theme: theme?.name ?? "default",
1182
1201
  url: url ?? "(server not running)",
1183
1202
  },
@@ -1634,6 +1653,7 @@ const commands = {
1634
1653
  const checks = await ctx.forge.checks(ctx.repo, ctx.cr);
1635
1654
  const baseline = await ctx.forge.baseline(ctx.repo, ctx.cr.baseBranch).catch(() => null);
1636
1655
  const ci = summariseCi(checks, baseline, ctx.cr.baseBranch);
1656
+ await recordForgeFacts(ctx.review, ctx.repo, ctx.cr, { ci: ciFacts(ci) });
1637
1657
  const shown = bool(p, "full") ? checks : checks.filter((c) => c.state !== "passed");
1638
1658
  const hidden = checks.length - shown.length;
1639
1659
  const help = [
@@ -1855,6 +1875,9 @@ const commands = {
1855
1875
  ],
1856
1876
  };
1857
1877
  const posted = await ctx.forge.submit(ctx.repo, ctx.cr, submission);
1878
+ await recordForgeFacts(ctx.review, ctx.repo, ctx.cr, {
1879
+ posted: { at: now(), verdict: posted.verdict, head: ctx.cr.head },
1880
+ });
1858
1881
  return {
1859
1882
  submitted: {
1860
1883
  forge: ctx.forge.id,