wiki-formant 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.
@@ -18,11 +18,20 @@
18
18
  // are utilities is derived, not listed — anything the build emits that globals.css
19
19
  // does not define is Tailwind's.
20
20
  //
21
+ // And the reverse: a selector globals.css defines that nothing the build ships
22
+ // references. The shared blocks and chrome render their markup inside
23
+ // wiki-formant while each app styles it, so references are read from the build
24
+ // output, which holds this repo's code, the package's components and bundled
25
+ // libraries alike. A class renamed in the package surfaces here as its old
26
+ // selector going unreferenced. HTML stored in a database is not in the build,
27
+ // so a selector only stored content uses is reported too.
28
+ //
21
29
  // npx check-classes # exit 1 on any dead token
22
30
  // npx check-classes --warn # report and exit 0
23
31
  // npx check-classes --compositions # also fail on 3+ inline utilities
32
+ // npx check-classes --unused # also fail on unreferenced selectors
24
33
  //
25
- // Run it from the repo root: `.next/static` and `src` are resolved against cwd.
34
+ // Run it from the repo root: `.next` and `src` are resolved against cwd.
26
35
  //
27
36
  // Needs a build first (npm run build): without one there is nothing to check
28
37
  // against, and the script says so and exits 0 rather than failing blind.
@@ -30,7 +39,7 @@ import fs from 'node:fs';
30
39
  import path from 'node:path';
31
40
 
32
41
  const WARN_ONLY = process.argv.includes('--warn');
33
- const CSS_DIR = '.next/static';
42
+ const STATIC_DIR = '.next/static';
34
43
  const SRC_DIR = 'src';
35
44
 
36
45
  const walk = (dir, ext, out = []) => {
@@ -43,7 +52,7 @@ const walk = (dir, ext, out = []) => {
43
52
  return out;
44
53
  };
45
54
 
46
- const cssFiles = walk(CSS_DIR, '.css');
55
+ const cssFiles = walk(STATIC_DIR, '.css');
47
56
  if (!cssFiles.length) {
48
57
  console.log('check-classes: no built CSS under .next/static — run `npm run build` first. Skipping.');
49
58
  process.exit(0);
@@ -59,12 +68,18 @@ for (const f of cssFiles) {
59
68
 
60
69
  // Classes this project defines itself. Everything else the build emitted is a
61
70
  // Tailwind utility, which is what makes the composition count derivable rather
62
- // than a maintained prefix list.
71
+ // than a maintained prefix list. Read from rule preludes only, so a decimal in a
72
+ // value or a class named in a comment is not taken for a selector.
63
73
  const globalsFile = walk(SRC_DIR, '.css').find(f => f.endsWith('globals.css'));
64
74
  const named = new Set();
65
75
  if (globalsFile) {
66
- for (const m of fs.readFileSync(globalsFile, 'utf8').matchAll(/\.((?:\\.|[-\w])+)/g)) {
67
- named.add(m[1].replace(/\\/g, ''));
76
+ const css = fs.readFileSync(globalsFile, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
77
+ for (const [, prelude] of css.matchAll(/([^{};]*)\{/g)) {
78
+ if (prelude.trim().startsWith('@')) continue;
79
+ for (const m of prelude.matchAll(/\.((?:\\.|[-\w])+)/g)) {
80
+ const name = m[1].replace(/\\/g, '');
81
+ if (/^[A-Za-z_-]/.test(name)) named.add(name);
82
+ }
68
83
  }
69
84
  }
70
85
 
@@ -92,6 +107,7 @@ for (const f of walk(SRC_DIR, '.tsx')) {
92
107
  }
93
108
 
94
109
  const CHECK_COMPOSITIONS = process.argv.includes('--compositions');
110
+ const CHECK_UNUSED = process.argv.includes('--unused');
95
111
 
96
112
  if (compositions.length) {
97
113
  const verb = CHECK_COMPOSITIONS ? 'must be named' : 'should be named (advisory)';
@@ -103,9 +119,30 @@ if (compositions.length) {
103
119
  console.error('');
104
120
  }
105
121
 
122
+ // Every token in the JavaScript the build ships, server and client. A selector
123
+ // is referenced when its name appears whole, or when a prefix of it ending in `-`
124
+ // or `_` does, which is how `editorial-banner-${variant}` is written.
125
+ const shipped = new Set();
126
+ for (const f of [...walk(STATIC_DIR, '.js'), ...walk('.next/server', '.js')]) {
127
+ for (const [tok] of fs.readFileSync(f, 'utf8').matchAll(/[\w-]+/g)) shipped.add(tok);
128
+ }
129
+ const referenced = name =>
130
+ shipped.has(name) ||
131
+ [...name].some((c, i) => i >= 3 && (c === '-' || c === '_') && shipped.has(name.slice(0, i + 1)));
132
+ const unused = [...named].filter(n => !referenced(n)).sort();
133
+
134
+ if (unused.length) {
135
+ const verb = CHECK_UNUSED ? 'must be deleted' : 'advisory';
136
+ console.error(`check-classes: ${unused.length} selector(s) in ${globalsFile} referenced by nothing the build ships (${verb}):\n`);
137
+ console.error(` ${unused.join(', ')}\n`);
138
+ console.error(' Class names inside HTML stored in a database do not count, so check stored content before deleting one.\n');
139
+ }
140
+
141
+ const advisoryFailed = (CHECK_COMPOSITIONS && compositions.length > 0) || (CHECK_UNUSED && unused.length > 0);
142
+
106
143
  if (!dead.size) {
107
144
  console.log(`check-classes: clean — every className token resolves against ${emitted.size} emitted selectors.`);
108
- process.exit(CHECK_COMPOSITIONS && compositions.length && !WARN_ONLY ? 1 : 0);
145
+ process.exit(advisoryFailed && !WARN_ONLY ? 1 : 0);
109
146
  }
110
147
 
111
148
  console.error(`check-classes: ${dead.size} className token(s) style nothing:\n`);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- // block-views.tsx — the block renderers both wikis had written twice.
3
+ // block-views.tsx — the block leaves the wikis share.
4
4
  //
5
5
  // Behind its own subpath for the reason `react.tsx` is: React is an OPTIONAL
6
6
  // peer, so a consumer that only wants the taxonomy or the MCP transport still
@@ -10,13 +10,12 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
10
10
  // that way (see the Block Model note in the workspace CLAUDE.md) — a closed
11
11
  // union is what makes `switch (block.type)` exhaustive, so a new block type is
12
12
  // a compile error rather than a silent blank. What is NOT per-repo is what a
13
- // codeTabs or a linkGrid LOOKS like once you have dispatched to it: those were
14
- // byte-identical in both repos, down to the class names. So the dispatch stays
15
- // with the caller and the leaves move here.
13
+ // codeTabs or a linkGrid LOOKS like once you have dispatched to it, down to the
14
+ // class names. So the dispatch stays with the caller and the leaves live here.
16
15
  //
17
- // The class names are NOT props. Like the rail's `wiki-rail__*` tree they are
18
- // the shared convention both stylesheets already implement, and making them
19
- // configurable would only let that convention fork again. Everything that
16
+ // The class names are NOT props. They are the shared convention every
17
+ // stylesheet implements, and making them configurable would let that convention
18
+ // fork. Everything that
20
19
  // genuinely differs — the prose of a banner, whether references run through an
21
20
  // HTML processor, the router's link — arrives as a prop.
22
21
  import { Fragment, useState } from 'react';
package/dist/combobox.js CHANGED
@@ -4,21 +4,12 @@
4
4
  // pure, so it can be tested without a DOM, and `react.tsx` is left holding only
5
5
  // the hook that calls it.
6
6
  //
7
- // `useTypeahead` already shared the state machine across five surfaces in three
8
- // wikis. The ARIA did not travel with it, and all five had drifted into
9
- // different wrongness:
10
- //
11
- // - one put `aria-selected` on a plain `<button>`, which is not a role that
12
- // takes it, and gave the container no `role="listbox"` at all;
13
- // - one gave the rows `role="option"` but still no listbox, so the options
14
- // had no owner;
15
- // - two had no roles whatsoever;
16
- // - one used a `data-highlighted` attribute, which no assistive technology
17
- // reads.
18
- //
19
- // None of the five set `aria-activedescendant`, which is the attribute that
20
- // actually announces the highlighted row as the reader arrows through it. A
21
- // combobox without it is a text field that silently changes what Enter does.
7
+ // `useTypeahead` shares the state machine; this is the ARIA that goes with it:
8
+ // a `role="listbox"` container that owns `role="option"` rows, `aria-selected`
9
+ // on the option rather than on a plain `<button>`, and `aria-activedescendant`,
10
+ // the attribute that announces the highlighted row as the reader arrows
11
+ // through it. A combobox without it is a text field that silently changes what
12
+ // Enter does.
22
13
  /** The id of one option row. Exported because a caller that scrolls the
23
14
  * highlighted row into view has to find it by the same id the input points at. */
24
15
  export function optionId(baseId, listKey, index) {
@@ -19,8 +19,7 @@ import { DEFAULT_MAX_BATCH, MCP_CORS, MCP_PROTOCOL_VERSION } from './mcp.js';
19
19
  /**
20
20
  * Does the live preflight carry every token `MCP_CORS` declares for this header?
21
21
  *
22
- * Derived rather than listed: the assertion used to name three headers by hand,
23
- * so a fourth added to `MCP_CORS` was tested by nobody.
22
+ * Derived rather than listed, so a header added to `MCP_CORS` is tested too.
24
23
  */
25
24
  const corsCovers = (res, header) => {
26
25
  const live = res.headers.get(header) ?? '';
@@ -306,10 +305,9 @@ export async function annotationChecks(t, opts = {}) {
306
305
  */
307
306
  export async function payloadBudget(t, calls, maxBytes = 120_000) {
308
307
  // Every read-only tool a caller can invoke with no arguments at all, whether
309
- // or not the suite thought to name it. This check used to weigh only the
310
- // listed calls, which is how a tool taking no parameters and answering with
311
- // 3.3 MB — 27× the budget it was exempt from — passed a suite that measured
312
- // the four tools beside it. A tool with required arguments still has to be
308
+ // or not the suite thought to name it, so a no-argument tool answering with
309
+ // megabytes cannot pass by going unlisted. A tool with required arguments
310
+ // still has to be
313
311
  // listed: the suite is the only thing that knows a valid pair.
314
312
  const listed = new Set(calls.map(c => c.name));
315
313
  const bare = ((await t.rpc('tools/list')).result?.tools ?? [])
package/dist/crawlers.js CHANGED
@@ -1,22 +1,16 @@
1
1
  // crawlers.ts — one roster of AI crawler tokens, for both surfaces that need it.
2
2
  //
3
- // Every wiki in the workspace kept this list twice: once in the proxy, keyed by
4
- // user-agent substring, to count an "AI Bot Visit"; and once in `robots.ts`, as
5
- // the set of agents that get their own group. The two copies were each
6
- // byte-identical across all three repos — and they were different lists.
3
+ // The proxy counts an "AI Bot Visit" by user-agent substring, and `robots.ts`
4
+ // gives each agent its own group. Both read this one roster.
7
5
  //
8
- // Only one direction of that difference was deliberate. `Applebot-Extended`
6
+ // One difference between the two uses is deliberate. `Applebot-Extended`
9
7
  // never fetches a page: it is a robots.txt-only token that Applebot consults
10
8
  // before using already-crawled data for AI, so counting it would count nothing.
11
9
  // That belongs in robots and not in the matcher, and it is declared here.
12
10
  //
13
- // The other direction was not deliberate. Bytespider, CCBot, cohere-ai,
14
- // Claude-Web and Meta-ExternalFetcher were matched by every proxy and named by
15
- // no robots.txt — and a crawler obeys only its most-specific matching group, so
16
- // an agent with no group of its own falls through to `*` and is granted
17
- // whatever that grants. The wikis were measuring five crawlers they had never
18
- // addressed. One roster makes that a property of the data rather than of which
19
- // file you happened to edit.
11
+ // Every other token appears in both, because a crawler obeys only its
12
+ // most-specific matching group: an agent with no group of its own falls through
13
+ // to `*` and is granted whatever that grants.
20
14
  /**
21
15
  * Order is significant: `detectAiBot` returns the first token the user agent
22
16
  * contains, so a token that is a substring of another must come after it.
package/dist/dom.d.ts CHANGED
@@ -19,8 +19,7 @@ export interface CopyButtonOptions {
19
19
  export declare function addCopyButtons(root: ParentNode, options?: CopyButtonOptions): number;
20
20
  /**
21
21
  * The one place this origin is written down. It is both the embed host and the
22
- * allow-list the resize listener checks, and it was previously spelled out at
23
- * four call sites across the two wikis.
22
+ * allow-list the resize listener checks.
24
23
  */
25
24
  export declare const TWITTER_ORIGIN = "https://platform.twitter.com";
26
25
  /** The embed iframe's src. `dnt=true` opts the embed out of Twitter's tracking. */
package/dist/dom.js CHANGED
@@ -55,8 +55,7 @@ export function addCopyButtons(root, options = {}) {
55
55
  // ---- twitter embeds ---------------------------------------------------------
56
56
  /**
57
57
  * The one place this origin is written down. It is both the embed host and the
58
- * allow-list the resize listener checks, and it was previously spelled out at
59
- * four call sites across the two wikis.
58
+ * allow-list the resize listener checks.
60
59
  */
61
60
  export const TWITTER_ORIGIN = 'https://platform.twitter.com';
62
61
  /** The embed iframe's src. `dnt=true` opts the embed out of Twitter's tracking. */
package/dist/editor.d.ts CHANGED
@@ -5,8 +5,7 @@ import { type ChangeEvent, type RefObject } from 'react';
5
5
  *
6
6
  * Each of these packages ships a `declare module '@tiptap/core'` block that adds
7
7
  * its commands to `ChainedCommands` — `toggleBold`, `insertTable`, `setLink`,
8
- * `setImage`. A consumer that imported the extensions itself picked those up as
9
- * a side effect of the import; now that this module owns them, it has to carry
8
+ * `setImage`. Because this module owns the extensions, it has to carry
10
9
  * the augmentation across the package boundary, and a re-exported type is what
11
10
  * makes the emitted `.d.ts` load the module that declares it. Without these
12
11
  * four lines every `editor.chain().focus().toggleBold()` in every consumer
@@ -16,19 +15,6 @@ export type { StarterKitOptions } from '@tiptap/starter-kit';
16
15
  export type { LinkOptions } from '@tiptap/extension-link';
17
16
  export type { ImageOptions } from '@tiptap/extension-image';
18
17
  export type { TableOptions } from '@tiptap/extension-table';
19
- /**
20
- * Reduce pasted HTML to structure plus the three attributes that carry meaning.
21
- *
22
- * A paste from a word processor or a web page arrives carrying its whole
23
- * stylesheet inline. Keeping any of it means the wiki's own typography loses to
24
- * whatever the author copied from, per paragraph, invisibly — and `style` on a
25
- * pasted node is also the cheapest way to smuggle a full-bleed overlay into a
26
- * page body.
27
- *
28
- * Browser-only: it parses with `DOMParser`. Called from `transformPastedHTML`,
29
- * which only ever runs in response to a paste.
30
- */
31
- export declare function cleanPastedHtml(html: string): string;
32
18
  export interface WikiEditorExtensionOptions {
33
19
  /** Empty-document prompt. */
34
20
  placeholder?: string;
@@ -42,15 +28,6 @@ export interface WikiEditorExtensionOptions {
42
28
  */
43
29
  nodes?: readonly AnyExtension[];
44
30
  }
45
- /**
46
- * The editor's extension set.
47
- *
48
- * `codeBlock: false` on StarterKit is load-bearing: the consumer registers its
49
- * own via `createCodeBlock`, and leaving StarterKit's in place would give the
50
- * schema two nodes claiming the same name. Headings stop at h2 — the page title
51
- * is the only h1 a wiki page has.
52
- */
53
- export declare function wikiEditorExtensions({ placeholder, nodes, }?: WikiEditorExtensionOptions): AnyExtension[];
54
31
  /**
55
32
  * Turn a pasted URL into the richest node that fits it, falling back to a bare
56
33
  * iframe. Order matters: a YouTube URL is also a valid iframe source, so the
package/dist/editor.js CHANGED
@@ -4,25 +4,16 @@
4
4
  // `tiptap.tsx` holds the custom NODES both wikis needed (iframe, tweet, map,
5
5
  // code block, tabs). This holds what sits around them: which extensions are
6
6
  // configured how, what a paste is scrubbed down to, how a pasted URL becomes
7
- // the right embed, and the state a toolbar reads. Those were byte-identical in
8
- // both repos — the same twelve-entry extension array, the same paste scrubber,
9
- // the same embed dispatch including the shortened-map async swap.
7
+ // the right embed, and the state a toolbar reads.
10
8
  //
11
- // TWO BUGS ARE FIXED HERE RATHER THAN PROPAGATED, and the reason this file
12
- // exists at all is that each repo had exactly one of them:
9
+ // TWO GUARDS HERE ARE LOAD-BEARING:
13
10
  //
14
- // 1. `onChangeRef.current = onChange` was written DURING render in one copy.
15
- // A ref mutated mid-render can tear under concurrent rendering. It is
16
- // written in an effect here, and only read from events and timeouts, which
17
- // run later.
18
- // 2. The other copy had no `onBlur`. The change is debounced 150ms, and
19
- // clicking Save blurs the editor before the click lands — so the final
20
- // keystroke of every edit that ended in a click was dropped. Blur flushes
21
- // the pending debounce.
22
- //
23
- // Neither repo was "behind": each had shipped the fix the other lacked, which
24
- // is the drift that costs the most, because neither file looks like the one to
25
- // fix.
11
+ // 1. `onChangeRef.current = onChange` is written in an effect, never during
12
+ // render: a ref mutated mid-render can tear under concurrent rendering.
13
+ // It is only read from events and timeouts, which run later.
14
+ // 2. Blur flushes the pending debounce. The change is debounced 150ms, and
15
+ // clicking Save blurs the editor before the click lands, so without the
16
+ // flush the final keystroke of an edit that ends in a click is dropped.
26
17
  //
27
18
  // Every @tiptap package here is an OPTIONAL PEER. A consumer that only wants
28
19
  // the taxonomy or the MCP transport installs none of them.
@@ -50,7 +41,7 @@ import { toMapEmbedUrl } from './maps.js';
50
41
  * Browser-only: it parses with `DOMParser`. Called from `transformPastedHTML`,
51
42
  * which only ever runs in response to a paste.
52
43
  */
53
- export function cleanPastedHtml(html) {
44
+ function cleanPastedHtml(html) {
54
45
  const doc = new DOMParser().parseFromString(html, 'text/html');
55
46
  doc.querySelectorAll('style, script, meta, link, svg, canvas, noscript').forEach(el => el.remove());
56
47
  doc.querySelectorAll('*').forEach(el => {
@@ -72,7 +63,7 @@ export function cleanPastedHtml(html) {
72
63
  * schema two nodes claiming the same name. Headings stop at h2 — the page title
73
64
  * is the only h1 a wiki page has.
74
65
  */
75
- export function wikiEditorExtensions({ placeholder = '', nodes = [], } = {}) {
66
+ function wikiEditorExtensions({ placeholder = '', nodes = [], } = {}) {
76
67
  return [
77
68
  StarterKit.configure({ heading: { levels: [2, 3, 4] }, codeBlock: false }),
78
69
  TiptapLink.configure({ openOnClick: false, HTMLAttributes: { class: 'link' } }),
package/dist/headings.js CHANGED
@@ -1,12 +1,6 @@
1
1
  // headings.ts — stable ids on a wiki page's headings, and the list a table of
2
2
  // contents is built from.
3
3
  //
4
- // Two of the three wikis had already written this out (caper's `injectHeadingIds`,
5
- // radix-wiki's heading branch in `processHtml`), down to byte-identical
6
- // `stripTags` and `getAttr` helpers — caper's file says "ported in spirit from
7
- // radix-wiki" at the top, which is the drift admitting itself. What they had
8
- // drifted on is below.
9
- //
10
4
  // The slug rule is a parameter, not a decision this module makes. A heading id
11
5
  // is a live URL: readers link to `#the-shape-of-a-code`, and so does the page's
12
6
  // own permalink anchor. Unifying two slug rules would silently move every
@@ -15,8 +9,7 @@
15
9
  //
16
10
  // Deduping, by contrast, is not a choice: two headings with the same text
17
11
  // otherwise mint the same id twice and every link to the second one lands on
18
- // the first. That was already a bug in the copy that lacked it, so this always
19
- // dedupes.
12
+ // the first, so this always dedupes.
20
13
  import { getAttr, stripTags } from './html.js';
21
14
  /** The default slug rule: lowercase words joined by hyphens. */
22
15
  export function slugifyHeading(text) {
@@ -1,18 +1,15 @@
1
- // link-check.ts — the dead-link probe both wikis' sweep scripts had written.
1
+ // link-check.ts — the dead-link probe wiki sweep scripts share.
2
2
  //
3
3
  // Node-only (fetch, AbortSignal, URL). No framework, no database: what a
4
4
  // checker does with the verdicts — which pages to walk, what counts as an
5
5
  // internal path, how to report — stays with the caller, because that is the
6
6
  // half that is genuinely per-wiki.
7
7
  //
8
- // EVERY GUARD BELOW WAS PAID FOR BY A FALSE POSITIVE, and the two copies had
9
- // each learned a different half of the lesson. One knew that npmjs.com 403s
10
- // scripted requests, that an expired certificate is not a dead host, and that a
11
- // YouTube /embed/ URL answers 200 for a deleted video. The other knew that a
12
- // connect refusal is usually concurrency rather than death, and that
13
- // serialising per hostname is what fixes it. Neither copy was behind. A sweep
14
- // running either one alone strips good citations for reasons the other repo had
15
- // already written down — which is the entire argument for this file.
8
+ // EVERY GUARD BELOW WAS PAID FOR BY A FALSE POSITIVE. npmjs.com 403s scripted
9
+ // requests, an expired certificate is not a dead host, a YouTube /embed/ URL
10
+ // answers 200 for a deleted video, and a connect refusal is usually concurrency
11
+ // rather than death, which serialising per hostname fixes. A sweep missing any
12
+ // one of these strips good citations.
16
13
  import { stripTags } from './html.js';
17
14
  const DEFAULTS = { timeoutMs: 12_000, slowTimeoutMs: 40_000 };
18
15
  /**
package/dist/maps.d.ts CHANGED
@@ -3,8 +3,6 @@ export interface MapCoords {
3
3
  lon: number;
4
4
  zoom?: number | undefined;
5
5
  }
6
- /** A plain embed URL for a coordinate pair. */
7
- export declare function mapsEmbedUrl(lat: number, lon: number, zoom?: number): string;
8
6
  /**
9
7
  * Dig a coordinate pair out of a maps URL. Four shapes in descending
10
8
  * specificity: the `@lat,lon,zoom` path segment, a `/search/lat,lon`, the
package/dist/maps.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // pure string work over Google and Apple Maps URL shapes, with no framework or
5
5
  // database in it, which is why neither copy had a reason to diverge.
6
6
  /** A plain embed URL for a coordinate pair. */
7
- export function mapsEmbedUrl(lat, lon, zoom = 15) {
7
+ function mapsEmbedUrl(lat, lon, zoom = 15) {
8
8
  return `https://maps.google.com/maps?q=${lat},${lon}&z=${zoom}&output=embed`;
9
9
  }
10
10
  /**
package/dist/mcp.d.ts CHANGED
@@ -11,10 +11,6 @@ export declare const MCP_PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26"
11
11
  export type McpProtocolVersion = (typeof MCP_PROTOCOL_VERSIONS)[number];
12
12
  /** The newest version spoken here, and what an unrecognised ask falls back to. */
13
13
  export declare const MCP_PROTOCOL_VERSION: McpProtocolVersion;
14
- /** Echo the client's version when it is one we speak, else offer the newest. */
15
- export declare function negotiateProtocol(requested: unknown): McpProtocolVersion;
16
- /** The version a post-initialize request is operating under. */
17
- export declare function requestProtocol(request: Request): McpProtocolVersion;
18
14
  import { type RateLimitOptions } from './rate-limit.js';
19
15
  export type ToolParam = {
20
16
  type: 'string' | 'number' | 'boolean' | 'array' | 'object';
package/dist/mcp.js CHANGED
@@ -28,11 +28,11 @@ export const MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSIONS[0];
28
28
  const ASSUMED_VERSION = '2025-03-26';
29
29
  const speaks = (v) => MCP_PROTOCOL_VERSIONS.includes(v);
30
30
  /** Echo the client's version when it is one we speak, else offer the newest. */
31
- export function negotiateProtocol(requested) {
31
+ function negotiateProtocol(requested) {
32
32
  return speaks(requested) ? requested : MCP_PROTOCOL_VERSION;
33
33
  }
34
34
  /** The version a post-initialize request is operating under. */
35
- export function requestProtocol(request) {
35
+ function requestProtocol(request) {
36
36
  const header = request.headers.get('mcp-protocol-version');
37
37
  return speaks(header) ? header : ASSUMED_VERSION;
38
38
  }
@@ -143,9 +143,7 @@ function methodsFor(config) {
143
143
  ...BASE_METHODS,
144
144
  // The list methods answer whether or not anything is registered: an empty
145
145
  // list is a better answer to a client that asked than a -32601 it has to
146
- // interpret. This enumeration used to omit them while the dispatch below
147
- // answered them, so the error message contradicted the server describing
148
- // itself.
146
+ // interpret.
149
147
  'resources/list',
150
148
  'resources/templates/list',
151
149
  'prompts/list',
@@ -286,13 +284,12 @@ async function handleRpc(req, config, dispatch) {
286
284
  try {
287
285
  const data = await tool.handler(args, ctx);
288
286
  const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
289
- // Every object answer, not only the schema-bearing ones. Gating this
290
- // on `outputSchema` meant that across four live servers and thirty-two
291
- // tools — every one of them returning JSON — not a single result ever
292
- // carried `structuredContent`, and every agent parsed prose to reach
293
- // data the server already had in hand. A declared `outputSchema` is
294
- // still the stronger contract (a client validates against it); it is
295
- // no longer the price of admission. The text block stays regardless:
287
+ // Every object answer, not only the schema-bearing ones. Gated on
288
+ // `outputSchema`, a JSON-returning tool without one would carry no
289
+ // `structuredContent`, and agents would parse prose to reach data the
290
+ // server has in hand. A declared `outputSchema` is the stronger
291
+ // contract (a client validates against it), not the price of
292
+ // admission. The text block stays regardless:
296
293
  // the spec asks for the serialised twin, and a client that reads only
297
294
  // content still has to be able to read the answer.
298
295
  const structured = data !== null && typeof data === 'object' && !Array.isArray(data)
@@ -7,8 +7,6 @@ export interface PaginatedResponse<T> extends Pagination {
7
7
  total: number;
8
8
  totalPages: number;
9
9
  }
10
- export declare const MAX_PAGE_SIZE = 100;
11
- export declare const DEFAULT_PAGE_SIZE = 20;
12
10
  /**
13
11
  * `page` clamped to ≥1, `pageSize` clamped to 1–`max`. Never trust either.
14
12
  *
@@ -55,14 +53,12 @@ export declare function toOffset({ page, pageSize }: Pagination): {
55
53
  * The ordering is the caller's, deliberately — it is the one thing here that is
56
54
  * never portable. A wiki's sequence is its section's configured sort, a
57
55
  * knowledge base's is a taxonomy walk, and a company log's is `updatedAt` desc.
58
- * What both wikis had written twice is this scan, not the sort.
59
56
  *
60
57
  * `null` on both sides when the page is not in the list, so a page reached by a
61
58
  * URL its own section does not list renders no nav rather than a wrong one.
62
59
  *
63
- * No query. Both callers already hold the ordered siblings for something else —
64
- * a related-pages panel, a section listing — and the two indexed lookups the
65
- * neighbours used to cost were the reason one wiki dropped the control.
60
+ * No query. Callers already hold the ordered siblings for something else, a
61
+ * related-pages panel or a section listing, so the neighbours cost no lookups.
66
62
  */
67
63
  export declare function adjacentPages<T>(ordered: readonly T[], isCurrent: (page: T) => boolean): {
68
64
  prev: T | null;
@@ -3,8 +3,8 @@
3
3
  // Forked three ways once: a re-typed clamp that dropped `totalPages`, and an
4
4
  // offset redone by hand in raw SQL. The response shape is the contract a client
5
5
  // codes against, so reshaping it per repo is a breaking change nobody declared.
6
- export const MAX_PAGE_SIZE = 100;
7
- export const DEFAULT_PAGE_SIZE = 20;
6
+ const MAX_PAGE_SIZE = 100;
7
+ const DEFAULT_PAGE_SIZE = 20;
8
8
  /**
9
9
  * `page` clamped to ≥1, `pageSize` clamped to 1–`max`. Never trust either.
10
10
  *
@@ -62,14 +62,12 @@ export function toOffset({ page, pageSize }) {
62
62
  * The ordering is the caller's, deliberately — it is the one thing here that is
63
63
  * never portable. A wiki's sequence is its section's configured sort, a
64
64
  * knowledge base's is a taxonomy walk, and a company log's is `updatedAt` desc.
65
- * What both wikis had written twice is this scan, not the sort.
66
65
  *
67
66
  * `null` on both sides when the page is not in the list, so a page reached by a
68
67
  * URL its own section does not list renders no nav rather than a wrong one.
69
68
  *
70
- * No query. Both callers already hold the ordered siblings for something else —
71
- * a related-pages panel, a section listing — and the two indexed lookups the
72
- * neighbours used to cost were the reason one wiki dropped the control.
69
+ * No query. Callers already hold the ordered siblings for something else, a
70
+ * related-pages panel or a section listing, so the neighbours cost no lookups.
73
71
  */
74
72
  export function adjacentPages(ordered, isCurrent) {
75
73
  const i = ordered.findIndex(isCurrent);
@@ -69,10 +69,9 @@ export declare function resetRateLimits(): void;
69
69
  * `.well-known/mcp.json`, the OpenAPI spec, agents.md, llms.txt and the
70
70
  * `initialize` instructions all quote this, so the endpoint enforces exactly
71
71
  * what the documents claim. It is a cross-surface contract, which is why it
72
- * lives here rather than three times over: it had already drifted to 200 on one
73
- * surface with nothing recorded about why, and that is the state that makes a
74
- * number impossible to change safely later — nobody can tell a considered
75
- * difference from a stale one.
72
+ * lives here rather than three times over. A second copy is how a number
73
+ * becomes impossible to change safely: nobody can tell a considered difference
74
+ * from a stale one.
76
75
  *
77
76
  * A surface with a genuine reason to differ passes its own `RateLimitOptions`.
78
77
  * What it must not do is restate this one.
@@ -117,10 +117,9 @@ export function resetRateLimits() {
117
117
  * `.well-known/mcp.json`, the OpenAPI spec, agents.md, llms.txt and the
118
118
  * `initialize` instructions all quote this, so the endpoint enforces exactly
119
119
  * what the documents claim. It is a cross-surface contract, which is why it
120
- * lives here rather than three times over: it had already drifted to 200 on one
121
- * surface with nothing recorded about why, and that is the state that makes a
122
- * number impossible to change safely later — nobody can tell a considered
123
- * difference from a stale one.
120
+ * lives here rather than three times over. A second copy is how a number
121
+ * becomes impossible to change safely: nobody can tell a considered difference
122
+ * from a stale one.
124
123
  *
125
124
  * A surface with a genuine reason to differ passes its own `RateLimitOptions`.
126
125
  * What it must not do is restate this one.
@@ -54,7 +54,7 @@ export interface FacetBarProps {
54
54
  * dropping the active facets — and the A–Z row leads with the reset control.
55
55
  *
56
56
  * `aria-current`, not `aria-pressed`: a link is not a toggle button and does not
57
- * take that attribute. Two of the three copies this replaces had it wrong.
57
+ * take that attribute.
58
58
  */
59
59
  export declare function FacetBar({ link: Link, facets, letters, alphaLabel, classNames, }: FacetBarProps): import("react").JSX.Element | null;
60
60
  export interface BreadcrumbItem {
@@ -114,7 +114,7 @@ export declare function Breadcrumbs({ items, base, className, link: Link, lastAs
114
114
  * The standard page-top row: the trail, plus optional right-aligned actions.
115
115
  *
116
116
  * The column it sits in is the one its route declares — the row reads the
117
- * page's own max-width by inheritance, so a trail can no longer disagree with
117
+ * page's own max-width by inheritance, so a trail cannot disagree with
118
118
  * the content it titles.
119
119
  */
120
120
  export declare function BreadcrumbsRow({ actions, className, ...props }: BreadcrumbsProps & {
@@ -146,15 +146,11 @@ export interface PageNavProps {
146
146
  * lateral links do not cover.
147
147
  *
148
148
  * Ordering is entirely the caller's; pair this with `adjacentPages` from
149
- * `wiki-formant/pagination` over a list you already hold. Both wikis had this
150
- * markup and had already drifted on the parts that matter rather than the parts
151
- * that show: one carried `rel="prev"`/`rel="next"` and an `aria-label` and the
152
- * other carried neither, and the one without expressed its right-hand alignment
153
- * as two inline utilities instead of the modifier its own stylesheet defines.
149
+ * `wiki-formant/pagination` over a list you already hold.
154
150
  *
155
- * The `page-nav__*` class names are NOT props, for the reason `WikiRail`'s are
156
- * not: they are the convention both stylesheets already implement, and making
157
- * them configurable is how a convention forks. The empty `<div>` holds the
158
- * first article's left column so the next link stays in the right one.
151
+ * The `page-nav__*` class names are NOT props: they are the convention every
152
+ * stylesheet implements, and making them configurable is how a convention
153
+ * forks. The empty `<div>` holds the first article's left column so the next
154
+ * link stays in the right one.
159
155
  */
160
156
  export declare function PageNav({ prev, next, link: Link, label, prevLabel, nextLabel, prevGlyph, nextGlyph, }: PageNavProps): import("react").JSX.Element | null;
@@ -34,7 +34,7 @@ export const Anchor = ({ href, className, children }) => _jsx("a", { href: href,
34
34
  * dropping the active facets — and the A–Z row leads with the reset control.
35
35
  *
36
36
  * `aria-current`, not `aria-pressed`: a link is not a toggle button and does not
37
- * take that attribute. Two of the three copies this replaces had it wrong.
37
+ * take that attribute.
38
38
  */
39
39
  export function FacetBar({ link: Link, facets, letters, alphaLabel = 'A–Z', classNames = {}, }) {
40
40
  if (!facets.length && !letters.length)
@@ -85,7 +85,7 @@ export function Breadcrumbs({ items, base, className = '', link: Link = Anchor,
85
85
  * The standard page-top row: the trail, plus optional right-aligned actions.
86
86
  *
87
87
  * The column it sits in is the one its route declares — the row reads the
88
- * page's own max-width by inheritance, so a trail can no longer disagree with
88
+ * page's own max-width by inheritance, so a trail cannot disagree with
89
89
  * the content it titles.
90
90
  */
91
91
  export function BreadcrumbsRow({ actions, className = '', ...props }) {
@@ -96,16 +96,12 @@ export function BreadcrumbsRow({ actions, className = '', ...props }) {
96
96
  * lateral links do not cover.
97
97
  *
98
98
  * Ordering is entirely the caller's; pair this with `adjacentPages` from
99
- * `wiki-formant/pagination` over a list you already hold. Both wikis had this
100
- * markup and had already drifted on the parts that matter rather than the parts
101
- * that show: one carried `rel="prev"`/`rel="next"` and an `aria-label` and the
102
- * other carried neither, and the one without expressed its right-hand alignment
103
- * as two inline utilities instead of the modifier its own stylesheet defines.
99
+ * `wiki-formant/pagination` over a list you already hold.
104
100
  *
105
- * The `page-nav__*` class names are NOT props, for the reason `WikiRail`'s are
106
- * not: they are the convention both stylesheets already implement, and making
107
- * them configurable is how a convention forks. The empty `<div>` holds the
108
- * first article's left column so the next link stays in the right one.
101
+ * The `page-nav__*` class names are NOT props: they are the convention every
102
+ * stylesheet implements, and making them configurable is how a convention
103
+ * forks. The empty `<div>` holds the first article's left column so the next
104
+ * link stays in the right one.
109
105
  */
110
106
  export function PageNav({ prev, next, link: Link = Anchor, label = 'Article navigation', prevLabel = 'Previous', nextLabel = 'Next', prevGlyph = '\u2190', nextGlyph = '\u2192', }) {
111
107
  if (!prev && !next)
package/dist/react.d.ts CHANGED
@@ -22,14 +22,6 @@ export interface SidebarState {
22
22
  */
23
23
  ready: boolean;
24
24
  }
25
- /**
26
- * Collapse state for a wiki rail: remembered across loads, defaulted from the
27
- * viewport only when the reader has never chosen.
28
- *
29
- * The reader's choice outranks the breakpoint. Someone who collapses the rail
30
- * on a laptop and then narrows the window has still collapsed the rail.
31
- */
32
- export declare function useCollapsibleSidebar(options?: SidebarOptions): SidebarState;
33
25
  export interface SidebarProviderProps extends SidebarOptions {
34
26
  children: ReactNode;
35
27
  }
@@ -286,8 +278,7 @@ export declare class ErrorBoundary extends Component<{
286
278
  children: ReactNode;
287
279
  /**
288
280
  * `error` is passed as well as `retry` because a fallback that cannot see
289
- * what failed can only say "something went wrong" — which is what one of
290
- * the copies this replaced said, while the other had the message and used it.
281
+ * what failed can only say "something went wrong".
291
282
  */
292
283
  fallback: (retry: () => void, error: unknown) => ReactNode;
293
284
  onError?: (error: unknown) => void;
package/dist/react.js CHANGED
@@ -47,7 +47,7 @@ const readStored = (key) => {
47
47
  * The reader's choice outranks the breakpoint. Someone who collapses the rail
48
48
  * on a laptop and then narrows the window has still collapsed the rail.
49
49
  */
50
- export function useCollapsibleSidebar(options = {}) {
50
+ function useCollapsibleSidebar(options = {}) {
51
51
  const { storageKey = 'wiki:sidebar', breakpoint = 1024 } = options;
52
52
  // Start open on the server and on the first client paint. SSR has no viewport
53
53
  // and no storage, so any other guess is a guaranteed mismatch on some loads.
@@ -54,15 +54,6 @@ interface Located<B> {
54
54
  }
55
55
  /** Every block in the tree, flattened, each with the path that addresses it. */
56
56
  export declare function extractBlocks<B>(blocks: readonly B[], containers: (block: B) => BlockGroup<B>[] | null, basePath?: string): Located<B>[];
57
- /**
58
- * Which scalar fields differ. Nested block arrays are skipped: they are walked
59
- * as their own entries, and comparing them here would report a container as
60
- * modified every time anything inside it moved.
61
- */
62
- export declare function diffAttributes<B extends DiffBlock>(oldBlock: B, newBlock: B, containers: (block: B) => BlockGroup<B>[] | null): Record<string, {
63
- from: unknown;
64
- to: unknown;
65
- }> | undefined;
66
57
  /**
67
58
  * Blocks matched by id, so a block that moved is reported as moved rather than
68
59
  * as one removal and one addition.
package/dist/revisions.js CHANGED
@@ -1,17 +1,9 @@
1
1
  // revisions.ts — what changed between two versions of a page.
2
2
  //
3
3
  // The semver lives next door in `versioning.ts`; this is the walk that decides
4
- // which bump to ask for. Both wikis had written it, and `extractBlocks` was
5
- // byte-identical between the two copies.
6
- //
7
- // They differed in what each had learned since. One grew a leaf-level diff, a
8
- // second changed-flag for the page's banner, and a `patch` classification for
9
- // when only that flag moved. The other grew none of those and instead dropped
10
- // twenty-five lines the first still carries: two Maps keyed by a recursive
11
- // JSON.stringify of every block, built on every save and never read once.
12
- // Matching is by id and always was.
13
- //
14
- // This is the union, minus the dead half.
4
+ // which bump to ask for. It carries a leaf-level diff, a changed-flag for the
5
+ // page's banner, and a `patch` classification for when only that flag moved.
6
+ // Matching is by id.
15
7
  import { incrementVersion, parseVersion } from './versioning.js';
16
8
  /** Every block in the tree, flattened, each with the path that addresses it. */
17
9
  export function extractBlocks(blocks, containers, basePath = 'root') {
@@ -30,7 +22,7 @@ export function extractBlocks(blocks, containers, basePath = 'root') {
30
22
  * as their own entries, and comparing them here would report a container as
31
23
  * modified every time anything inside it moved.
32
24
  */
33
- export function diffAttributes(oldBlock, newBlock, containers) {
25
+ function diffAttributes(oldBlock, newBlock, containers) {
34
26
  const diffs = {};
35
27
  // Derived from these two blocks rather than from the tree, so a container
36
28
  // nested inside another container is still exempted. Deriving it once from
package/dist/rola.js CHANGED
@@ -1,17 +1,11 @@
1
1
  // rola.ts — Radix On-Ledger Authentication: challenge, proof, session.
2
2
  //
3
- // Two Radix wikis had written this file, and the copies were 94% identical — the
4
- // whole difference was a cookie name, plus the half radix-wiki had learned and
5
- // miow had not: persona (identity_*) proofs, which miow rejected because it
6
- // passed `type: 'account'` unconditionally, and the error logging that makes a
7
- // failed verification diagnosable at all.
3
+ // Parameterised on the two things a second wiki would differ in: the cookie
4
+ // name and its dApp identity. It verifies persona (identity_*) proofs as well
5
+ // as account proofs, and logs a failed verification so it is diagnosable.
8
6
  //
9
- // This is radix-wiki's version, parameterised on the two things a second wiki
10
- // would actually differ in: the cookie name and its dApp identity.
11
- //
12
- // **One consumer, as of Sep 2026.** miow is retired, so the second wiki this was
13
- // parameterised for no longer exists; caper's wallet auth never used this stack
14
- // and acuiq gates only its wiki editor, on a shared secret. Keep it — the
7
+ // **One consumer.** caper's wallet auth never used this stack and acuiq gates
8
+ // only its wiki editor, on a shared secret. Keep it — the
15
9
  // persona-proof fix and the error logging are worth not losing, and the ports
16
10
  // below are what make it reusable at all — but do not mistake it for a proven
17
11
  // shared abstraction. It is radix-wiki's auth that happens to live in a package.
@@ -44,12 +44,11 @@ export interface RelatedRanking<T> {
44
44
  * value is precisely a set the category view already filters and counts, so the
45
45
  * row is the way into that set rather than dead text.
46
46
  *
47
- * Rows, not markup: the three wikis this was lifted from render the same
48
- * selection three ways (an HTML `<table>` string folded into a block, a React
49
- * `<aside>`, a markdown twin), and they differ only in how a value is
50
- * *formatted* — a `url` gets an external link here, a shortened display there.
51
- * What they had all rebuilt, and had already begun to disagree about, is which
52
- * keys appear and where each one links.
47
+ * Rows, not markup: wikis render the same selection differently (an HTML
48
+ * `<table>` string folded into a block, a React `<aside>`, a markdown twin) and
49
+ * differ only in how a value is *formatted*: a `url` gets an external link
50
+ * here, a shortened display there. Which keys appear and where each one links
51
+ * is what they share.
53
52
  */
54
53
  export interface MetadataRow extends MetadataKeyDefinition {
55
54
  value: string;
@@ -119,7 +118,6 @@ export interface TaxonomyConfig {
119
118
  export declare function hrefBuilder(prefix?: string): (tagPath: string, state: CategoryState) => string;
120
119
  /** Categories mounted at the root. */
121
120
  export declare const defaultHref: (tagPath: string, state: CategoryState) => string;
122
- export declare const DEFAULT_ALPHA_INDEX_MIN_PAGES = 40;
123
121
  /** Bucket for titles that don't start with a letter (numerals, `$CAPER`). */
124
122
  export declare function firstLetter(title: string): string;
125
123
  /** Adding a filter already active removes it, so every chip is its own off-switch. */
package/dist/taxonomy.js CHANGED
@@ -36,7 +36,7 @@ export function hrefBuilder(prefix = '') {
36
36
  }
37
37
  /** Categories mounted at the root. */
38
38
  export const defaultHref = hrefBuilder();
39
- export const DEFAULT_ALPHA_INDEX_MIN_PAGES = 40;
39
+ const DEFAULT_ALPHA_INDEX_MIN_PAGES = 40;
40
40
  const metaValue = (page, key) => (page.metadata?.[key] ?? '').trim();
41
41
  /** Bucket for titles that don't start with a letter (numerals, `$CAPER`). */
42
42
  export function firstLetter(title) {
package/dist/text.d.ts CHANGED
@@ -33,15 +33,6 @@ export declare function bannerToText(label: string, text?: string | null): strin
33
33
  export declare function codeTabsToText(tabs: readonly CodeTab[]): string;
34
34
  /** A numbered reference list, or `''` when there are none. */
35
35
  export declare function referencesToText(items: readonly ReferenceItem[]): string;
36
- /**
37
- * Every `text` value at any depth of a block tree, in document order.
38
- *
39
- * Deliberately NOT the typed extractor above: that walks a switch and formats
40
- * for reading (labels, bullets, reference numbering), so it can surface text a
41
- * search index never matched and miss text it did. A snippet claiming to show
42
- * why a row matched has to read the same bytes the match was made against.
43
- */
44
- export declare function collectText(node: unknown, out?: string[]): string[];
45
36
  /**
46
37
  * The passage that matched `query`, not the opening of the page.
47
38
  *
package/dist/text.js CHANGED
@@ -73,7 +73,7 @@ export function referencesToText(items) {
73
73
  * search index never matched and miss text it did. A snippet claiming to show
74
74
  * why a row matched has to read the same bytes the match was made against.
75
75
  */
76
- export function collectText(node, out = []) {
76
+ function collectText(node, out = []) {
77
77
  if (Array.isArray(node)) {
78
78
  for (const item of node)
79
79
  collectText(item, out);
package/dist/tiptap.js CHANGED
@@ -1,17 +1,12 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- // tiptap.tsx — the custom editor nodes both wikis had written twice.
3
+ // tiptap.tsx — the custom editor nodes the wikis share.
4
4
  //
5
5
  // Behind its own subpath for the same reason `react.tsx` is: the four
6
6
  // `@tiptap/*` packages are OPTIONAL peer dependencies, so a consumer that only
7
7
  // wants the taxonomy or the MCP transport still installs a package with no
8
8
  // runtime dependencies at all.
9
9
  //
10
- // The two copies had drifted in both directions — one had grown tabs the other
11
- // lacked, the other had extracted the Twitter helper the first still wrote out
12
- // three times — which is the shape of drift that costs the most: neither copy
13
- // is behind, so neither looks like the one to fix.
14
- //
15
10
  // What is shared is the node schema and its behaviour, never appearance. Class
16
11
  // names and icons are injected, which is what lets one wiki keep `text-jupiter`
17
12
  // and the other `text-accent` without either forking the node. It also keeps
@@ -64,7 +59,7 @@ export const YouTube = TiptapYoutube.extend({
64
59
  function TwitterEmbedView({ node }) {
65
60
  const containerRef = useRef(null);
66
61
  // The embed posts its measured height back; `onTweetResize` owns the origin
67
- // check, which is the part that was written out at four call sites.
62
+ // check.
68
63
  useEffect(() => onTweetResize(height => {
69
64
  const iframe = containerRef.current?.querySelector('iframe');
70
65
  if (iframe)
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // The block-diff half stays in the app: it has to walk a block tree, and every
4
4
  // project owns its own type set. What travels is the version arithmetic and the
5
- // rule for choosing a bump, which had drifted into a per-repo string split.
5
+ // rule for choosing a bump.
6
6
  /** Tolerant of null, empty and malformed input — a page always has a version. */
7
7
  export function parseVersion(version) {
8
8
  if (!version)
@@ -55,12 +55,6 @@ export interface AgentCardLicense {
55
55
  /** What the licence covers, e.g. `'content'`. */
56
56
  scope?: string;
57
57
  }
58
- /**
59
- * The A2A revision these cards are written to. Required since v0.3 — a card
60
- * without it is not a card a spec-current client will accept, and all three
61
- * origins here were serving one.
62
- */
63
- export declare const A2A_PROTOCOL_VERSION = "0.3.0";
64
58
  export interface AgentCardConfig {
65
59
  name: string;
66
60
  description: string;
@@ -61,7 +61,7 @@ export function skillsFromTools(tools) {
61
61
  * without it is not a card a spec-current client will accept, and all three
62
62
  * origins here were serving one.
63
63
  */
64
- export const A2A_PROTOCOL_VERSION = '0.3.0';
64
+ const A2A_PROTOCOL_VERSION = '0.3.0';
65
65
  /**
66
66
  * An A2A Agent Card. Serve the same object at both `/.well-known/agent.json`
67
67
  * and `/.well-known/agent-card.json`: v0.3 renamed the path and defined no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-formant",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "description": "The portable half of a wiki: derived taxonomy and facet controls, a version-negotiating MCP transport, markdown twins, block rendering, a rich-text editor engine, and conditional-GET plumbing.",
5
5
  "type": "module",
6
6
  "license": "MIT",