thurview 0.17.2 → 0.19.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 +52 -43
- package/dist/cli.js +74 -222
- package/dist/cli.js.map +1 -1
- package/dist/coverage.js +53 -148
- package/dist/coverage.js.map +1 -1
- package/dist/document/compile.js +51 -43
- package/dist/document/compile.js.map +1 -1
- package/dist/document/schema.js +25 -13
- package/dist/document/schema.js.map +1 -1
- package/dist/export.js +158 -0
- package/dist/export.js.map +1 -0
- package/dist/git.js +29 -0
- package/dist/git.js.map +1 -1
- package/dist/interfaces.js +7 -244
- package/dist/interfaces.js.map +1 -1
- package/dist/server/server.js +33 -22
- package/dist/server/server.js.map +1 -1
- package/dist/ui/app.css +14 -9
- package/dist/ui/app.js +164 -195
- package/dist/ui/app.js.map +3 -3
- package/package.json +1 -12
- package/skills/thurview/SKILL.md +51 -34
- package/skills/thurview/references/components.md +45 -22
- package/skills/thurview/references/document-authoring.md +22 -26
- package/skills/thurview/references/searching.md +74 -0
- package/skills/thurview-design/SKILL.md +21 -14
- package/skills/thurview-design/references/anchors-and-proposals.md +11 -9
- package/skills/thurview-explain/SKILL.md +47 -31
- package/skills/thurview-fix/SKILL.md +47 -42
- package/dist/graph.js +0 -568
- package/dist/graph.js.map +0 -1
package/README.md
CHANGED
|
@@ -23,13 +23,14 @@ _The reader's half of it; the agent is working off camera._
|
|
|
23
23
|
agent — at once if one is listening, queued if not — and the answer lands
|
|
24
24
|
in the same thread; a comment waits for your decision. Then you approve the
|
|
25
25
|
change or send it back with the comments attached.
|
|
26
|
-
- **
|
|
27
|
-
moved, which tests reach it,
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
- **The agent searches what the diff cannot show.** Who calls the symbol that
|
|
27
|
+
moved, which tests reach it, what imports the module it touched: the skills
|
|
28
|
+
give the agent `git grep` recipes run at the pinned commits, in any language,
|
|
29
|
+
and every claim that rests on a search carries the search, so you can run it
|
|
30
|
+
again.
|
|
30
31
|
- **The evidence is checked, not taken on trust.** An anchor whose lines
|
|
31
32
|
do not exist at the pinned commit, a call stack frame asserting a call the
|
|
32
|
-
diff does not show, an interface
|
|
33
|
+
diff does not show, an interface entry anchored on lines the change never
|
|
33
34
|
moved, a trust boundary crossing that resolves to nothing — publishing
|
|
34
35
|
rejects each one rather than rendering it.
|
|
35
36
|
|
|
@@ -115,9 +116,8 @@ Install the command itself, rather than leaving the skills to reach it through
|
|
|
115
116
|
npm install -g thurview # or: pnpm add -g thurview
|
|
116
117
|
```
|
|
117
118
|
|
|
118
|
-
Nothing else needs installing
|
|
119
|
-
|
|
120
|
-
what it reaches, who calls a symbol, what tests cover it and how files cluster.
|
|
119
|
+
Nothing else needs installing beyond `git`: callers, tests and importers are
|
|
120
|
+
found by the agent's own search at the pinned commits.
|
|
121
121
|
|
|
122
122
|
To run from a checkout instead:
|
|
123
123
|
|
|
@@ -224,13 +224,21 @@ Approve, or send it back with the comments:
|
|
|
224
224
|

|
|
226
226
|
|
|
227
|
+
To share a document with someone who will not run thurview,
|
|
228
|
+
`thurview export --out review.html` writes the published revision as one HTML
|
|
229
|
+
file: the same page, read only, with every snippet, diagram and diff resolved
|
|
230
|
+
at the pinned commits and the reader's threads shown as notes (`--no-threads`
|
|
231
|
+
leaves them out). It opens from disk, fetches nothing, and names no server and
|
|
232
|
+
no local path. Point `--out` at a folder for `<folder>/index.html`, such as a
|
|
233
|
+
repository's Pages folder.
|
|
234
|
+
|
|
227
235
|
## What's in a review
|
|
228
236
|
|
|
229
237
|
- **Interface delta**: above the document, what the change added to, changed
|
|
230
238
|
in or removed from the surfaces other code can reach - exported functions
|
|
231
|
-
and types,
|
|
232
|
-
|
|
233
|
-
|
|
239
|
+
and types, CLI flags, routes, config keys and formats. The agent declares
|
|
240
|
+
each one, and publish holds it to an anchor on lines the diff really moved,
|
|
241
|
+
so an entry cannot invent a feature the change did not deliver.
|
|
234
242
|
- **Review**: the document with a table of contents. Anchor links open the
|
|
235
243
|
exact code beside the text; peeks show it inline. Sequence diagrams, call
|
|
236
244
|
stack diffs and storage views are clickable down to the line.
|
|
@@ -240,11 +248,11 @@ changes](./media/review-decision.png)
|
|
|
240
248
|
jumps there.
|
|
241
249
|
- **Commits**: the commits between base and head.
|
|
242
250
|
- **Coverage** (explainers): every file in scope at the pinned commit, in one
|
|
243
|
-
of
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
than something the reader has to infer.
|
|
251
|
+
of four states - anchored in the document, placed on the map only, matched
|
|
252
|
+
by a search the agent recorded, or not examined - grouped by directory, with
|
|
253
|
+
each search and what it matched. Derived at publish, which re-runs every
|
|
254
|
+
recorded search with `git grep` at the pinned commit, so what the explainer
|
|
255
|
+
skipped is a stated fact rather than something the reader has to infer.
|
|
248
256
|
- **Map**: systems, containers, components and code, with what the change
|
|
249
257
|
added, removed or touched, linked to files and code.
|
|
250
258
|
- **Threads**: _Send to the agent_ delivers a question at once and the answer
|
|
@@ -268,21 +276,21 @@ bar.
|
|
|
268
276
|
|
|
269
277
|
## CLI
|
|
270
278
|
|
|
271
|
-
| Command
|
|
272
|
-
|
|
|
273
|
-
| `thurview scaffold [--pr N \| --base R --head R]`
|
|
274
|
-
| `thurview explain [<path>] [--commit R]`
|
|
275
|
-
| `thurview design [<path>] [--commit R]`
|
|
276
|
-
| `thurview info [--all]`
|
|
277
|
-
| `thurview publish --review ID [--view T] [--open]`
|
|
278
|
-
| `thurview open --review ID [--view T]`
|
|
279
|
-
| `thurview
|
|
280
|
-
| `thurview
|
|
281
|
-
| `thurview
|
|
282
|
-
| `thurview forge status\|prior\|pass\|submit\|reply`
|
|
283
|
-
| `thurview serve` / `thurview stop`
|
|
284
|
-
| `thurview setup hooks\|skill\|status`
|
|
285
|
-
| `thurview update`
|
|
279
|
+
| Command | Purpose |
|
|
280
|
+
| ------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
281
|
+
| `thurview scaffold [--pr N \| --base R --head R]` | Create a review pinned to exact commits (`--update` re-pins) |
|
|
282
|
+
| `thurview explain [<path>] [--commit R]` | Create a code explainer of a codebase or subsystem at one commit |
|
|
283
|
+
| `thurview design [<path>] [--commit R]` | Create a design of what to build, pinned to the commit it argues from |
|
|
284
|
+
| `thurview info [--all]` | Reviews, explainers and designs bound to this worktree |
|
|
285
|
+
| `thurview publish --review ID [--view T] [--open]` | Validate the document and map, seal a revision |
|
|
286
|
+
| `thurview open --review ID [--view T]` | Start the server if needed and open the browser |
|
|
287
|
+
| `thurview export --review ID --out PATH [--no-threads]` | Write the published revision as one static HTML file, no server |
|
|
288
|
+
| `thurview wait --review ID [--timeout S]` | Block until the reader needs the agent |
|
|
289
|
+
| `thurview threads list\|get\|reply\|resolve` | Read and answer threads |
|
|
290
|
+
| `thurview forge status\|prior\|pass\|submit\|reply` | Read a change request through its forge, and post the review back |
|
|
291
|
+
| `thurview serve` / `thurview stop` | Run the server in the foreground / stop the background one |
|
|
292
|
+
| `thurview setup hooks\|skill\|status` | Session hooks, agent skill, install state |
|
|
293
|
+
| `thurview update` | Self-update from npm |
|
|
286
294
|
|
|
287
295
|
thurview is an [AXI](https://axi.md): built for agents that drive it through a
|
|
288
296
|
shell. Output is [TOON](https://toonformat.dev) on stdout, errors are
|
|
@@ -298,19 +306,19 @@ stderr.
|
|
|
298
306
|
|
|
299
307
|
The `thurview-fix` skill reviews a branch, a commit range or a pull or merge
|
|
300
308
|
request, fixes what it is sure of and reports the rest, with no browser and no
|
|
301
|
-
approval step. For each changed symbol it
|
|
302
|
-
which tests
|
|
309
|
+
approval step. For each changed symbol it searches, at the pinned commits, who
|
|
310
|
+
calls it and which tests name it, so a finding can name a caller the diff never
|
|
311
|
+
shows.
|
|
303
312
|
Fixes that pass the repository's own tests and lint land as one local commit;
|
|
304
313
|
nothing is pushed unless you ask.
|
|
305
314
|
|
|
306
315
|
```sh
|
|
307
|
-
|
|
308
|
-
|
|
316
|
+
git grep -n -E -e '\bdiscount *\(' <head> -- # every call site of one symbol
|
|
317
|
+
git grep -n -E -e '\bdiscount\b' <head> -- '*test*' '*spec*' # the tests that name it
|
|
309
318
|
```
|
|
310
319
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
of its call and whether any test reaches it.
|
|
320
|
+
Each finding carries the search behind it, so "no other caller" is a line you
|
|
321
|
+
can run.
|
|
314
322
|
|
|
315
323
|
With `--post`, the skill posts the findings it did not fix as inline comments
|
|
316
324
|
on the change request, through `thurview forge`:
|
|
@@ -358,9 +366,10 @@ The agent writes three files in `~/.thurview/reviews/<id>/`:
|
|
|
358
366
|
blocks `peek`, `sequence`, `flow`, `callstack` and `database` add components.
|
|
359
367
|
`## Heading {collapsed}` folds a section by default.
|
|
360
368
|
- `data.yaml`: typed inputs: `actors`, `anchors` (file, from, to, graph),
|
|
361
|
-
`stores`, `interfaces` (
|
|
362
|
-
|
|
363
|
-
the change lets input cross a trust boundary
|
|
369
|
+
`stores`, `interfaces` (one entry per interface the change moved, each
|
|
370
|
+
anchored on the lines that moved it), `security`, where a review says where
|
|
371
|
+
the change lets input cross a trust boundary, and `searches`, where an
|
|
372
|
+
explainer records what it searched for its Coverage tab.
|
|
364
373
|
- `map.yaml`: the software map at head, optionally at base. In an explainer it
|
|
365
374
|
carries the breadth the prose has no room for, and a node's `files` globs are
|
|
366
375
|
what let a file count as placed rather than not examined.
|
|
@@ -377,14 +386,14 @@ defined in one place — the `thurview-fix` skill's finding rules — and
|
|
|
377
386
|
nothing else restates it.
|
|
378
387
|
|
|
379
388
|
An explainer writes the same files, minus `interfaces` and `security`: there is
|
|
380
|
-
no change to
|
|
389
|
+
no change to take a delta from or to carry input across a boundary, and
|
|
381
390
|
`graph: base` on an anchor is an error because there is one commit.
|
|
382
391
|
|
|
383
392
|
A design writes the same files, and `interfaces` means something else in it:
|
|
384
393
|
each entry is a **proposal** — what the design would add, change or remove,
|
|
385
394
|
with the anchor of the code that proposal lands in, replaces or plugs into
|
|
386
395
|
today. `graph: base` and `security` are errors for the same reason as in an
|
|
387
|
-
explainer,
|
|
396
|
+
explainer, `searches` is an error because a design has no Coverage tab, and a
|
|
388
397
|
design that proposes nothing is refused: that document is an explainer. In its
|
|
389
398
|
`map.yaml`, `base` is the structure as it stands and `nodes` the structure it
|
|
390
399
|
proposes, so a proposed part may own files that do not exist yet while a `base`
|
package/dist/cli.js
CHANGED
|
@@ -11,13 +11,14 @@ import open from "open";
|
|
|
11
11
|
import * as g from "./git.js";
|
|
12
12
|
import { SCHEMA, home, newId, now, readReview, writeReview, listReviews, reviewsFor, reviewDir, revisionDir, passFile, readThreads, readText, writeText, writeJson, readJson, serverStateFile, deleteReview, kindOf, } from "./store.js";
|
|
13
13
|
import { compileDocument, compileMap, globToRegExp } from "./document/compile.js";
|
|
14
|
-
import { computeCoverage, scopeGlob
|
|
14
|
+
import { computeCoverage, scopeGlob } from "./coverage.js";
|
|
15
15
|
import { parseTheme, compileTheme } from "./theme.js";
|
|
16
16
|
import { registerTheme } from "./highlight.js";
|
|
17
17
|
import { replyThread, setThreadStatus, needsAgent } from "./threads.js";
|
|
18
18
|
import { targetLabel, truncate } from "./thread-state.js";
|
|
19
19
|
import { attach } from "./presence.js";
|
|
20
20
|
import { startServer } from "./server/server.js";
|
|
21
|
+
import { exportReview, localTarget } from "./export.js";
|
|
21
22
|
import { parseFlags, helpFor, str, bool } from "./flags.js";
|
|
22
23
|
import { forgeFor, repoOf, summariseCi, } from "./forge/index.js";
|
|
23
24
|
import { parseSubmission, longComments, buildPass } from "./forge/submission.js";
|
|
@@ -205,9 +206,6 @@ async function ensureServer() {
|
|
|
205
206
|
function reviewUrl(base, id, view) {
|
|
206
207
|
return `${base}/review/${id}${view ? `#/${view}` : ""}`;
|
|
207
208
|
}
|
|
208
|
-
function pinnedOf(review) {
|
|
209
|
-
return { worktree: review.worktree, pins: review.pins, dir: reviewDir(review.id) };
|
|
210
|
-
}
|
|
211
209
|
/**
|
|
212
210
|
* Base and head as `--base` and `--head` name them. Head defaults to HEAD and
|
|
213
211
|
* base to where head forked from trunk, so a branch is diffed against what it
|
|
@@ -227,28 +225,6 @@ async function pinRange(worktree, base, head, usage) {
|
|
|
227
225
|
]);
|
|
228
226
|
}
|
|
229
227
|
}
|
|
230
|
-
/**
|
|
231
|
-
* The interface delta at two pinned commits. The graphs are cached per commit
|
|
232
|
-
* under `at.dir`, so publish and `graph interfaces` build them once between
|
|
233
|
-
* them; both modules load lazily to keep tree-sitter off every other command's path.
|
|
234
|
-
*/
|
|
235
|
-
async function deltaFor(at, base, head) {
|
|
236
|
-
const graph = await import("./graph.js");
|
|
237
|
-
const { interfaceDelta } = await import("./interfaces.js");
|
|
238
|
-
const b = base ?? (await graph.graphAt(at.worktree, at.pins.base, at.dir));
|
|
239
|
-
const h = head ?? (await graph.graphAt(at.worktree, at.pins.head, at.dir));
|
|
240
|
-
const changes = await g.lineChanges(at.worktree, at.pins.base, at.pins.head);
|
|
241
|
-
const changed = await g.changedFiles(at.worktree, at.pins.base, at.pins.head);
|
|
242
|
-
return interfaceDelta({
|
|
243
|
-
cwd: at.worktree,
|
|
244
|
-
pins: at.pins,
|
|
245
|
-
base: b,
|
|
246
|
-
head: h,
|
|
247
|
-
impact: graph.impact(b, h, changes, 1),
|
|
248
|
-
changes,
|
|
249
|
-
changed,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
228
|
async function guidanceFiles(repoRoot) {
|
|
253
229
|
return [join(home(), "THURVIEW.md"), join(repoRoot, "THURVIEW.md")].filter((p) => existsSync(p));
|
|
254
230
|
}
|
|
@@ -272,8 +248,13 @@ const TEMPLATE_DATA = `# Typed inputs for review.md: actors, anchors and stores.
|
|
|
272
248
|
# title: PTY spawn site
|
|
273
249
|
# peek: { file: src/pty.ts, from: 214, to: 223 } # add graph: base for the old side
|
|
274
250
|
#
|
|
275
|
-
# interfaces holds one
|
|
276
|
-
#
|
|
251
|
+
# interfaces holds one entry per interface the change added, changed or removed:
|
|
252
|
+
# a CLI flag, an exported function, an HTTP route, a config key. Name it, say what
|
|
253
|
+
# it lets a consumer do, and anchor it on the lines that moved it (a base-side
|
|
254
|
+
# anchor for a removed one). Find what depends on it with git grep at the pins.
|
|
255
|
+
#
|
|
256
|
+
# interfaces:
|
|
257
|
+
# shellFlag: { name: --shell, change: added, capability: Runs in the named shell., anchor: spawn }
|
|
277
258
|
#
|
|
278
259
|
# security says where this change lets input cross a trust boundary. Leave it
|
|
279
260
|
# out (or write \`security: pending\`) until you have looked and the document says
|
|
@@ -324,14 +305,23 @@ const TEMPLATE_EXPLAIN_DATA = `# Typed inputs for the explainer: actors, anchors
|
|
|
324
305
|
# dispatch:
|
|
325
306
|
# title: where a request picks its handler
|
|
326
307
|
# peek: { file: src/server/router.ts, from: 41, to: 58 }
|
|
308
|
+
#
|
|
309
|
+
# searches records the searches you ran to find callers, tests and importers.
|
|
310
|
+
# publish re-runs each one with \`git grep -E\` at the pinned commit, and the
|
|
311
|
+
# Coverage tab counts the files it matches as searched. A search that matched
|
|
312
|
+
# nothing is stated too.
|
|
313
|
+
#
|
|
314
|
+
# searches:
|
|
315
|
+
# dispatchCallers: { pattern: '\\bdispatch\\(', paths: ["src/"], why: who routes a request }
|
|
327
316
|
actors: {}
|
|
328
317
|
anchors: {}
|
|
329
318
|
stores: {}
|
|
319
|
+
searches: {}
|
|
330
320
|
`;
|
|
331
321
|
const TEMPLATE_EXPLAIN_MAP = `# The structure of the code at the pinned commit: systems, containers,
|
|
332
322
|
# components, code. The map carries breadth so the prose can carry depth, and a
|
|
333
323
|
# node's \`files\` globs are what tell the Coverage tab a file was at least placed.
|
|
334
|
-
# Seed it from \`
|
|
324
|
+
# Seed it from the directories: \`git ls-tree -d -r --name-only <commit>\`.
|
|
335
325
|
nodes: []
|
|
336
326
|
edges: []
|
|
337
327
|
`;
|
|
@@ -375,7 +365,7 @@ const TEMPLATE_DESIGN_MAP = `# The structure the design proposes, with the struc
|
|
|
375
365
|
# \`base\`, so the Map tab shows what it adds, changes and removes. A node under
|
|
376
366
|
# \`nodes\` may own files that do not exist yet - that is a proposed part. A node
|
|
377
367
|
# under \`base\` may not: it is a claim about today, and publish warns.
|
|
378
|
-
# Seed base from \`
|
|
368
|
+
# Seed base from the directories: \`git ls-tree -d -r --name-only <commit>\`.
|
|
379
369
|
nodes: []
|
|
380
370
|
edges: []
|
|
381
371
|
`;
|
|
@@ -576,6 +566,27 @@ const SPECS = {
|
|
|
576
566
|
},
|
|
577
567
|
examples: ["thurview open", "thurview open --review <id> --view map --no-browser"],
|
|
578
568
|
},
|
|
569
|
+
export: {
|
|
570
|
+
description: "Write a published document to one self-contained HTML file that opens with no server",
|
|
571
|
+
flags: {
|
|
572
|
+
review: {
|
|
573
|
+
kind: "string",
|
|
574
|
+
help: "review id prefix (default: the review for this worktree)",
|
|
575
|
+
},
|
|
576
|
+
out: {
|
|
577
|
+
kind: "string",
|
|
578
|
+
help: "a .html file, or a folder to write index.html into (a Pages folder works)",
|
|
579
|
+
},
|
|
580
|
+
threads: {
|
|
581
|
+
kind: "boolean",
|
|
582
|
+
help: "show the reader's threads as static notes (use --no-threads to leave them out)",
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
examples: [
|
|
586
|
+
"thurview export --out review.html",
|
|
587
|
+
"thurview export --review <id> --out docs/reviews/auth --no-threads",
|
|
588
|
+
],
|
|
589
|
+
},
|
|
579
590
|
serve: {
|
|
580
591
|
description: "Run the review server in the foreground",
|
|
581
592
|
flags: {
|
|
@@ -616,28 +627,6 @@ const SPECS = {
|
|
|
616
627
|
"thurview threads resolve <threadId>",
|
|
617
628
|
],
|
|
618
629
|
},
|
|
619
|
-
graph: {
|
|
620
|
-
description: "Ask the code graph at the pinned commits: the interface delta, what the change reaches, callers, tests, architecture",
|
|
621
|
-
args: "interfaces|impact|callers <name>|tests-for <name>|architecture",
|
|
622
|
-
flags: {
|
|
623
|
-
review: { kind: "string", help: "review id prefix" },
|
|
624
|
-
base: {
|
|
625
|
-
kind: "string",
|
|
626
|
-
help: "instead of --review: base revision (default: trunk fork point)",
|
|
627
|
-
},
|
|
628
|
-
head: { kind: "string", help: "instead of --review: head revision (default: HEAD)" },
|
|
629
|
-
graph: { kind: "string", help: "callers, tests-for: head or base", default: "head" },
|
|
630
|
-
depth: { kind: "string", help: "how many caller hops to follow", default: "2" },
|
|
631
|
-
},
|
|
632
|
-
examples: [
|
|
633
|
-
"thurview graph interfaces",
|
|
634
|
-
"thurview graph impact",
|
|
635
|
-
"thurview graph impact --base main --head HEAD",
|
|
636
|
-
"thurview graph callers login",
|
|
637
|
-
"thurview graph tests-for login --graph base",
|
|
638
|
-
"thurview graph architecture",
|
|
639
|
-
],
|
|
640
|
-
},
|
|
641
630
|
forge: {
|
|
642
631
|
description: "Read a pull or merge request through its forge, and post the review back to it",
|
|
643
632
|
args: 'status|prior|pass|submit --file <path>|reply <threadId> --body "<text>"',
|
|
@@ -888,7 +877,7 @@ const commands = {
|
|
|
888
877
|
guidance: await guidanceFiles(worktree),
|
|
889
878
|
help: [
|
|
890
879
|
`Edit ${join(dir, "review.md")} and data.yaml, then run \`thurview publish --review ${short(review.id)}\``,
|
|
891
|
-
`Run \`
|
|
880
|
+
`Run \`git grep -n -w <symbol> ${head.slice(0, 12)} --\` in ${worktree} for who reaches a symbol the change moved`,
|
|
892
881
|
stat.additions + stat.deletions < 300
|
|
893
882
|
? `Small change: run \`thurview publish --review ${short(review.id)} --view files --open\` now, then write the document`
|
|
894
883
|
: `Run \`git diff ${base.slice(0, 12)} ${head.slice(0, 12)}\` in ${worktree} to study the change`,
|
|
@@ -923,7 +912,7 @@ const commands = {
|
|
|
923
912
|
scale: { filesInScope: pinned.inScope.length },
|
|
924
913
|
guidance: await guidanceFiles(worktree),
|
|
925
914
|
help: [
|
|
926
|
-
`Run \`
|
|
915
|
+
`Run \`git ls-tree -d -r --name-only ${commit.slice(0, 12)}\` in ${worktree} for the directories in scope`,
|
|
927
916
|
`Author ${join(dir, "map.yaml")} first: it carries the breadth the prose cannot`,
|
|
928
917
|
`Edit ${join(dir, "review.md")} and data.yaml, then run \`thurview publish --review ${short(review.id)}\``,
|
|
929
918
|
],
|
|
@@ -957,7 +946,7 @@ const commands = {
|
|
|
957
946
|
scale: { filesInScope: pinned.inScope.length },
|
|
958
947
|
guidance: await guidanceFiles(worktree),
|
|
959
948
|
help: [
|
|
960
|
-
`Run \`
|
|
949
|
+
`Run \`git grep -n -w <symbol> ${commit.slice(0, 12)} --\` in ${worktree} for who depends on what the design changes`,
|
|
961
950
|
`Declare in ${join(dir, "data.yaml")} what the design would add, change or remove, each anchored to the code it lands in today`,
|
|
962
951
|
`Edit ${join(dir, "review.md")}, then run \`thurview publish --review ${short(review.id)}\``,
|
|
963
952
|
],
|
|
@@ -1035,21 +1024,6 @@ const commands = {
|
|
|
1035
1024
|
}
|
|
1036
1025
|
const themeName = theme ? await registerTheme(theme.shiki) : undefined;
|
|
1037
1026
|
const kind = kindOf(review);
|
|
1038
|
-
// An explainer has one pinned commit, so there is no delta to derive: the
|
|
1039
|
-
// panel above its document states coverage instead.
|
|
1040
|
-
let interfaces = null;
|
|
1041
|
-
if (kind === "review") {
|
|
1042
|
-
try {
|
|
1043
|
-
interfaces = await deltaFor(pinnedOf(review));
|
|
1044
|
-
}
|
|
1045
|
-
catch (e) {
|
|
1046
|
-
diags.push({
|
|
1047
|
-
level: "warning",
|
|
1048
|
-
file: "review.md",
|
|
1049
|
-
message: `the interface delta is unavailable: ${e.message}`,
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
1027
|
const doc = await compileDocument({
|
|
1054
1028
|
cwd: review.worktree,
|
|
1055
1029
|
pins: review.pins,
|
|
@@ -1057,7 +1031,6 @@ const commands = {
|
|
|
1057
1031
|
dataYaml: dataYaml ?? "",
|
|
1058
1032
|
kind,
|
|
1059
1033
|
...(themeName ? { themeName } : {}),
|
|
1060
|
-
interfaces,
|
|
1061
1034
|
});
|
|
1062
1035
|
diags.push(...doc.diagnostics);
|
|
1063
1036
|
let map = null;
|
|
@@ -1095,13 +1068,11 @@ const commands = {
|
|
|
1095
1068
|
let coverage = null;
|
|
1096
1069
|
if (kind === "explainer") {
|
|
1097
1070
|
try {
|
|
1098
|
-
const graph = await import("./graph.js");
|
|
1099
|
-
const g0 = await graph.graphAt(review.worktree, review.pins.head, dir);
|
|
1100
1071
|
coverage = computeCoverage({
|
|
1101
1072
|
commit: review.pins.head,
|
|
1102
1073
|
scope: review.binding.name,
|
|
1103
1074
|
allFiles: await g.listFiles(review.worktree, review.pins.head),
|
|
1104
|
-
|
|
1075
|
+
searches: doc.searches,
|
|
1105
1076
|
anchored: Object.values(doc.document.anchors)
|
|
1106
1077
|
.map((a) => a.peek?.file)
|
|
1107
1078
|
.filter((f) => !!f),
|
|
@@ -1241,6 +1212,35 @@ const commands = {
|
|
|
1241
1212
|
],
|
|
1242
1213
|
};
|
|
1243
1214
|
},
|
|
1215
|
+
async export(args) {
|
|
1216
|
+
const p = parseFlags("export", args, spec("export").flags);
|
|
1217
|
+
const out = str(p, "out");
|
|
1218
|
+
if (!out)
|
|
1219
|
+
throw new AxiError("--out is required", "VALIDATION_ERROR", [
|
|
1220
|
+
"Run `thurview export --out review.html`, or `--out <folder>` for <folder>/index.html",
|
|
1221
|
+
]);
|
|
1222
|
+
const review = await resolveReview(str(p, "review"), { terminal: true });
|
|
1223
|
+
if (!review.revision)
|
|
1224
|
+
throw new AxiError(`review ${short(review.id)} is not published yet`, "VALIDATION_ERROR", [
|
|
1225
|
+
`Run \`thurview publish --review ${short(review.id)}\` first`,
|
|
1226
|
+
]);
|
|
1227
|
+
const threads = p.flags["threads"] !== false;
|
|
1228
|
+
const html = await exportReview(review, { threads });
|
|
1229
|
+
const file = await localTarget(out).write(html);
|
|
1230
|
+
return {
|
|
1231
|
+
exported: {
|
|
1232
|
+
id: short(review.id),
|
|
1233
|
+
revision: review.revision,
|
|
1234
|
+
file,
|
|
1235
|
+
bytes: Buffer.byteLength(html),
|
|
1236
|
+
threads,
|
|
1237
|
+
},
|
|
1238
|
+
help: [
|
|
1239
|
+
"Open the file in any browser; it needs no thurview server and fetches nothing",
|
|
1240
|
+
"Commit it to a Pages folder or copy it to a static host to share it",
|
|
1241
|
+
],
|
|
1242
|
+
};
|
|
1243
|
+
},
|
|
1244
1244
|
async serve(args) {
|
|
1245
1245
|
const p = parseFlags("serve", args, spec("serve").flags);
|
|
1246
1246
|
const port = str(p, "port");
|
|
@@ -1358,154 +1358,6 @@ const commands = {
|
|
|
1358
1358
|
await (handedQuestion ? listening.handOff() : listening.stop());
|
|
1359
1359
|
}
|
|
1360
1360
|
},
|
|
1361
|
-
async graph(args) {
|
|
1362
|
-
const sub = args[0];
|
|
1363
|
-
const rest = args.slice(1);
|
|
1364
|
-
const s = spec("graph").flags;
|
|
1365
|
-
const help = [
|
|
1366
|
-
"thurview graph interfaces",
|
|
1367
|
-
"thurview graph impact",
|
|
1368
|
-
"thurview graph callers <name> [--graph base]",
|
|
1369
|
-
"thurview graph tests-for <name> [--graph base]",
|
|
1370
|
-
"thurview graph architecture",
|
|
1371
|
-
];
|
|
1372
|
-
if (!sub || !["interfaces", "impact", "callers", "tests-for", "architecture"].includes(sub))
|
|
1373
|
-
throw new AxiError(`unknown graph command${sub ? ` ${sub}` : ""}`, "VALIDATION_ERROR", help);
|
|
1374
|
-
const named = sub === "callers" || sub === "tests-for";
|
|
1375
|
-
const p = parseFlags(`graph ${sub}`, rest, s, named ? 1 : 0);
|
|
1376
|
-
const name = p.positional[0];
|
|
1377
|
-
if (named && !name)
|
|
1378
|
-
throw new AxiError(`graph ${sub} needs a symbol name`, "VALIDATION_ERROR", help);
|
|
1379
|
-
const depth = Number(str(p, "depth") ?? "2");
|
|
1380
|
-
if (!Number.isInteger(depth) || depth < 1)
|
|
1381
|
-
throw new AxiError("--depth must be a positive integer", "VALIDATION_ERROR", help);
|
|
1382
|
-
const side = str(p, "graph") ?? "head";
|
|
1383
|
-
if (side !== "head" && side !== "base")
|
|
1384
|
-
throw new AxiError("--graph must be head or base", "VALIDATION_ERROR", help);
|
|
1385
|
-
const baseRef = str(p, "base");
|
|
1386
|
-
const headRef = str(p, "head");
|
|
1387
|
-
const commits = baseRef !== undefined || headRef !== undefined;
|
|
1388
|
-
if (commits && str(p, "review"))
|
|
1389
|
-
throw new AxiError("pass --review or --base/--head, not both", "VALIDATION_ERROR", help);
|
|
1390
|
-
const graph = await import("./graph.js");
|
|
1391
|
-
const review = commits ? null : await resolveReview(str(p, "review"));
|
|
1392
|
-
let t;
|
|
1393
|
-
if (review)
|
|
1394
|
-
t = pinnedOf(review);
|
|
1395
|
-
else {
|
|
1396
|
-
const worktree = await worktreeOf(process.cwd());
|
|
1397
|
-
if (!worktree)
|
|
1398
|
-
throw new AxiError("not inside a git repository", "VALIDATION_ERROR", [
|
|
1399
|
-
"Run inside the source worktree, or pass --review <id>",
|
|
1400
|
-
]);
|
|
1401
|
-
// No review directory owns these graphs, and a commit's graph is the same
|
|
1402
|
-
// whoever asks, so they share one cache under the thurview home.
|
|
1403
|
-
const pins = await pinRange(worktree, baseRef, headRef, "thurview graph impact --base <ref>");
|
|
1404
|
-
t = { worktree, pins, dir: home() };
|
|
1405
|
-
}
|
|
1406
|
-
// A next step has to name the same commits, or it answers about another change.
|
|
1407
|
-
const again = review ? "" : ` --base ${short(t.pins.base)} --head ${short(t.pins.head)}`;
|
|
1408
|
-
if (review && kindOf(review) !== "review" && (sub === "interfaces" || sub === "impact"))
|
|
1409
|
-
throw new AxiError(`graph ${sub} compares two commits; ${kindOf(review) === "design" ? "a design" : "an explainer"} is pinned to one`, "VALIDATION_ERROR", [
|
|
1410
|
-
`Run \`thurview graph architecture --review ${short(review.id)}\` for the structure at that commit`,
|
|
1411
|
-
`Run \`thurview graph callers <name> --review ${short(review.id)}\` to follow one symbol`,
|
|
1412
|
-
]);
|
|
1413
|
-
const at = (commit) => graph.graphAt(t.worktree, commit, t.dir);
|
|
1414
|
-
if (sub === "callers" || sub === "tests-for") {
|
|
1415
|
-
const g = await at(side === "base" ? t.pins.base : t.pins.head);
|
|
1416
|
-
const pins = {
|
|
1417
|
-
graph: side,
|
|
1418
|
-
commit: short(g.commit),
|
|
1419
|
-
languages: graph.LANGUAGES.join(","),
|
|
1420
|
-
truncated: g.truncated,
|
|
1421
|
-
};
|
|
1422
|
-
if (sub === "callers")
|
|
1423
|
-
return {
|
|
1424
|
-
...pins,
|
|
1425
|
-
symbol: name,
|
|
1426
|
-
callers: graph.callers(g, name),
|
|
1427
|
-
help: [`Run \`thurview graph tests-for ${name}${again}\` to see what exercises it`],
|
|
1428
|
-
};
|
|
1429
|
-
return {
|
|
1430
|
-
...pins,
|
|
1431
|
-
symbol: name,
|
|
1432
|
-
depth,
|
|
1433
|
-
tests: graph.testsFor(g, name, depth),
|
|
1434
|
-
help: [`Run \`thurview graph callers ${name}${again}\` for every reference`],
|
|
1435
|
-
};
|
|
1436
|
-
}
|
|
1437
|
-
const base = await at(t.pins.base);
|
|
1438
|
-
const head = await at(t.pins.head);
|
|
1439
|
-
const pins = {
|
|
1440
|
-
base: short(base.commit),
|
|
1441
|
-
head: short(head.commit),
|
|
1442
|
-
languages: graph.LANGUAGES.join(","),
|
|
1443
|
-
};
|
|
1444
|
-
if (sub === "interfaces") {
|
|
1445
|
-
const delta = await deltaFor(t, base, head);
|
|
1446
|
-
return {
|
|
1447
|
-
...pins,
|
|
1448
|
-
verdict: delta.verdict,
|
|
1449
|
-
interfaces: delta.entries.map((e) => ({
|
|
1450
|
-
id: e.id,
|
|
1451
|
-
change: e.change,
|
|
1452
|
-
name: e.name,
|
|
1453
|
-
was: e.was,
|
|
1454
|
-
kind: e.kind,
|
|
1455
|
-
file: e.file,
|
|
1456
|
-
line: e.line,
|
|
1457
|
-
graph: e.graph,
|
|
1458
|
-
})),
|
|
1459
|
-
internal: delta.internal,
|
|
1460
|
-
unreadable: delta.unreadable,
|
|
1461
|
-
truncated: delta.truncated,
|
|
1462
|
-
help: [
|
|
1463
|
-
...(review
|
|
1464
|
-
? ["Write one capability line per entry in data.yaml under `interfaces`, keyed by id"]
|
|
1465
|
-
: []),
|
|
1466
|
-
`Run \`thurview graph callers <name>${again}\` to see who a removed or changed interface reached`,
|
|
1467
|
-
],
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
if (sub === "impact") {
|
|
1471
|
-
const changes = await g.lineChanges(t.worktree, t.pins.base, t.pins.head);
|
|
1472
|
-
return {
|
|
1473
|
-
...pins,
|
|
1474
|
-
depth,
|
|
1475
|
-
...graph.impact(base, head, changes, depth),
|
|
1476
|
-
help: [
|
|
1477
|
-
`Run \`thurview graph callers <name>${again}\` to follow one symbol`,
|
|
1478
|
-
`Run \`thurview graph architecture${again}\` for the module structure and its diff`,
|
|
1479
|
-
],
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
// An explainer and a design are both scoped to a path and pinned to one
|
|
1483
|
-
// commit, so the structure they get back is that path's, not the
|
|
1484
|
-
// repository's: for an explainer, the same bound the Coverage tab accounts
|
|
1485
|
-
// for; for a design, the structure it has to fit.
|
|
1486
|
-
if (review && kindOf(review) !== "review") {
|
|
1487
|
-
const scope = review.binding.name;
|
|
1488
|
-
const g0 = scopeGraph(head, scope);
|
|
1489
|
-
const allFiles = await g.listFiles(t.worktree, t.pins.head);
|
|
1490
|
-
const { diff: _diff, truncated: _truncated, ...rest } = graph.architecture(g0, g0);
|
|
1491
|
-
return {
|
|
1492
|
-
commit: short(head.commit),
|
|
1493
|
-
scope,
|
|
1494
|
-
languages: pins.languages,
|
|
1495
|
-
truncated: scopeTruncated(allFiles, head, scope),
|
|
1496
|
-
...rest,
|
|
1497
|
-
help: [
|
|
1498
|
-
"Seed map.yaml nodes from communities, their `files` from a community's files, and edges from edges",
|
|
1499
|
-
"A file in no community is outside the languages the graph reads; `thurview publish` counts those",
|
|
1500
|
-
],
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
return {
|
|
1504
|
-
...pins,
|
|
1505
|
-
...graph.architecture(base, head),
|
|
1506
|
-
help: ["Seed map.yaml nodes from communities and edges from diff.added"],
|
|
1507
|
-
};
|
|
1508
|
-
},
|
|
1509
1361
|
async threads(args) {
|
|
1510
1362
|
const sub = args[0];
|
|
1511
1363
|
const rest = args.slice(1);
|