@docsworth/cli 0.2.0 → 0.3.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.
Files changed (49) hide show
  1. package/README.md +60 -8
  2. package/dist/cli.js +52 -3
  3. package/dist/core/acl.d.ts +9 -0
  4. package/dist/core/acl.js +40 -6
  5. package/dist/core/adopt.d.ts +68 -0
  6. package/dist/core/adopt.js +665 -0
  7. package/dist/core/config.d.ts +6 -1
  8. package/dist/core/config.js +46 -2
  9. package/dist/core/edits.d.ts +13 -0
  10. package/dist/core/edits.js +20 -0
  11. package/dist/core/frontmatter.d.ts +12 -0
  12. package/dist/core/frontmatter.js +16 -7
  13. package/dist/core/gfm.d.ts +13 -0
  14. package/dist/core/gfm.js +244 -0
  15. package/dist/core/links.d.ts +82 -0
  16. package/dist/core/links.js +345 -0
  17. package/dist/core/lint.d.ts +1 -2
  18. package/dist/core/lint.js +40 -6
  19. package/dist/core/mcp/server.js +11 -2
  20. package/dist/core/printable.d.ts +4 -0
  21. package/dist/core/printable.js +16 -0
  22. package/dist/core/references.d.ts +38 -3
  23. package/dist/core/references.js +543 -41
  24. package/dist/core/repo.d.ts +4 -0
  25. package/dist/core/repo.js +3 -1
  26. package/dist/core/schema.d.ts +4 -0
  27. package/dist/core/schema.js +8 -6
  28. package/dist/core/search.d.ts +8 -0
  29. package/dist/core/search.js +34 -0
  30. package/dist/core/serve/server.d.ts +1 -2
  31. package/dist/core/serve/sessions.d.ts +5 -4
  32. package/dist/core/serve/sessions.js +102 -29
  33. package/dist/core/service.d.ts +118 -20
  34. package/dist/core/service.js +249 -91
  35. package/dist/core/stale.js +2 -1
  36. package/dist/core/tool-registry.js +6 -3
  37. package/dist/core/verify-pages.d.ts +19 -0
  38. package/dist/core/verify-pages.js +110 -0
  39. package/dist/core/version.d.ts +2 -2
  40. package/dist/core/version.js +2 -2
  41. package/dist/web/assets/index-8J-FUbb0.js +29 -0
  42. package/dist/web/assets/index-BGpAZzeM.css +1 -0
  43. package/dist/web/index.html +2 -2
  44. package/docs/format-spec.md +40 -2
  45. package/package.json +2 -2
  46. package/plugin/.claude-plugin/plugin.json +1 -1
  47. package/plugin/bin/docsworth-mcp.mjs +1 -1
  48. package/dist/web/assets/index-BglLMIyN.css +0 -1
  49. package/dist/web/assets/index-DugFPoRu.js +0 -25
@@ -0,0 +1 @@
1
+ :root,:host{--dw-bg:#fbfbfa;--dw-surface:#fff;--dw-fg:#1d1d1b;--dw-muted:#6b6b66;--dw-line:#e4e3de;--dw-track:#ecebe6;--dw-accent:#2b5fa8;--dw-good:#2f7d4f;--dw-warn:#b7791f;--dw-bad:#b4442c;--dw-code:#f2f1ed;--dw-radius:8px;--dw-font:15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;--dw-mono:13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root,:host{--dw-bg:#161615;--dw-surface:#1d1d1c;--dw-fg:#ecebe6;--dw-muted:#9a9993;--dw-line:#2c2c2a;--dw-track:#2a2a28;--dw-accent:#7aa7e8;--dw-good:#58b07c;--dw-warn:#d9a441;--dw-bad:#e0735a;--dw-code:#242422;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}*{box-sizing:border-box}.dw{background:var(--dw-bg);color:var(--dw-fg);font:var(--dw-font)}.dw a{color:var(--dw-accent);text-decoration:none}.dw a:hover{text-decoration:underline}.dw code{font:var(--dw-mono);background:var(--dw-code);border-radius:4px;padding:0 4px}.dw h1{margin:0 0 4px;font-size:24px;line-height:1.25}.dw h2{margin:28px 0 10px;font-size:17px}.dw-muted{color:var(--dw-muted)}.dw-num{font-variant-numeric:tabular-nums}.dw-header{border-bottom:1px solid var(--dw-line);background:var(--dw-surface);flex-wrap:wrap;align-items:center;gap:20px;padding:12px 20px;display:flex}.dw-brand{letter-spacing:-.01em;font-weight:650;color:var(--dw-fg)!important}.dw-nav{flex-wrap:wrap;gap:4px;display:flex}.dw-nav a{color:var(--dw-muted);border-radius:6px;padding:6px 10px}.dw-nav a[aria-current=page]{color:var(--dw-fg);background:var(--dw-track)}.dw-who{color:var(--dw-muted);align-items:center;gap:8px;margin-left:auto;font-size:13px;display:flex}.dw-main{max-width:1040px;margin:0 auto;padding:28px 20px 64px}.dw-sub{color:var(--dw-muted);margin:0 0 20px}.dw-save{flex-wrap:wrap;gap:8px;margin:0 0 20px;display:flex}.dw-save .dw-note{flex-basis:100%;margin:4px 0 0}.dw-tiles{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;display:grid}.dw-tile{border:1px solid var(--dw-line);border-radius:var(--dw-radius);background:var(--dw-surface);padding:14px 16px}.dw-tile b{font-variant-numeric:tabular-nums;font-size:28px;display:block}.dw-tile span{color:var(--dw-muted);font-size:13px}.dw-table{border-collapse:collapse;width:100%}.dw-table th,.dw-table td{text-align:left;border-bottom:1px solid var(--dw-line);vertical-align:middle;padding:8px 6px}.dw-table thead th{text-transform:uppercase;letter-spacing:.04em;color:var(--dw-muted);font-size:12px;font-weight:600}.dw-scroll{overflow-x:auto}.dw-bar{background:var(--dw-track);border-radius:4px;max-width:240px;height:8px;margin-bottom:4px;overflow:hidden}.dw-bar>span{height:100%;display:block}.dw-good{background:var(--dw-good)}.dw-warn{background:var(--dw-warn)}.dw-bad{background:var(--dw-bad)}.dw-badge{border:1px solid var(--dw-line);color:var(--dw-muted);white-space:nowrap;border-radius:999px;padding:0 7px;font-size:12px;line-height:18px;display:inline-block}.dw-badge.ok{color:var(--dw-good);border-color:currentColor}.dw-badge.warn{color:var(--dw-warn);border-color:currentColor}.dw-badge.bad{color:var(--dw-bad);border-color:currentColor}.dw-meta{color:var(--dw-muted);flex-wrap:wrap;align-items:center;gap:8px;font-size:13px;display:flex}.dw-search{gap:8px;margin-bottom:20px;display:flex}.dw-search input{flex:1}.dw-results{gap:10px;margin:0;padding:0;list-style:none;display:grid}.dw-result{border:1px solid var(--dw-line);border-radius:var(--dw-radius);background:var(--dw-surface);padding:12px 14px}.dw-result h3{margin:0 0 4px;font-size:16px}.dw-result p{color:var(--dw-muted);margin:6px 0 0;font-size:14px}.dw-excluded{border-left:3px solid var(--dw-warn);background:var(--dw-surface);margin:6px 0;padding:8px 12px}.dw-page{max-width:760px}.dw-prose{line-height:1.65}.dw-prose pre{font:var(--dw-mono);background:var(--dw-code);border-radius:var(--dw-radius);padding:12px 14px;overflow-x:auto}.dw-prose pre code{background:0 0;padding:0}.dw-prose .dw-table-scroll{margin:12px 0;overflow-x:auto}.dw-prose table{border-collapse:collapse;font-size:14px;line-height:1.45}.dw-prose th,.dw-prose td{border:1px solid var(--dw-line);text-align:left;vertical-align:top;padding:6px 10px}.dw-prose th{background:var(--dw-surface);font-weight:600}.dw-prose .dw-align-center{text-align:center}.dw-prose .dw-align-right{text-align:right}.dw-prose h1{margin-top:24px;font-size:22px}.dw-prose h2{margin-top:24px}.dw-note{border:1px solid var(--dw-line);border-left:3px solid var(--dw-warn);background:var(--dw-surface);border-radius:6px;margin:12px 0;padding:10px 12px;font-size:14px}.dw-error{border-left-color:var(--dw-bad)}.dw input,.dw select,.dw textarea,.dw button{font:inherit;color:inherit}.dw input,.dw select,.dw textarea{background:var(--dw-surface);border:1px solid var(--dw-line);border-radius:6px;width:100%;padding:8px 10px}.dw textarea{font:var(--dw-mono);resize:vertical;min-height:220px}.dw button{background:var(--dw-fg);color:var(--dw-bg);cursor:pointer;border:0;border-radius:6px;padding:8px 14px;font-weight:550}.dw button.secondary{color:var(--dw-fg);border:1px solid var(--dw-line);background:0 0}.dw button:disabled{opacity:.45;cursor:not-allowed}.dw-form{gap:14px;max-width:760px;display:grid}.dw-form label{gap:6px;font-size:14px;font-weight:550;display:grid}.dw-form .dw-row{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;display:grid}.dw-preview{font:var(--dw-mono);white-space:pre-wrap;background:var(--dw-code);border-radius:var(--dw-radius);max-height:260px;padding:12px 14px;overflow:auto}.dw-graph{border:1px solid var(--dw-line);border-radius:var(--dw-radius);background:var(--dw-surface);width:100%;height:auto;display:block}.dw-graph line{stroke:var(--dw-line);stroke-width:1.2px}.dw-graph circle{stroke:var(--dw-surface);stroke-width:1.5px;cursor:pointer}.dw-graph circle.dim{opacity:.35}.dw-graph text{fill:var(--dw-muted);pointer-events:none;font-size:11px}.dw-legend{color:var(--dw-muted);flex-wrap:wrap;gap:14px;margin:10px 0;font-size:13px;display:flex}.dw-teams{flex-wrap:wrap;gap:12px;margin:-8px 0 16px;font-size:14px;display:flex}.dw-swatch{vertical-align:-1px;border-radius:50%;width:10px;height:10px;margin-right:6px;display:inline-block}
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Docsworth</title>
7
7
  <link rel="icon" href="data:," />
8
- <script type="module" crossorigin src="/assets/index-DugFPoRu.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-BglLMIyN.css">
8
+ <script type="module" crossorigin src="/assets/index-8J-FUbb0.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-BGpAZzeM.css">
10
10
  </head>
11
11
  <body class="dw">
12
12
  <div id="app"></div>
@@ -22,6 +22,10 @@ CODEOWNERS # or .github/CODEOWNERS or docs/CODEOWNERS
22
22
  ```
23
23
 
24
24
  Every `.md` file under the knowledge root is a page, and every page has frontmatter.
25
+ Inside the knowledge root, a directory whose name starts with a dot (an editor's
26
+ `.obsidian/`, say) is skipped, with everything in it: its files are neither pages nor lint
27
+ errors. The knowledge root itself is read whatever its name, and a file whose name starts
28
+ with a dot is a page like any other.
25
29
 
26
30
  ## Frontmatter
27
31
 
@@ -45,7 +49,30 @@ description: One sentence. # optional; used in search and compiled skills
45
49
  ---
46
50
  ```
47
51
 
48
- Unknown fields produce a lint warning, never an error.
52
+ Unknown fields produce a lint warning, never an error, unless the config lists them in
53
+ `frontmatter.extra_fields`: keys a site generator keeps there, such as Docusaurus's
54
+ `sidebar_position`.
55
+
56
+ ## Links
57
+
58
+ Lint reads a page's links as CommonMark reads them. A `[x](y)` in code (a fenced or
59
+ indented block, or a code span) or in HTML is no link. A reference link counts through its
60
+ definition, and a definition no link uses is invisible.
61
+
62
+ A relative link must resolve inside the repo. Under the knowledge root it must name a page
63
+ (`.md`) or an attachment (a file with an extension, such as `diagram.png`). A link to a
64
+ directory, or to a file with no extension, is an error: it names no page, and a route like
65
+ Docusaurus's `/guides/setup` reads as one. Link the page itself (`guides/setup.md`).
66
+
67
+ A root-relative link to an attachment (`/img/x.png`) that names no file of the repo's
68
+ resolves in the directories `static_dirs` lists, as a docs site serves them (Docusaurus's
69
+ `static/`). A page never does: pages are under the knowledge root.
70
+
71
+ A root-relative link (`/docs`) is an error too when it names a directory or a file with no
72
+ extension outside the knowledge root, and, read from the knowledge root as a docs site
73
+ serves it (with or without a leading `docs/`, a number prefix, or a page's own `slug` or `id`),
74
+ also names a page. The repo's file and the page both answer to it. Link the one you mean,
75
+ with its extension.
49
76
 
50
77
  ## Ownership
51
78
 
@@ -104,6 +131,13 @@ In a page the caller can see, a reference to a page they can't see (a link in an
104
131
  a URL, or its path in text) reads `[restricted page]`. A page that doesn't load counts as
105
132
  one they can't see, and so does a link to a path under the knowledge root that is no page
106
133
  at all: relative, or, with `github.repo` set, a GitHub URL into the repo on any branch.
134
+
135
+ Links are also read as a docs site serves them. A link to a hidden page's route reads
136
+ `[restricted page]` too: by its `slug`, by an `id` other than its file name, by its path
137
+ without number prefixes, or by its file's stem. So does a link without an extension that
138
+ names no page, file or directory in the repo (`/guides/gone`), for every reader. With
139
+ `site_url` set, a URL on the docs site counts as the route it names. A route written in
140
+ plain text, not as a link, is served as written.
107
141
  See [security-model.md](security-model.md#the-one-read-path).
108
142
 
109
143
  **`acl` isn't confidentiality.** GitHub has no per-file read permissions, so anyone with
@@ -266,7 +300,9 @@ or `applies_to` leave mirrored pages out; `owner_team` applies to them.
266
300
 
267
301
  ```yaml
268
302
  format: 1
269
- knowledge_root: knowledge
303
+ knowledge_root: knowledge # a directory in the repo, named relative to it: not . or .., not absolute, not in .git or .knowledge; a trailing slash is ignored
304
+ static_dirs: [website/static] # optional: directories a docs site serves at its root; lint reads /img/x.png there too
305
+ site_url: https://docs.example.com/docs/ # optional: where a docs site serves the pages; redaction reads a URL under it as a route
270
306
  github:
271
307
  org: acme # required: team owners resolve to @acme/<team>
272
308
  repo: acme/knowledge # optional: makes GitHub the ACL authority; enables PR tools
@@ -275,6 +311,8 @@ github:
275
311
  sla:
276
312
  due_soon_days: 14
277
313
  usage_log: true # page-id-only read log for the owner digest
314
+ frontmatter:
315
+ extra_fields: [sidebar_label, sidebar_position] # keys a site generator keeps in frontmatter; lint doesn't warn on them
278
316
  audit_log: # attributed audit log; off unless enabled
279
317
  enabled: true
280
318
  dir: /var/lib/docsworth/audit # optional; default $XDG_STATE_HOME/docsworth/<repo>-<hash>/audit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsworth/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Git-native knowledge layer for coding agents: team-owned pages, review SLAs, a citation gate, and an MCP server agents read for free.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -61,6 +61,7 @@
61
61
  "dependencies": {
62
62
  "@modelcontextprotocol/sdk": "^1.32.2",
63
63
  "commander": "^15.0.0",
64
+ "commonmark": "^0.31.2",
64
65
  "yaml": "^2.9.1",
65
66
  "zod": "^4.6.7"
66
67
  },
@@ -68,7 +69,6 @@
68
69
  "@mixmark-io/domino": "2.2.0",
69
70
  "@types/commonmark": "0.27.10",
70
71
  "@types/node": "^22.20.5",
71
- "commonmark": "0.31.2",
72
72
  "entities": "3.0.1",
73
73
  "tsx": "^4.23.15",
74
74
  "typescript": "^7.0.2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docsworth",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Team-owned, verified engineering knowledge for Claude Code: an MCP server that cites only canonical pages inside their review SLA, plus skills to draft, update and verify runbooks through PRs.",
5
5
  "author": { "name": "docsworth" },
6
6
  "license": "Apache-2.0",
@@ -11,7 +11,7 @@ import { resolve } from "node:path";
11
11
  // The npm package to run. `agent-kb` on npm belongs to another publisher and nobody owns the
12
12
  // bare `docsworth`: never fall back to either. Kept equal to PUBLISHED_PACKAGE in
13
13
  // src/core/version.ts by test/package-name.test.ts.
14
- const PUBLISHED_PACKAGE = "@docsworth/cli@^0.2";
14
+ const PUBLISHED_PACKAGE = "@docsworth/cli@^0.3";
15
15
 
16
16
  // The repo reaches the server in the environment, never on the command line, which Windows
17
17
  // runs through cmd.exe: a path would split at a space there, and what follows an `&` would
@@ -1 +0,0 @@
1
- :root,:host{--dw-bg:#fbfbfa;--dw-surface:#fff;--dw-fg:#1d1d1b;--dw-muted:#6b6b66;--dw-line:#e4e3de;--dw-track:#ecebe6;--dw-accent:#2b5fa8;--dw-good:#2f7d4f;--dw-warn:#b7791f;--dw-bad:#b4442c;--dw-code:#f2f1ed;--dw-radius:8px;--dw-font:15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;--dw-mono:13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root,:host{--dw-bg:#161615;--dw-surface:#1d1d1c;--dw-fg:#ecebe6;--dw-muted:#9a9993;--dw-line:#2c2c2a;--dw-track:#2a2a28;--dw-accent:#7aa7e8;--dw-good:#58b07c;--dw-warn:#d9a441;--dw-bad:#e0735a;--dw-code:#242422;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}*{box-sizing:border-box}.dw{background:var(--dw-bg);color:var(--dw-fg);font:var(--dw-font)}.dw a{color:var(--dw-accent);text-decoration:none}.dw a:hover{text-decoration:underline}.dw code{font:var(--dw-mono);background:var(--dw-code);border-radius:4px;padding:0 4px}.dw h1{margin:0 0 4px;font-size:24px;line-height:1.25}.dw h2{margin:28px 0 10px;font-size:17px}.dw-muted{color:var(--dw-muted)}.dw-num{font-variant-numeric:tabular-nums}.dw-header{border-bottom:1px solid var(--dw-line);background:var(--dw-surface);flex-wrap:wrap;align-items:center;gap:20px;padding:12px 20px;display:flex}.dw-brand{letter-spacing:-.01em;font-weight:650;color:var(--dw-fg)!important}.dw-nav{flex-wrap:wrap;gap:4px;display:flex}.dw-nav a{color:var(--dw-muted);border-radius:6px;padding:6px 10px}.dw-nav a[aria-current=page]{color:var(--dw-fg);background:var(--dw-track)}.dw-who{color:var(--dw-muted);align-items:center;gap:8px;margin-left:auto;font-size:13px;display:flex}.dw-main{max-width:1040px;margin:0 auto;padding:28px 20px 64px}.dw-sub{color:var(--dw-muted);margin:0 0 20px}.dw-tiles{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;display:grid}.dw-tile{border:1px solid var(--dw-line);border-radius:var(--dw-radius);background:var(--dw-surface);padding:14px 16px}.dw-tile b{font-variant-numeric:tabular-nums;font-size:28px;display:block}.dw-tile span{color:var(--dw-muted);font-size:13px}.dw-table{border-collapse:collapse;width:100%}.dw-table th,.dw-table td{text-align:left;border-bottom:1px solid var(--dw-line);vertical-align:middle;padding:8px 6px}.dw-table thead th{text-transform:uppercase;letter-spacing:.04em;color:var(--dw-muted);font-size:12px;font-weight:600}.dw-scroll{overflow-x:auto}.dw-bar{background:var(--dw-track);border-radius:4px;max-width:240px;height:8px;margin-bottom:4px;overflow:hidden}.dw-bar>span{height:100%;display:block}.dw-good{background:var(--dw-good)}.dw-warn{background:var(--dw-warn)}.dw-bad{background:var(--dw-bad)}.dw-badge{border:1px solid var(--dw-line);color:var(--dw-muted);white-space:nowrap;border-radius:999px;padding:0 7px;font-size:12px;line-height:18px;display:inline-block}.dw-badge.ok{color:var(--dw-good);border-color:currentColor}.dw-badge.warn{color:var(--dw-warn);border-color:currentColor}.dw-badge.bad{color:var(--dw-bad);border-color:currentColor}.dw-meta{color:var(--dw-muted);flex-wrap:wrap;align-items:center;gap:8px;font-size:13px;display:flex}.dw-search{gap:8px;margin-bottom:20px;display:flex}.dw-search input{flex:1}.dw-results{gap:10px;margin:0;padding:0;list-style:none;display:grid}.dw-result{border:1px solid var(--dw-line);border-radius:var(--dw-radius);background:var(--dw-surface);padding:12px 14px}.dw-result h3{margin:0 0 4px;font-size:16px}.dw-result p{color:var(--dw-muted);margin:6px 0 0;font-size:14px}.dw-excluded{border-left:3px solid var(--dw-warn);background:var(--dw-surface);margin:6px 0;padding:8px 12px}.dw-page{max-width:760px}.dw-prose{line-height:1.65}.dw-prose pre{font:var(--dw-mono);background:var(--dw-code);border-radius:var(--dw-radius);padding:12px 14px;overflow-x:auto}.dw-prose pre code{background:0 0;padding:0}.dw-prose h1{margin-top:24px;font-size:22px}.dw-prose h2{margin-top:24px}.dw-note{border:1px solid var(--dw-line);border-left:3px solid var(--dw-warn);background:var(--dw-surface);border-radius:6px;margin:12px 0;padding:10px 12px;font-size:14px}.dw-error{border-left-color:var(--dw-bad)}.dw input,.dw select,.dw textarea,.dw button{font:inherit;color:inherit}.dw input,.dw select,.dw textarea{background:var(--dw-surface);border:1px solid var(--dw-line);border-radius:6px;width:100%;padding:8px 10px}.dw textarea{font:var(--dw-mono);resize:vertical;min-height:220px}.dw button{background:var(--dw-fg);color:var(--dw-bg);cursor:pointer;border:0;border-radius:6px;padding:8px 14px;font-weight:550}.dw button.secondary{color:var(--dw-fg);border:1px solid var(--dw-line);background:0 0}.dw button:disabled{opacity:.45;cursor:not-allowed}.dw-form{gap:14px;max-width:760px;display:grid}.dw-form label{gap:6px;font-size:14px;font-weight:550;display:grid}.dw-form .dw-row{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;display:grid}.dw-preview{font:var(--dw-mono);white-space:pre-wrap;background:var(--dw-code);border-radius:var(--dw-radius);max-height:260px;padding:12px 14px;overflow:auto}.dw-graph{border:1px solid var(--dw-line);border-radius:var(--dw-radius);background:var(--dw-surface);width:100%;height:560px;display:block}.dw-graph line{stroke:var(--dw-line);stroke-width:1.2px}.dw-graph circle{stroke:var(--dw-surface);stroke-width:1.5px;cursor:pointer}.dw-graph circle.dim{opacity:.35}.dw-graph text{fill:var(--dw-muted);pointer-events:none;font-size:11px}.dw-legend{color:var(--dw-muted);flex-wrap:wrap;gap:14px;margin:10px 0;font-size:13px;display:flex}.dw-swatch{vertical-align:-1px;border-radius:50%;width:10px;height:10px;margin-right:6px;display:inline-block}