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 +140 -87
- package/dist/cli.js +24 -1
- package/dist/cli.js.map +1 -1
- package/dist/document/compile.js +107 -1
- package/dist/document/compile.js.map +1 -1
- package/dist/document/schema.js +17 -0
- package/dist/document/schema.js.map +1 -1
- package/dist/queue.js +123 -0
- package/dist/queue.js.map +1 -0
- package/dist/server/server.js +10 -10
- package/dist/server/server.js.map +1 -1
- package/dist/store.js +10 -0
- package/dist/store.js.map +1 -1
- package/dist/ui/app.css +25 -0
- package/dist/ui/app.js +141 -12
- package/dist/ui/app.js.map +2 -2
- package/package.json +1 -1
- package/skills/thurview/SKILL.md +32 -15
- package/skills/thurview/references/components.md +38 -0
- package/skills/thurview/references/document-authoring.md +19 -0
- package/skills/thurview/references/lifecycle.md +21 -0
- package/skills/thurview-design/SKILL.md +5 -5
- package/skills/{thurview/references/code-explainer.md → thurview-explain/SKILL.md} +95 -30
package/README.md
CHANGED
|
@@ -1,82 +1,123 @@
|
|
|
1
1
|
# thurview
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
+
[](https://github.com/Thurbeen/thurview/actions/workflows/ci.yml)
|
|
7
|
+
[](https://www.npmjs.com/package/thurview)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+

|
|
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
|
-
|
|
12
|
-
CLI - it works with Claude
|
|
13
|
-
reads the Agent Skills
|
|
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
|
|
45
|
+
--skill '*' --agent universal claude-code --global --yes
|
|
18
46
|
```
|
|
19
47
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
range
|
|
35
|
-
|
|
36
|
-
|
|
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> -
|
|
40
|
-
command from npm, run from a checkout, session hooks,
|
|
41
|
-
|
|
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
|
|
45
|
-
no single agent owns, and every other agent you name gets a
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
skill
|
|
54
|
-
|
|
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.
|
|
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
|
|
65
|
-
that
|
|
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
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
133
|
+
skills from this checkout instead of the `skills` CLI copies; use one or the
|
|
93
134
|
other.
|
|
94
135
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
144
|
-
to
|
|
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
|
.
|
|
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
|
-
: {
|
|
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,
|