thurview 0.17.1 → 0.18.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 +43 -43
- package/dist/cli.js +23 -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/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/ui/app.css +29 -10
- package/dist/ui/app.js +169 -192
- package/dist/ui/app.js.map +3 -3
- package/package.json +1 -12
- package/skills/thurview/SKILL.md +32 -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 +17 -14
- package/skills/thurview-design/references/anchors-and-proposals.md +11 -9
- package/skills/thurview-explain/SKILL.md +44 -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
|
|
|
@@ -228,9 +228,9 @@ changes](./media/review-decision.png)
|
|
|
228
228
|
|
|
229
229
|
- **Interface delta**: above the document, what the change added to, changed
|
|
230
230
|
in or removed from the surfaces other code can reach - exported functions
|
|
231
|
-
and types,
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
and types, CLI flags, routes, config keys and formats. The agent declares
|
|
232
|
+
each one, and publish holds it to an anchor on lines the diff really moved,
|
|
233
|
+
so an entry cannot invent a feature the change did not deliver.
|
|
234
234
|
- **Review**: the document with a table of contents. Anchor links open the
|
|
235
235
|
exact code beside the text; peeks show it inline. Sequence diagrams, call
|
|
236
236
|
stack diffs and storage views are clickable down to the line.
|
|
@@ -240,11 +240,11 @@ changes](./media/review-decision.png)
|
|
|
240
240
|
jumps there.
|
|
241
241
|
- **Commits**: the commits between base and head.
|
|
242
242
|
- **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.
|
|
243
|
+
of four states - anchored in the document, placed on the map only, matched
|
|
244
|
+
by a search the agent recorded, or not examined - grouped by directory, with
|
|
245
|
+
each search and what it matched. Derived at publish, which re-runs every
|
|
246
|
+
recorded search with `git grep` at the pinned commit, so what the explainer
|
|
247
|
+
skipped is a stated fact rather than something the reader has to infer.
|
|
248
248
|
- **Map**: systems, containers, components and code, with what the change
|
|
249
249
|
added, removed or touched, linked to files and code.
|
|
250
250
|
- **Threads**: _Send to the agent_ delivers a question at once and the answer
|
|
@@ -268,21 +268,20 @@ bar.
|
|
|
268
268
|
|
|
269
269
|
## CLI
|
|
270
270
|
|
|
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 wait --review ID [--timeout S]`
|
|
280
|
-
| `thurview threads list\|get\|reply\|resolve`
|
|
281
|
-
| `thurview
|
|
282
|
-
| `thurview
|
|
283
|
-
| `thurview
|
|
284
|
-
| `thurview
|
|
285
|
-
| `thurview update` | Self-update from npm |
|
|
271
|
+
| Command | Purpose |
|
|
272
|
+
| --------------------------------------------------- | --------------------------------------------------------------------- |
|
|
273
|
+
| `thurview scaffold [--pr N \| --base R --head R]` | Create a review pinned to exact commits (`--update` re-pins) |
|
|
274
|
+
| `thurview explain [<path>] [--commit R]` | Create a code explainer of a codebase or subsystem at one commit |
|
|
275
|
+
| `thurview design [<path>] [--commit R]` | Create a design of what to build, pinned to the commit it argues from |
|
|
276
|
+
| `thurview info [--all]` | Reviews, explainers and designs bound to this worktree |
|
|
277
|
+
| `thurview publish --review ID [--view T] [--open]` | Validate the document and map, seal a revision |
|
|
278
|
+
| `thurview open --review ID [--view T]` | Start the server if needed and open the browser |
|
|
279
|
+
| `thurview wait --review ID [--timeout S]` | Block until the reader needs the agent |
|
|
280
|
+
| `thurview threads list\|get\|reply\|resolve` | Read and answer threads |
|
|
281
|
+
| `thurview forge status\|prior\|pass\|submit\|reply` | Read a change request through its forge, and post the review back |
|
|
282
|
+
| `thurview serve` / `thurview stop` | Run the server in the foreground / stop the background one |
|
|
283
|
+
| `thurview setup hooks\|skill\|status` | Session hooks, agent skill, install state |
|
|
284
|
+
| `thurview update` | Self-update from npm |
|
|
286
285
|
|
|
287
286
|
thurview is an [AXI](https://axi.md): built for agents that drive it through a
|
|
288
287
|
shell. Output is [TOON](https://toonformat.dev) on stdout, errors are
|
|
@@ -298,19 +297,19 @@ stderr.
|
|
|
298
297
|
|
|
299
298
|
The `thurview-fix` skill reviews a branch, a commit range or a pull or merge
|
|
300
299
|
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
|
|
300
|
+
approval step. For each changed symbol it searches, at the pinned commits, who
|
|
301
|
+
calls it and which tests name it, so a finding can name a caller the diff never
|
|
302
|
+
shows.
|
|
303
303
|
Fixes that pass the repository's own tests and lint land as one local commit;
|
|
304
304
|
nothing is pushed unless you ask.
|
|
305
305
|
|
|
306
306
|
```sh
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
git grep -n -E -e '\bdiscount *\(' <head> -- # every call site of one symbol
|
|
308
|
+
git grep -n -E -e '\bdiscount\b' <head> -- '*test*' '*spec*' # the tests that name it
|
|
309
309
|
```
|
|
310
310
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
of its call and whether any test reaches it.
|
|
311
|
+
Each finding carries the search behind it, so "no other caller" is a line you
|
|
312
|
+
can run.
|
|
314
313
|
|
|
315
314
|
With `--post`, the skill posts the findings it did not fix as inline comments
|
|
316
315
|
on the change request, through `thurview forge`:
|
|
@@ -358,9 +357,10 @@ The agent writes three files in `~/.thurview/reviews/<id>/`:
|
|
|
358
357
|
blocks `peek`, `sequence`, `flow`, `callstack` and `database` add components.
|
|
359
358
|
`## Heading {collapsed}` folds a section by default.
|
|
360
359
|
- `data.yaml`: typed inputs: `actors`, `anchors` (file, from, to, graph),
|
|
361
|
-
`stores`, `interfaces` (
|
|
362
|
-
|
|
363
|
-
the change lets input cross a trust boundary
|
|
360
|
+
`stores`, `interfaces` (one entry per interface the change moved, each
|
|
361
|
+
anchored on the lines that moved it), `security`, where a review says where
|
|
362
|
+
the change lets input cross a trust boundary, and `searches`, where an
|
|
363
|
+
explainer records what it searched for its Coverage tab.
|
|
364
364
|
- `map.yaml`: the software map at head, optionally at base. In an explainer it
|
|
365
365
|
carries the breadth the prose has no room for, and a node's `files` globs are
|
|
366
366
|
what let a file count as placed rather than not examined.
|
|
@@ -377,14 +377,14 @@ defined in one place — the `thurview-fix` skill's finding rules — and
|
|
|
377
377
|
nothing else restates it.
|
|
378
378
|
|
|
379
379
|
An explainer writes the same files, minus `interfaces` and `security`: there is
|
|
380
|
-
no change to
|
|
380
|
+
no change to take a delta from or to carry input across a boundary, and
|
|
381
381
|
`graph: base` on an anchor is an error because there is one commit.
|
|
382
382
|
|
|
383
383
|
A design writes the same files, and `interfaces` means something else in it:
|
|
384
384
|
each entry is a **proposal** — what the design would add, change or remove,
|
|
385
385
|
with the anchor of the code that proposal lands in, replaces or plugs into
|
|
386
386
|
today. `graph: base` and `security` are errors for the same reason as in an
|
|
387
|
-
explainer,
|
|
387
|
+
explainer, `searches` is an error because a design has no Coverage tab, and a
|
|
388
388
|
design that proposes nothing is refused: that document is an explainer. In its
|
|
389
389
|
`map.yaml`, `base` is the structure as it stands and `nodes` the structure it
|
|
390
390
|
proposes, so a proposed part may own files that do not exist yet while a `base`
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ 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";
|
|
@@ -205,9 +205,6 @@ async function ensureServer() {
|
|
|
205
205
|
function reviewUrl(base, id, view) {
|
|
206
206
|
return `${base}/review/${id}${view ? `#/${view}` : ""}`;
|
|
207
207
|
}
|
|
208
|
-
function pinnedOf(review) {
|
|
209
|
-
return { worktree: review.worktree, pins: review.pins, dir: reviewDir(review.id) };
|
|
210
|
-
}
|
|
211
208
|
/**
|
|
212
209
|
* Base and head as `--base` and `--head` name them. Head defaults to HEAD and
|
|
213
210
|
* base to where head forked from trunk, so a branch is diffed against what it
|
|
@@ -227,28 +224,6 @@ async function pinRange(worktree, base, head, usage) {
|
|
|
227
224
|
]);
|
|
228
225
|
}
|
|
229
226
|
}
|
|
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
227
|
async function guidanceFiles(repoRoot) {
|
|
253
228
|
return [join(home(), "THURVIEW.md"), join(repoRoot, "THURVIEW.md")].filter((p) => existsSync(p));
|
|
254
229
|
}
|
|
@@ -272,8 +247,13 @@ const TEMPLATE_DATA = `# Typed inputs for review.md: actors, anchors and stores.
|
|
|
272
247
|
# title: PTY spawn site
|
|
273
248
|
# peek: { file: src/pty.ts, from: 214, to: 223 } # add graph: base for the old side
|
|
274
249
|
#
|
|
275
|
-
# interfaces holds one
|
|
276
|
-
#
|
|
250
|
+
# interfaces holds one entry per interface the change added, changed or removed:
|
|
251
|
+
# a CLI flag, an exported function, an HTTP route, a config key. Name it, say what
|
|
252
|
+
# it lets a consumer do, and anchor it on the lines that moved it (a base-side
|
|
253
|
+
# anchor for a removed one). Find what depends on it with git grep at the pins.
|
|
254
|
+
#
|
|
255
|
+
# interfaces:
|
|
256
|
+
# shellFlag: { name: --shell, change: added, capability: Runs in the named shell., anchor: spawn }
|
|
277
257
|
#
|
|
278
258
|
# security says where this change lets input cross a trust boundary. Leave it
|
|
279
259
|
# out (or write \`security: pending\`) until you have looked and the document says
|
|
@@ -324,14 +304,23 @@ const TEMPLATE_EXPLAIN_DATA = `# Typed inputs for the explainer: actors, anchors
|
|
|
324
304
|
# dispatch:
|
|
325
305
|
# title: where a request picks its handler
|
|
326
306
|
# peek: { file: src/server/router.ts, from: 41, to: 58 }
|
|
307
|
+
#
|
|
308
|
+
# searches records the searches you ran to find callers, tests and importers.
|
|
309
|
+
# publish re-runs each one with \`git grep -E\` at the pinned commit, and the
|
|
310
|
+
# Coverage tab counts the files it matches as searched. A search that matched
|
|
311
|
+
# nothing is stated too.
|
|
312
|
+
#
|
|
313
|
+
# searches:
|
|
314
|
+
# dispatchCallers: { pattern: '\\bdispatch\\(', paths: ["src/"], why: who routes a request }
|
|
327
315
|
actors: {}
|
|
328
316
|
anchors: {}
|
|
329
317
|
stores: {}
|
|
318
|
+
searches: {}
|
|
330
319
|
`;
|
|
331
320
|
const TEMPLATE_EXPLAIN_MAP = `# The structure of the code at the pinned commit: systems, containers,
|
|
332
321
|
# components, code. The map carries breadth so the prose can carry depth, and a
|
|
333
322
|
# node's \`files\` globs are what tell the Coverage tab a file was at least placed.
|
|
334
|
-
# Seed it from \`
|
|
323
|
+
# Seed it from the directories: \`git ls-tree -d -r --name-only <commit>\`.
|
|
335
324
|
nodes: []
|
|
336
325
|
edges: []
|
|
337
326
|
`;
|
|
@@ -375,7 +364,7 @@ const TEMPLATE_DESIGN_MAP = `# The structure the design proposes, with the struc
|
|
|
375
364
|
# \`base\`, so the Map tab shows what it adds, changes and removes. A node under
|
|
376
365
|
# \`nodes\` may own files that do not exist yet - that is a proposed part. A node
|
|
377
366
|
# under \`base\` may not: it is a claim about today, and publish warns.
|
|
378
|
-
# Seed base from \`
|
|
367
|
+
# Seed base from the directories: \`git ls-tree -d -r --name-only <commit>\`.
|
|
379
368
|
nodes: []
|
|
380
369
|
edges: []
|
|
381
370
|
`;
|
|
@@ -616,28 +605,6 @@ const SPECS = {
|
|
|
616
605
|
"thurview threads resolve <threadId>",
|
|
617
606
|
],
|
|
618
607
|
},
|
|
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
608
|
forge: {
|
|
642
609
|
description: "Read a pull or merge request through its forge, and post the review back to it",
|
|
643
610
|
args: 'status|prior|pass|submit --file <path>|reply <threadId> --body "<text>"',
|
|
@@ -888,7 +855,7 @@ const commands = {
|
|
|
888
855
|
guidance: await guidanceFiles(worktree),
|
|
889
856
|
help: [
|
|
890
857
|
`Edit ${join(dir, "review.md")} and data.yaml, then run \`thurview publish --review ${short(review.id)}\``,
|
|
891
|
-
`Run \`
|
|
858
|
+
`Run \`git grep -n -w <symbol> ${head.slice(0, 12)} --\` in ${worktree} for who reaches a symbol the change moved`,
|
|
892
859
|
stat.additions + stat.deletions < 300
|
|
893
860
|
? `Small change: run \`thurview publish --review ${short(review.id)} --view files --open\` now, then write the document`
|
|
894
861
|
: `Run \`git diff ${base.slice(0, 12)} ${head.slice(0, 12)}\` in ${worktree} to study the change`,
|
|
@@ -923,7 +890,7 @@ const commands = {
|
|
|
923
890
|
scale: { filesInScope: pinned.inScope.length },
|
|
924
891
|
guidance: await guidanceFiles(worktree),
|
|
925
892
|
help: [
|
|
926
|
-
`Run \`
|
|
893
|
+
`Run \`git ls-tree -d -r --name-only ${commit.slice(0, 12)}\` in ${worktree} for the directories in scope`,
|
|
927
894
|
`Author ${join(dir, "map.yaml")} first: it carries the breadth the prose cannot`,
|
|
928
895
|
`Edit ${join(dir, "review.md")} and data.yaml, then run \`thurview publish --review ${short(review.id)}\``,
|
|
929
896
|
],
|
|
@@ -957,7 +924,7 @@ const commands = {
|
|
|
957
924
|
scale: { filesInScope: pinned.inScope.length },
|
|
958
925
|
guidance: await guidanceFiles(worktree),
|
|
959
926
|
help: [
|
|
960
|
-
`Run \`
|
|
927
|
+
`Run \`git grep -n -w <symbol> ${commit.slice(0, 12)} --\` in ${worktree} for who depends on what the design changes`,
|
|
961
928
|
`Declare in ${join(dir, "data.yaml")} what the design would add, change or remove, each anchored to the code it lands in today`,
|
|
962
929
|
`Edit ${join(dir, "review.md")}, then run \`thurview publish --review ${short(review.id)}\``,
|
|
963
930
|
],
|
|
@@ -1035,21 +1002,6 @@ const commands = {
|
|
|
1035
1002
|
}
|
|
1036
1003
|
const themeName = theme ? await registerTheme(theme.shiki) : undefined;
|
|
1037
1004
|
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
1005
|
const doc = await compileDocument({
|
|
1054
1006
|
cwd: review.worktree,
|
|
1055
1007
|
pins: review.pins,
|
|
@@ -1057,7 +1009,6 @@ const commands = {
|
|
|
1057
1009
|
dataYaml: dataYaml ?? "",
|
|
1058
1010
|
kind,
|
|
1059
1011
|
...(themeName ? { themeName } : {}),
|
|
1060
|
-
interfaces,
|
|
1061
1012
|
});
|
|
1062
1013
|
diags.push(...doc.diagnostics);
|
|
1063
1014
|
let map = null;
|
|
@@ -1095,13 +1046,11 @@ const commands = {
|
|
|
1095
1046
|
let coverage = null;
|
|
1096
1047
|
if (kind === "explainer") {
|
|
1097
1048
|
try {
|
|
1098
|
-
const graph = await import("./graph.js");
|
|
1099
|
-
const g0 = await graph.graphAt(review.worktree, review.pins.head, dir);
|
|
1100
1049
|
coverage = computeCoverage({
|
|
1101
1050
|
commit: review.pins.head,
|
|
1102
1051
|
scope: review.binding.name,
|
|
1103
1052
|
allFiles: await g.listFiles(review.worktree, review.pins.head),
|
|
1104
|
-
|
|
1053
|
+
searches: doc.searches,
|
|
1105
1054
|
anchored: Object.values(doc.document.anchors)
|
|
1106
1055
|
.map((a) => a.peek?.file)
|
|
1107
1056
|
.filter((f) => !!f),
|
|
@@ -1358,154 +1307,6 @@ const commands = {
|
|
|
1358
1307
|
await (handedQuestion ? listening.handOff() : listening.stop());
|
|
1359
1308
|
}
|
|
1360
1309
|
},
|
|
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
1310
|
async threads(args) {
|
|
1510
1311
|
const sub = args[0];
|
|
1511
1312
|
const rest = args.slice(1);
|