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 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
- - **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.
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 annotation for a symbol the change never
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: 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.
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, plus the CLI flags, routes, config keys and formats the agent
232
- declares. Derived from the code graph at both pinned commits, so a change
233
- that moved no surface says exactly that instead of inventing a feature.
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 three states - anchored in the document, placed on the map only, or not
244
- examined - with the parts of the system they belong to, the references that
245
- cross between those parts, and the names defined in more than one of them.
246
- Derived at publish, so what the explainer skipped is a stated fact rather
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 | 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 graph interfaces\|impact\|callers\|tests-for\|architecture` | Ask the code graph at a review's pins, or at `--base`/`--head` |
282
- | `thurview forge status\|prior\|pass\|submit\|reply` | Read a change request through its forge, and post the review back |
283
- | `thurview serve` / `thurview stop` | Run the server in the foreground / stop the background one |
284
- | `thurview setup hooks\|skill\|status` | Session hooks, agent skill, install state |
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 asks the code graph who calls it and
302
- which tests reach it, so a finding can name a caller the diff never shows.
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
- thurview graph impact --head HEAD # changed symbols, the callers they left alone, the tests
308
- thurview graph callers discount --head HEAD # every call site of one symbol
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
- `--base` and `--head` ask about two commits directly; `--head` alone diffs
312
- from where it forked from trunk. Each caller in `impact.reach` carries the line
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` (a capability line per derived entry, plus the
362
- interfaces the graph cannot see), and `security`, where a review says where
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 derive a delta from or to carry input across a boundary, and
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, a `symbol:` entry is an error because no diff derived one, and a
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, scopeGraph, scopeTruncated, } from "./coverage.js";
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 capability line per interface the change moved. thurview
276
- # derives the list itself; run \`thurview graph interfaces\` for the ids.
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 \`thurview graph architecture\`.
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 \`thurview graph architecture\`.
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 \`thurview graph impact --review ${short(review.id)}\` to see what the change reaches`,
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 \`thurview graph architecture --review ${short(review.id)}\` for the clusters, their hubs and the links between them`,
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 \`thurview graph architecture --review ${short(review.id)}\` for the structure the design has to fit`,
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
- graph: g0,
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);