mrplex 0.0.1

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 (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -0
  3. package/dist/cli/config.d.ts +28 -0
  4. package/dist/cli/config.js +48 -0
  5. package/dist/cli/config.js.map +1 -0
  6. package/dist/cli/exit-codes.d.ts +13 -0
  7. package/dist/cli/exit-codes.js +39 -0
  8. package/dist/cli/exit-codes.js.map +1 -0
  9. package/dist/cli/format.d.ts +22 -0
  10. package/dist/cli/format.js +148 -0
  11. package/dist/cli/format.js.map +1 -0
  12. package/dist/cli/main.d.ts +16 -0
  13. package/dist/cli/main.js +1518 -0
  14. package/dist/cli/main.js.map +1 -0
  15. package/dist/client/kernel-client.d.ts +97 -0
  16. package/dist/client/kernel-client.js +14 -0
  17. package/dist/client/kernel-client.js.map +1 -0
  18. package/dist/client/local.d.ts +26 -0
  19. package/dist/client/local.js +103 -0
  20. package/dist/client/local.js.map +1 -0
  21. package/dist/client/remote-mcp.d.ts +35 -0
  22. package/dist/client/remote-mcp.js +174 -0
  23. package/dist/client/remote-mcp.js.map +1 -0
  24. package/dist/embed/backfill.d.ts +24 -0
  25. package/dist/embed/backfill.js +33 -0
  26. package/dist/embed/backfill.js.map +1 -0
  27. package/dist/embed/chunker.d.ts +31 -0
  28. package/dist/embed/chunker.js +121 -0
  29. package/dist/embed/chunker.js.map +1 -0
  30. package/dist/embed/cmd-hook.d.ts +21 -0
  31. package/dist/embed/cmd-hook.js +100 -0
  32. package/dist/embed/cmd-hook.js.map +1 -0
  33. package/dist/embed/config.d.ts +61 -0
  34. package/dist/embed/config.js +109 -0
  35. package/dist/embed/config.js.map +1 -0
  36. package/dist/embed/hook.d.ts +38 -0
  37. package/dist/embed/hook.js +56 -0
  38. package/dist/embed/hook.js.map +1 -0
  39. package/dist/embed/http-hook.d.ts +13 -0
  40. package/dist/embed/http-hook.js +39 -0
  41. package/dist/embed/http-hook.js.map +1 -0
  42. package/dist/embed/worker.d.ts +62 -0
  43. package/dist/embed/worker.js +228 -0
  44. package/dist/embed/worker.js.map +1 -0
  45. package/dist/fixtures/hwe/characters/albion-ever.md +12 -0
  46. package/dist/fixtures/hwe/characters/cent.md +11 -0
  47. package/dist/fixtures/hwe/concepts/cent-demiurge-resonance.md +10 -0
  48. package/dist/fixtures/hwe/concepts/phase-looper.md +10 -0
  49. package/dist/fixtures/hwe/entities/demiurge.md +11 -0
  50. package/dist/fixtures/hwe/entities/monad.md +11 -0
  51. package/dist/fixtures/hwe/moc/globocorp-network.md +19 -0
  52. package/dist/fixtures/hwe/organizations/globocorp.md +11 -0
  53. package/dist/fixtures/hwe/readme.md +24 -0
  54. package/dist/fixtures/starship/crew/aria-okonkwo.md +14 -0
  55. package/dist/fixtures/starship/crew/bexley-orr.md +14 -0
  56. package/dist/fixtures/starship/crew/dax-thorne.md +14 -0
  57. package/dist/fixtures/starship/crew/isolde-marsh.md +16 -0
  58. package/dist/fixtures/starship/crew/kestrel-vance.md +15 -0
  59. package/dist/fixtures/starship/crew/quill-vasquez.md +14 -0
  60. package/dist/fixtures/starship/crew/soren-halloway.md +16 -0
  61. package/dist/fixtures/starship/encounters/the-drift-choir.md +14 -0
  62. package/dist/fixtures/starship/encounters/the-hollow-signal.md +16 -0
  63. package/dist/fixtures/starship/encounters/the-wandering-buoy.md +14 -0
  64. package/dist/fixtures/starship/equipment/coolant-loop-b.md +13 -0
  65. package/dist/fixtures/starship/equipment/emergency-ration-bars.md +12 -0
  66. package/dist/fixtures/starship/equipment/long-range-sensors.md +13 -0
  67. package/dist/fixtures/starship/equipment/plasma-manifold-3.md +15 -0
  68. package/dist/fixtures/starship/equipment/shuttle-corvid.md +13 -0
  69. package/dist/fixtures/starship/logs/marsh-4419-1.md +13 -0
  70. package/dist/fixtures/starship/logs/okonkwo-4415-1.md +13 -0
  71. package/dist/fixtures/starship/logs/orr-4413-1.md +13 -0
  72. package/dist/fixtures/starship/logs/orr-4420-1.md +12 -0
  73. package/dist/fixtures/starship/logs/thorne-4413-2.md +13 -0
  74. package/dist/fixtures/starship/logs/vance-4419-2.md +13 -0
  75. package/dist/fixtures/starship/misc/the-galley-ficus.md +11 -0
  76. package/dist/fixtures/starship/missions/the-cinder-run.md +17 -0
  77. package/dist/fixtures/starship/missions/the-drift-choir.md +17 -0
  78. package/dist/fixtures/starship/missions/the-hollow-signal.md +17 -0
  79. package/dist/fixtures/starship/missions/the-silent-beacon.md +17 -0
  80. package/dist/fixtures/starship/moc/crew.md +19 -0
  81. package/dist/fixtures/starship/moc/encounters.md +14 -0
  82. package/dist/fixtures/starship/moc/missions.md +15 -0
  83. package/dist/fixtures/starship/readme.md +60 -0
  84. package/dist/index.d.ts +1 -0
  85. package/dist/index.js +3 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/kernel/auth/glob.d.ts +46 -0
  88. package/dist/kernel/auth/glob.js +112 -0
  89. package/dist/kernel/auth/glob.js.map +1 -0
  90. package/dist/kernel/auth/scope.d.ts +53 -0
  91. package/dist/kernel/auth/scope.js +83 -0
  92. package/dist/kernel/auth/scope.js.map +1 -0
  93. package/dist/kernel/casefold.d.ts +37 -0
  94. package/dist/kernel/casefold.js +44 -0
  95. package/dist/kernel/casefold.js.map +1 -0
  96. package/dist/kernel/constants.d.ts +21 -0
  97. package/dist/kernel/constants.js +22 -0
  98. package/dist/kernel/constants.js.map +1 -0
  99. package/dist/kernel/context.d.ts +70 -0
  100. package/dist/kernel/context.js +114 -0
  101. package/dist/kernel/context.js.map +1 -0
  102. package/dist/kernel/deletion.d.ts +50 -0
  103. package/dist/kernel/deletion.js +83 -0
  104. package/dist/kernel/deletion.js.map +1 -0
  105. package/dist/kernel/diff.d.ts +36 -0
  106. package/dist/kernel/diff.js +61 -0
  107. package/dist/kernel/diff.js.map +1 -0
  108. package/dist/kernel/errors.d.ts +37 -0
  109. package/dist/kernel/errors.js +55 -0
  110. package/dist/kernel/errors.js.map +1 -0
  111. package/dist/kernel/frontmatter-input.d.ts +32 -0
  112. package/dist/kernel/frontmatter-input.js +85 -0
  113. package/dist/kernel/frontmatter-input.js.map +1 -0
  114. package/dist/kernel/graph.d.ts +34 -0
  115. package/dist/kernel/graph.js +559 -0
  116. package/dist/kernel/graph.js.map +1 -0
  117. package/dist/kernel/history.d.ts +74 -0
  118. package/dist/kernel/history.js +143 -0
  119. package/dist/kernel/history.js.map +1 -0
  120. package/dist/kernel/kernel.d.ts +162 -0
  121. package/dist/kernel/kernel.js +675 -0
  122. package/dist/kernel/kernel.js.map +1 -0
  123. package/dist/kernel/path-config.d.ts +101 -0
  124. package/dist/kernel/path-config.js +171 -0
  125. package/dist/kernel/path-config.js.map +1 -0
  126. package/dist/kernel/query/ast.d.ts +24 -0
  127. package/dist/kernel/query/ast.js +52 -0
  128. package/dist/kernel/query/ast.js.map +1 -0
  129. package/dist/kernel/query/cel-parse.d.ts +37 -0
  130. package/dist/kernel/query/cel-parse.js +88 -0
  131. package/dist/kernel/query/cel-parse.js.map +1 -0
  132. package/dist/kernel/query/degrees.d.ts +31 -0
  133. package/dist/kernel/query/degrees.js +135 -0
  134. package/dist/kernel/query/degrees.js.map +1 -0
  135. package/dist/kernel/query/graph-ast.d.ts +51 -0
  136. package/dist/kernel/query/graph-ast.js +139 -0
  137. package/dist/kernel/query/graph-ast.js.map +1 -0
  138. package/dist/kernel/query/query.d.ts +56 -0
  139. package/dist/kernel/query/query.js +276 -0
  140. package/dist/kernel/query/query.js.map +1 -0
  141. package/dist/kernel/validation.d.ts +64 -0
  142. package/dist/kernel/validation.js +183 -0
  143. package/dist/kernel/validation.js.map +1 -0
  144. package/dist/kernel/version-id.d.ts +10 -0
  145. package/dist/kernel/version-id.js +21 -0
  146. package/dist/kernel/version-id.js.map +1 -0
  147. package/dist/kernel/wire.d.ts +148 -0
  148. package/dist/kernel/wire.js +6 -0
  149. package/dist/kernel/wire.js.map +1 -0
  150. package/dist/links/backfill.d.ts +28 -0
  151. package/dist/links/backfill.js +33 -0
  152. package/dist/links/backfill.js.map +1 -0
  153. package/dist/links/extract.d.ts +29 -0
  154. package/dist/links/extract.js +233 -0
  155. package/dist/links/extract.js.map +1 -0
  156. package/dist/links/link-config.d.ts +70 -0
  157. package/dist/links/link-config.js +107 -0
  158. package/dist/links/link-config.js.map +1 -0
  159. package/dist/links/maintain.d.ts +37 -0
  160. package/dist/links/maintain.js +91 -0
  161. package/dist/links/maintain.js.map +1 -0
  162. package/dist/links/repair.d.ts +46 -0
  163. package/dist/links/repair.js +96 -0
  164. package/dist/links/repair.js.map +1 -0
  165. package/dist/links/resolve.d.ts +41 -0
  166. package/dist/links/resolve.js +121 -0
  167. package/dist/links/resolve.js.map +1 -0
  168. package/dist/links/stale.d.ts +32 -0
  169. package/dist/links/stale.js +73 -0
  170. package/dist/links/stale.js.map +1 -0
  171. package/dist/markdown/content-hash.d.ts +34 -0
  172. package/dist/markdown/content-hash.js +46 -0
  173. package/dist/markdown/content-hash.js.map +1 -0
  174. package/dist/markdown/frontmatter.d.ts +69 -0
  175. package/dist/markdown/frontmatter.js +147 -0
  176. package/dist/markdown/frontmatter.js.map +1 -0
  177. package/dist/markdown/hash-backfill.d.ts +22 -0
  178. package/dist/markdown/hash-backfill.js +43 -0
  179. package/dist/markdown/hash-backfill.js.map +1 -0
  180. package/dist/mcp/query-syntax.d.ts +20 -0
  181. package/dist/mcp/query-syntax.js +194 -0
  182. package/dist/mcp/query-syntax.js.map +1 -0
  183. package/dist/mcp/render.d.ts +31 -0
  184. package/dist/mcp/render.js +110 -0
  185. package/dist/mcp/render.js.map +1 -0
  186. package/dist/mcp/server.d.ts +60 -0
  187. package/dist/mcp/server.js +170 -0
  188. package/dist/mcp/server.js.map +1 -0
  189. package/dist/mcp/tools.d.ts +93 -0
  190. package/dist/mcp/tools.js +1218 -0
  191. package/dist/mcp/tools.js.map +1 -0
  192. package/dist/rest/conditional.d.ts +37 -0
  193. package/dist/rest/conditional.js +58 -0
  194. package/dist/rest/conditional.js.map +1 -0
  195. package/dist/rest/negotiate.d.ts +33 -0
  196. package/dist/rest/negotiate.js +54 -0
  197. package/dist/rest/negotiate.js.map +1 -0
  198. package/dist/rest/routes.d.ts +37 -0
  199. package/dist/rest/routes.js +744 -0
  200. package/dist/rest/routes.js.map +1 -0
  201. package/dist/seed/fixture-seed.d.ts +35 -0
  202. package/dist/seed/fixture-seed.js +75 -0
  203. package/dist/seed/fixture-seed.js.map +1 -0
  204. package/dist/seed/starship-config.d.ts +3 -0
  205. package/dist/seed/starship-config.js +5 -0
  206. package/dist/seed/starship-config.js.map +1 -0
  207. package/dist/server/headers.d.ts +36 -0
  208. package/dist/server/headers.js +30 -0
  209. package/dist/server/headers.js.map +1 -0
  210. package/dist/server/http-error.d.ts +53 -0
  211. package/dist/server/http-error.js +104 -0
  212. package/dist/server/http-error.js.map +1 -0
  213. package/dist/server/serve.d.ts +56 -0
  214. package/dist/server/serve.js +135 -0
  215. package/dist/server/serve.js.map +1 -0
  216. package/dist/shell/audit.d.ts +32 -0
  217. package/dist/shell/audit.js +28 -0
  218. package/dist/shell/audit.js.map +1 -0
  219. package/dist/shell/guard.d.ts +50 -0
  220. package/dist/shell/guard.js +176 -0
  221. package/dist/shell/guard.js.map +1 -0
  222. package/dist/shell/keys.d.ts +36 -0
  223. package/dist/shell/keys.js +64 -0
  224. package/dist/shell/keys.js.map +1 -0
  225. package/dist/shell/login.d.ts +56 -0
  226. package/dist/shell/login.js +130 -0
  227. package/dist/shell/login.js.map +1 -0
  228. package/dist/shell/oidc.d.ts +62 -0
  229. package/dist/shell/oidc.js +102 -0
  230. package/dist/shell/oidc.js.map +1 -0
  231. package/dist/shell/policy.d.ts +100 -0
  232. package/dist/shell/policy.js +232 -0
  233. package/dist/shell/policy.js.map +1 -0
  234. package/dist/shell/proxy-policy.d.ts +34 -0
  235. package/dist/shell/proxy-policy.js +120 -0
  236. package/dist/shell/proxy-policy.js.map +1 -0
  237. package/dist/shell/proxy.d.ts +47 -0
  238. package/dist/shell/proxy.js +253 -0
  239. package/dist/shell/proxy.js.map +1 -0
  240. package/dist/shell/serve.d.ts +86 -0
  241. package/dist/shell/serve.js +0 -0
  242. package/dist/shell/serve.js.map +1 -0
  243. package/dist/shell/stdio.d.ts +55 -0
  244. package/dist/shell/stdio.js +63 -0
  245. package/dist/shell/stdio.js.map +1 -0
  246. package/dist/storage/registry.d.ts +20 -0
  247. package/dist/storage/registry.js +38 -0
  248. package/dist/storage/registry.js.map +1 -0
  249. package/dist/storage/search-plan.d.ts +93 -0
  250. package/dist/storage/search-plan.js +15 -0
  251. package/dist/storage/search-plan.js.map +1 -0
  252. package/dist/storage/types.d.ts +448 -0
  253. package/dist/storage/types.js +14 -0
  254. package/dist/storage/types.js.map +1 -0
  255. package/dist/storage/versions-since.d.ts +72 -0
  256. package/dist/storage/versions-since.js +92 -0
  257. package/dist/storage/versions-since.js.map +1 -0
  258. package/dist/storage-postgres/adapter.d.ts +17 -0
  259. package/dist/storage-postgres/adapter.js +782 -0
  260. package/dist/storage-postgres/adapter.js.map +1 -0
  261. package/dist/storage-postgres/compile-postgres.d.ts +22 -0
  262. package/dist/storage-postgres/compile-postgres.js +830 -0
  263. package/dist/storage-postgres/compile-postgres.js.map +1 -0
  264. package/dist/storage-postgres/errors.d.ts +34 -0
  265. package/dist/storage-postgres/errors.js +50 -0
  266. package/dist/storage-postgres/errors.js.map +1 -0
  267. package/dist/storage-postgres/migrations/0001_init.sql +113 -0
  268. package/dist/storage-postgres/migrations/0002_content_hash.sql +7 -0
  269. package/dist/storage-postgres/migrations/index.d.ts +22 -0
  270. package/dist/storage-postgres/migrations/index.js +70 -0
  271. package/dist/storage-postgres/migrations/index.js.map +1 -0
  272. package/dist/storage-sqlite/adapter.d.ts +2 -0
  273. package/dist/storage-sqlite/adapter.js +688 -0
  274. package/dist/storage-sqlite/adapter.js.map +1 -0
  275. package/dist/storage-sqlite/compile-filter.d.ts +41 -0
  276. package/dist/storage-sqlite/compile-filter.js +881 -0
  277. package/dist/storage-sqlite/compile-filter.js.map +1 -0
  278. package/dist/storage-sqlite/compile-sqlite.d.ts +19 -0
  279. package/dist/storage-sqlite/compile-sqlite.js +176 -0
  280. package/dist/storage-sqlite/compile-sqlite.js.map +1 -0
  281. package/dist/storage-sqlite/migrations/0001_init.sql +123 -0
  282. package/dist/storage-sqlite/migrations/0002_content_hash.sql +7 -0
  283. package/dist/storage-sqlite/migrations/index.d.ts +6 -0
  284. package/dist/storage-sqlite/migrations/index.js +41 -0
  285. package/dist/storage-sqlite/migrations/index.js.map +1 -0
  286. package/dist/storage-sqlite/vec.d.ts +40 -0
  287. package/dist/storage-sqlite/vec.js +62 -0
  288. package/dist/storage-sqlite/vec.js.map +1 -0
  289. package/dist/sync/cursor.d.ts +36 -0
  290. package/dist/sync/cursor.js +66 -0
  291. package/dist/sync/cursor.js.map +1 -0
  292. package/dist/sync/daemon.d.ts +40 -0
  293. package/dist/sync/daemon.js +202 -0
  294. package/dist/sync/daemon.js.map +1 -0
  295. package/dist/sync/feed.d.ts +38 -0
  296. package/dist/sync/feed.js +125 -0
  297. package/dist/sync/feed.js.map +1 -0
  298. package/dist/sync/fs-store.d.ts +8 -0
  299. package/dist/sync/fs-store.js +76 -0
  300. package/dist/sync/fs-store.js.map +1 -0
  301. package/dist/sync/intrinsics.d.ts +52 -0
  302. package/dist/sync/intrinsics.js +76 -0
  303. package/dist/sync/intrinsics.js.map +1 -0
  304. package/dist/sync/paths.d.ts +38 -0
  305. package/dist/sync/paths.js +56 -0
  306. package/dist/sync/paths.js.map +1 -0
  307. package/dist/sync/push.d.ts +48 -0
  308. package/dist/sync/push.js +328 -0
  309. package/dist/sync/push.js.map +1 -0
  310. package/dist/sync/reconcile.d.ts +54 -0
  311. package/dist/sync/reconcile.js +252 -0
  312. package/dist/sync/reconcile.js.map +1 -0
  313. package/dist/sync/run.d.ts +27 -0
  314. package/dist/sync/run.js +46 -0
  315. package/dist/sync/run.js.map +1 -0
  316. package/dist/version.d.ts +2 -0
  317. package/dist/version.js +5 -0
  318. package/dist/version.js.map +1 -0
  319. package/package.json +80 -0
@@ -0,0 +1,1218 @@
1
+ /**
2
+ * MCP tool registry — design §6.2.
3
+ *
4
+ * Tools mirror the kernel one-to-one. Each entry names the tool, describes it,
5
+ * carries a JSON Schema for the input, and delegates to a kernel call with the
6
+ * session's CallContext. No-auth (noauth plan): the user and token tools are
7
+ * gone; write tools gain an optional `author` and the query tool an optional
8
+ * `scope` — both overridden by the X-Mrplex-* request headers when present.
9
+ *
10
+ * Results shape:
11
+ * • On success: { structured, text } — structured is the wire type,
12
+ * text is a compact human rendering (render.ts).
13
+ * • On kernel error: throws; the SDK wiring in server.ts converts to an
14
+ * in-band tool error ({ code, data }) per §6.2.
15
+ */
16
+ import { validateScopeClaims } from "../kernel/context.js";
17
+ import { KernelError } from "../kernel/errors.js";
18
+ import { appendSystemProperty, extractSystemProperties } from "../markdown/frontmatter.js";
19
+ import { QUERY_SYNTAX_DOC } from "./query-syntax.js";
20
+ import { renderDocGetManyText, renderGraphSummary, renderJson, renderQueryHitList, renderRepoList, renderVersion, renderVersionList, } from "./render.js";
21
+ /** Shared exact-path input note for docs tools (canonical-path-normalization). */
22
+ const EXACT_PATH_DOC = "Canonical repository-relative path (e.g. `projects/example.md`). Exact-path operations also accept one leading `/` as a repository-root reference alias; responses always return the slashless canonical path.";
23
+ /** Wrap an array result as `{ items }` so it fits `structuredContent`. */
24
+ function wrapList(items) {
25
+ return { items };
26
+ }
27
+ // -----------------------------------------------------------------------------
28
+ // Argument helpers — narrow untyped JSON args into the kernel's typed shapes.
29
+ // The MCP transport already validates the JSON Schema; these accessors just
30
+ // pull typed values out of the validated bag.
31
+ // -----------------------------------------------------------------------------
32
+ function argStr(args, key) {
33
+ const v = args[key];
34
+ if (typeof v !== "string") {
35
+ throw new Error(`tool arg "${key}" must be a string`);
36
+ }
37
+ return v;
38
+ }
39
+ function argStrOpt(args, key) {
40
+ const v = args[key];
41
+ if (v === undefined || v === null)
42
+ return undefined;
43
+ if (typeof v !== "string")
44
+ throw new Error(`tool arg "${key}" must be a string`);
45
+ return v;
46
+ }
47
+ function argBoolOpt(args, key) {
48
+ const v = args[key];
49
+ if (v === undefined || v === null)
50
+ return undefined;
51
+ if (typeof v !== "boolean")
52
+ throw new Error(`tool arg "${key}" must be a boolean`);
53
+ return v;
54
+ }
55
+ function argStrArray(args, key) {
56
+ const v = args[key];
57
+ if (!Array.isArray(v) || v.length === 0 || !v.every((x) => typeof x === "string")) {
58
+ throw new Error(`tool arg "${key}" must be a non-empty array of strings`);
59
+ }
60
+ return v;
61
+ }
62
+ function argIntOpt(args, key) {
63
+ const v = args[key];
64
+ if (v === undefined || v === null)
65
+ return undefined;
66
+ if (typeof v !== "number" || !Number.isSafeInteger(v)) {
67
+ throw new Error(`tool arg "${key}" must be an integer`);
68
+ }
69
+ return v;
70
+ }
71
+ /**
72
+ * Merge a write's `author` tool-arg into the session context. Headers win:
73
+ * if the session already carries an author (header-injected), the tool arg is
74
+ * ignored (noauth plan decision 8).
75
+ */
76
+ function writeCtx(ctx, args) {
77
+ if (ctx.author !== undefined)
78
+ return ctx;
79
+ const author = argStrOpt(args, "author");
80
+ return author === undefined ? ctx : { ...ctx, author };
81
+ }
82
+ /**
83
+ * Merge a query's `scope` tool-arg into the session context. Headers win:
84
+ * a header-injected scope shadows the tool arg entirely.
85
+ */
86
+ function queryCtx(ctx, args) {
87
+ if (ctx.scope !== undefined)
88
+ return ctx;
89
+ const scope = args.scope;
90
+ if (scope === undefined || scope === null)
91
+ return ctx;
92
+ // Same structural check as the header/body paths — a repo-less claim is a
93
+ // loud filter_invalid, not a silent deny_all empty result.
94
+ return { ...ctx, scope: validateScopeClaims(scope) };
95
+ }
96
+ /**
97
+ * Append the injected system properties — `$version` then `$content_hash`, in
98
+ * fixed order (sync/history plan §2.4) — to `frontmatter_raw` unless the caller
99
+ * asked for raw output. Non-destructive — plain text append, no YAML round-trip.
100
+ * This is the linchpin of sync: a materialized file carries its own ancestry
101
+ * (`$version`) and clean-state fingerprint (`$content_hash`).
102
+ */
103
+ function withInjectedSystemProps(v, raw) {
104
+ if (raw)
105
+ return v;
106
+ let frontmatter_raw = appendSystemProperty(v.frontmatter_raw, "version", v.version_id);
107
+ frontmatter_raw = appendSystemProperty(frontmatter_raw, "content_hash", v.content_hash);
108
+ return { ...v, frontmatter_raw };
109
+ }
110
+ // -----------------------------------------------------------------------------
111
+ // Output schemas — success-result shapes of `structuredContent` (wire types,
112
+ // §6.4), declared via MCP's `outputSchema` so clients know what each tool
113
+ // returns without calling it. SDK clients validate any structuredContent
114
+ // against these — including on isError results — which is why toolError
115
+ // (server.ts) keeps the error payload in the text channel only.
116
+ // -----------------------------------------------------------------------------
117
+ /** `{ items: [...] }` — the wrapList shape for list-returning tools. */
118
+ function listResultSchema(item, description) {
119
+ return {
120
+ type: "object",
121
+ properties: { items: { type: "array", items: item, description } },
122
+ required: ["items"],
123
+ };
124
+ }
125
+ const REPO_SCHEMA = {
126
+ type: "object",
127
+ description: "A repo (wire shape).",
128
+ properties: {
129
+ repo: { type: "string", description: "Repo slug." },
130
+ path_config: {
131
+ anyOf: [{ type: "object", additionalProperties: true }, { type: "null" }],
132
+ description: "Per-repo path-config override; null = server defaults.",
133
+ },
134
+ },
135
+ required: ["repo", "path_config"],
136
+ };
137
+ const VERSION_SCHEMA = {
138
+ type: "object",
139
+ description: "A document version (wire shape).",
140
+ properties: {
141
+ version_id: {
142
+ type: "string",
143
+ description: "Opaque version id — usable as prev_version_id on the next write.",
144
+ },
145
+ prev_version_id: { anyOf: [{ type: "string" }, { type: "null" }] },
146
+ next_version_id: {
147
+ anyOf: [{ type: "string" }, { type: "null" }],
148
+ description: "null when this is the document's current version.",
149
+ },
150
+ repo: { type: "string", description: "Repo slug." },
151
+ path: { type: "string" },
152
+ frontmatter: {
153
+ type: "object",
154
+ additionalProperties: true,
155
+ description: "Frontmatter parsed to JSON (the query view of frontmatter_raw).",
156
+ },
157
+ frontmatter_raw: {
158
+ type: "string",
159
+ description: "Verbatim YAML frontmatter. Reads append `$version: <id>` then `$content_hash: <sha256>` " +
160
+ "unless raw: true.",
161
+ },
162
+ body: { type: "string", description: "Markdown body." },
163
+ author: { type: "string", description: "Opaque author string." },
164
+ created_at: { type: "string", description: "ISO-8601 UTC." },
165
+ content_hash: {
166
+ type: "string",
167
+ description: "SHA-256 (bare hex) of canonical content (frontmatter stripped of $*, plus body).",
168
+ },
169
+ },
170
+ required: [
171
+ "version_id",
172
+ "prev_version_id",
173
+ "next_version_id",
174
+ "repo",
175
+ "path",
176
+ "frontmatter",
177
+ "frontmatter_raw",
178
+ "body",
179
+ "author",
180
+ "created_at",
181
+ "content_hash",
182
+ ],
183
+ };
184
+ const DOC_GET_MANY_ERROR_SCHEMA = {
185
+ type: "object",
186
+ description: "Per-path failure from a batch get — the call still succeeds.",
187
+ properties: {
188
+ path: { type: "string" },
189
+ code: { type: "string" },
190
+ data: { type: "object", additionalProperties: true },
191
+ },
192
+ required: ["path", "code", "data"],
193
+ };
194
+ const DOC_GET_MANY_RESULT_SCHEMA = {
195
+ type: "object",
196
+ properties: {
197
+ items: {
198
+ type: "array",
199
+ items: VERSION_SCHEMA,
200
+ description: "Found current versions, in request order.",
201
+ },
202
+ errors: {
203
+ type: "array",
204
+ items: DOC_GET_MANY_ERROR_SCHEMA,
205
+ description: "Per-path failures (doc_not_found, forbidden), in request order.",
206
+ },
207
+ },
208
+ required: ["items", "errors"],
209
+ };
210
+ const DIFF_SCHEMA = {
211
+ type: "object",
212
+ properties: {
213
+ repo: { type: "string" },
214
+ path: { type: "string" },
215
+ from_version_id: { type: "string" },
216
+ to_version_id: { type: "string" },
217
+ patch: { type: "string", description: "Unified diff text." },
218
+ },
219
+ required: ["repo", "path", "from_version_id", "to_version_id", "patch"],
220
+ };
221
+ const LINKS_BACKFILL_SCHEMA = {
222
+ type: "object",
223
+ properties: {
224
+ documents: { type: "integer", description: "Live documents (re)extracted." },
225
+ edges: { type: "integer", description: "Link edges indexed." },
226
+ },
227
+ required: ["documents", "edges"],
228
+ };
229
+ const STALE_LINK_SCHEMA = {
230
+ type: "object",
231
+ description: "A live doc whose written link text no longer matches the target's current path.",
232
+ properties: {
233
+ repo: { type: "string" },
234
+ source_path: { type: "string", description: "Doc containing the stale link." },
235
+ ord: { type: "integer", description: "Link's ordinal within the source doc." },
236
+ written: { type: "string", description: "Link target as written." },
237
+ current: { type: "string", description: "Target's current path." },
238
+ },
239
+ required: ["repo", "source_path", "ord", "written", "current"],
240
+ };
241
+ const LINKS_REPAIR_SCHEMA = {
242
+ type: "object",
243
+ properties: {
244
+ dry_run: { type: "boolean" },
245
+ repaired: {
246
+ type: "array",
247
+ items: {
248
+ type: "object",
249
+ properties: { path: { type: "string" }, edges: { type: "integer" } },
250
+ required: ["path", "edges"],
251
+ },
252
+ },
253
+ skipped: {
254
+ type: "array",
255
+ items: {
256
+ type: "object",
257
+ properties: { path: { type: "string" }, reason: { type: "string" } },
258
+ required: ["path", "reason"],
259
+ },
260
+ },
261
+ },
262
+ required: ["dry_run", "repaired", "skipped"],
263
+ };
264
+ const SET_PATH_CONFIG_RESULT_SCHEMA = {
265
+ type: "object",
266
+ properties: {
267
+ repo: REPO_SCHEMA,
268
+ warnings: {
269
+ type: "array",
270
+ description: "Existing paths that violate the new config (flagged, not rejected).",
271
+ items: {
272
+ type: "object",
273
+ properties: {
274
+ version_id: { type: "string" },
275
+ path: { type: "string" },
276
+ reason: { type: "string" },
277
+ },
278
+ required: ["version_id", "path", "reason"],
279
+ },
280
+ },
281
+ },
282
+ required: ["repo", "warnings"],
283
+ };
284
+ const SET_LINK_CONFIG_RESULT_SCHEMA = {
285
+ type: "object",
286
+ properties: {
287
+ repo: REPO_SCHEMA,
288
+ reindexed: LINKS_BACKFILL_SCHEMA,
289
+ },
290
+ required: ["repo", "reindexed"],
291
+ };
292
+ const QUERY_HIT_SCHEMA = {
293
+ type: "object",
294
+ description: "A projected query hit — not a full document. `$`-keys are system intrinsics selected " +
295
+ "via `select` ($path, $repo, $version_id, $prev_version_id, $next_version_id, $updated_at, " +
296
+ "$author, $body, $content_hash, $semantic_score); any other keys are `select`-projected frontmatter. A key " +
297
+ 'appears only when selected (and, for frontmatter, present). Default `select` is ["$path"], ' +
298
+ 'so a hit is `{ "$path": "…" }` only unless you ask for more.',
299
+ // Which keys appear depends entirely on `select`, so none are required and
300
+ // both intrinsics and bare frontmatter keys ride additionalProperties.
301
+ additionalProperties: true,
302
+ };
303
+ const VERSION_REF_SCHEMA = {
304
+ type: "object",
305
+ description: "A change-feed pointer (sync/history plan §3.3). Consumers fetch bodies via docs_get_version " +
306
+ "only when needed; content_hash lets them skip no-op materializations.",
307
+ properties: {
308
+ version_id: { type: "string", description: "Opaque id of this version." },
309
+ prev_version_id: {
310
+ anyOf: [{ type: "string" }, { type: "null" }],
311
+ description: "Prior version id, or null for a create.",
312
+ },
313
+ repo: { type: "string", description: "Repo slug." },
314
+ path: { type: "string", description: "This version's path." },
315
+ prev_path: {
316
+ anyOf: [{ type: "string" }, { type: "null" }],
317
+ description: "Path of the prior version (both ends of a move/delete), or null.",
318
+ },
319
+ content_hash: { type: "string", description: "SHA-256 (bare hex) of canonical content." },
320
+ op: {
321
+ type: "string",
322
+ enum: ["create", "update", "move", "delete"],
323
+ description: "Server-derived operation.",
324
+ },
325
+ created_at: { type: "string", description: "ISO-8601 UTC timestamp." },
326
+ },
327
+ required: [
328
+ "version_id",
329
+ "prev_version_id",
330
+ "repo",
331
+ "path",
332
+ "prev_path",
333
+ "content_hash",
334
+ "op",
335
+ "created_at",
336
+ ],
337
+ };
338
+ const GRAPH_DOCUMENT_SCHEMA = {
339
+ type: "object",
340
+ description: "A reached document. `$`-keys are system intrinsics; any other keys are `select`-projected " +
341
+ "frontmatter (a missing key is simply absent).",
342
+ properties: {
343
+ $path: { type: "string", description: "The document's current path." },
344
+ $degrees: {
345
+ type: "integer",
346
+ description: "Call-relative: minimum hops from the nearest root under THIS call's direction/fields/" +
347
+ "filter/scope. Not a stable property — do not persist it across calls. Roots are 0.",
348
+ },
349
+ $links: {
350
+ type: "integer",
351
+ description: "Count of distinct scope-visible documents this document links to — its true visible " +
352
+ "out-degree, independent of this call's filter/fields/degrees (stable across calls). " +
353
+ "Useful for ranking frontier docs (hub vs. leaf).",
354
+ },
355
+ $backlinks: {
356
+ type: "integer",
357
+ description: "Count of distinct scope-visible documents linking TO this document.",
358
+ },
359
+ },
360
+ required: ["$path", "$degrees", "$links", "$backlinks"],
361
+ // `select`-projected frontmatter keys appear as bare keys alongside the $-intrinsics.
362
+ additionalProperties: true,
363
+ };
364
+ const GRAPH_LINK_SCHEMA = {
365
+ type: "object",
366
+ description: "An induced link: a distinct (source, target, field) triple where both endpoints appear in " +
367
+ "`documents`. `field` is the relationship type (`$body` = an untyped body link). No " +
368
+ "occurrence count — a link is a pure statement of relationship.",
369
+ properties: {
370
+ source: { type: "string", description: "Linking document's path." },
371
+ target: { type: "string", description: "Linked-to document's path." },
372
+ field: { type: "string", description: "Relationship type; `$body` for body links." },
373
+ },
374
+ required: ["source", "target", "field"],
375
+ };
376
+ const GRAPH_RESULT_SCHEMA = {
377
+ type: "object",
378
+ description: "A graph neighborhood: documents and the links between them (docs/graph-plan.md).",
379
+ properties: {
380
+ documents: {
381
+ type: "array",
382
+ items: GRAPH_DOCUMENT_SCHEMA,
383
+ description: "Reached documents, ordered by ($degrees, $path).",
384
+ },
385
+ links: {
386
+ type: "array",
387
+ items: GRAPH_LINK_SCHEMA,
388
+ description: "Induced distinct links over the returned documents, ordered (source,target,field).",
389
+ },
390
+ frontier: {
391
+ type: "array",
392
+ items: { type: "string" },
393
+ description: "Paths of returned documents whose links were NOT fully enumerated (cut by the degrees " +
394
+ "cap or by max_documents). The continuation contract: there are no cursors — re-root a " +
395
+ "follow-up `graph` call at chosen frontier paths and union the results.",
396
+ },
397
+ complete_degrees: {
398
+ type: "integer",
399
+ description: "Largest d such that every effective-graph document within d hops of a root is present. " +
400
+ "When truncated is false this equals the requested degrees; when max_documents cut a ring " +
401
+ 'it makes the partial result precise ("the 2-hop ball is exhaustive, the 3-ring is sampled").',
402
+ },
403
+ truncated: {
404
+ type: "boolean",
405
+ description: "True iff max_documents (or the server links ceiling) elided anything.",
406
+ },
407
+ },
408
+ required: ["documents", "links", "frontier", "complete_degrees", "truncated"],
409
+ };
410
+ // -----------------------------------------------------------------------------
411
+ // Tool definitions
412
+ // -----------------------------------------------------------------------------
413
+ export const TOOL_REGISTRY = [
414
+ // ---- repos ----
415
+ {
416
+ name: "repos_list",
417
+ description: "List repos the caller can address. Deleted (system-namespaced) repos are omitted unless `include_system` is true.",
418
+ inputSchema: {
419
+ type: "object",
420
+ properties: {
421
+ include_system: {
422
+ type: "boolean",
423
+ description: "Include system-namespaced (deleted) repos.",
424
+ },
425
+ },
426
+ },
427
+ outputSchema: listResultSchema(REPO_SCHEMA, "Repos the caller can address."),
428
+ handler: async (kernel, ctx, args) => {
429
+ const result = await kernel.repos.list(ctx, {
430
+ include_system: argBoolOpt(args, "include_system") ?? false,
431
+ });
432
+ return { structured: wrapList(result), text: renderRepoList(result) };
433
+ },
434
+ },
435
+ {
436
+ name: "repos_get",
437
+ description: "Fetch a repo by slug. Missing or out-of-scope slugs raise not-found.",
438
+ inputSchema: {
439
+ type: "object",
440
+ properties: { repo: { type: "string", description: "Repo slug." } },
441
+ required: ["repo"],
442
+ },
443
+ outputSchema: REPO_SCHEMA,
444
+ handler: async (kernel, ctx, args) => {
445
+ const result = await kernel.repos.get(ctx, argStr(args, "repo"));
446
+ return { structured: result, text: renderJson(result) };
447
+ },
448
+ },
449
+ {
450
+ name: "repos_create",
451
+ description: "Create a new repo. Fails with slug_taken if the slug is already in use.",
452
+ inputSchema: {
453
+ type: "object",
454
+ properties: { repo: { type: "string", description: "New repo slug." } },
455
+ required: ["repo"],
456
+ },
457
+ outputSchema: REPO_SCHEMA,
458
+ handler: async (kernel, ctx, args) => {
459
+ const result = await kernel.repos.create(ctx, argStr(args, "repo"));
460
+ return { structured: result, text: `created ${result.repo}` };
461
+ },
462
+ },
463
+ {
464
+ name: "repos_rename",
465
+ description: "Rename a repo slug (recasing the same repo is allowed). Fails with slug_taken on collision.",
466
+ inputSchema: {
467
+ type: "object",
468
+ properties: {
469
+ repo: { type: "string" },
470
+ new_repo: { type: "string" },
471
+ },
472
+ required: ["repo", "new_repo"],
473
+ },
474
+ outputSchema: REPO_SCHEMA,
475
+ handler: async (kernel, ctx, args) => {
476
+ const result = await kernel.repos.rename(ctx, argStr(args, "repo"), argStr(args, "new_repo"));
477
+ return { structured: result, text: `renamed to ${result.repo}` };
478
+ },
479
+ },
480
+ {
481
+ name: "repos_delete",
482
+ description: "Soft-delete a repo by renaming its slug into the system namespace (`:deleted-…`). Idempotent if already system-namespaced.",
483
+ inputSchema: {
484
+ type: "object",
485
+ properties: { repo: { type: "string" } },
486
+ required: ["repo"],
487
+ },
488
+ outputSchema: REPO_SCHEMA,
489
+ handler: async (kernel, ctx, args) => {
490
+ const result = await kernel.repos.delete(ctx, argStr(args, "repo"));
491
+ return { structured: result, text: `deleted (now ${result.repo})` };
492
+ },
493
+ },
494
+ {
495
+ name: "repos_set_path_config",
496
+ description: "Set or clear a repo's path-config override (disallowed chars, system/hidden sigils). Pass `config: null` to clear. Existing live paths that violate the new config are returned as warnings, not rejected.",
497
+ inputSchema: {
498
+ type: "object",
499
+ properties: {
500
+ repo: { type: "string" },
501
+ config: {
502
+ oneOf: [
503
+ {
504
+ type: "object",
505
+ additionalProperties: true,
506
+ description: "PathConfig override (disallowed_chars, system_sigils, hidden_sigils).",
507
+ },
508
+ { type: "null" },
509
+ ],
510
+ },
511
+ },
512
+ required: ["repo", "config"],
513
+ },
514
+ outputSchema: SET_PATH_CONFIG_RESULT_SCHEMA,
515
+ handler: async (kernel, ctx, args) => {
516
+ const cfg = args.config;
517
+ const result = await kernel.repos.set_path_config(ctx, argStr(args, "repo"), cfg);
518
+ return { structured: result, text: `warnings: ${result.warnings.length}` };
519
+ },
520
+ },
521
+ {
522
+ name: "repos_set_link_config",
523
+ description: "Set or clear a repo's link-extraction config (body / frontmatter syntax profiles and resolution) and re-extract the whole repo under the new config. Pass `config: null` to clear.",
524
+ inputSchema: {
525
+ type: "object",
526
+ properties: {
527
+ repo: { type: "string" },
528
+ config: {
529
+ oneOf: [
530
+ {
531
+ type: "object",
532
+ additionalProperties: true,
533
+ description: "LinkConfig override — body / frontmatter syntax profiles / resolution.",
534
+ },
535
+ { type: "null" },
536
+ ],
537
+ },
538
+ },
539
+ required: ["repo", "config"],
540
+ },
541
+ outputSchema: SET_LINK_CONFIG_RESULT_SCHEMA,
542
+ handler: async (kernel, ctx, args) => {
543
+ const cfg = args.config;
544
+ const result = await kernel.repos.set_link_config(ctx, argStr(args, "repo"), cfg);
545
+ return {
546
+ structured: result,
547
+ text: `link_config updated; reindexed ${result.reindexed.documents} doc(s), ${result.reindexed.edges} edge(s)`,
548
+ };
549
+ },
550
+ },
551
+ // ---- docs ----
552
+ {
553
+ name: "docs_get",
554
+ description: "Read the current version of a document at (repo, path) — the way to recover a full document after `query`. " +
555
+ EXACT_PATH_DOC +
556
+ " Returned `frontmatter_raw` has `$version: <version_id>` then `$content_hash: <sha256>` appended so a subsequent `docs_put` can reuse `$version` as `prev_version_id` (unless `raw: true`). A missing path raises doc_not_found (unlike `query`, which omits unmatched paths). For several paths at once, use `docs_get_many`.",
557
+ inputSchema: {
558
+ type: "object",
559
+ properties: {
560
+ repo: { type: "string" },
561
+ path: { type: "string", description: EXACT_PATH_DOC },
562
+ raw: {
563
+ type: "boolean",
564
+ description: "Suppress server-injected `$*` system properties in frontmatter_raw.",
565
+ },
566
+ },
567
+ required: ["repo", "path"],
568
+ },
569
+ outputSchema: VERSION_SCHEMA,
570
+ handler: async (kernel, ctx, args) => {
571
+ const v = await kernel.docs.get(ctx, argStr(args, "repo"), argStr(args, "path"));
572
+ const out = withInjectedSystemProps(v, args.raw === true);
573
+ return { structured: out, text: renderVersion(out) };
574
+ },
575
+ },
576
+ {
577
+ name: "docs_get_many",
578
+ description: "Read the current versions of several documents at once — the batch recover path after `query`. " +
579
+ "Returns `{ items, errors }`: found docs are full `Version`s (same injection as `docs_get` unless " +
580
+ "`raw: true`); per-path misses land in `errors` without failing the call. Duplicate paths are " +
581
+ "collapsed (first-seen). Max 50 unique paths.",
582
+ inputSchema: {
583
+ type: "object",
584
+ properties: {
585
+ repo: { type: "string" },
586
+ paths: {
587
+ type: "array",
588
+ items: { type: "string" },
589
+ description: `Non-empty list of document paths to fetch. ${EXACT_PATH_DOC}`,
590
+ },
591
+ raw: {
592
+ type: "boolean",
593
+ description: "Suppress server-injected `$*` system properties in frontmatter_raw.",
594
+ },
595
+ },
596
+ required: ["repo", "paths"],
597
+ },
598
+ outputSchema: DOC_GET_MANY_RESULT_SCHEMA,
599
+ handler: async (kernel, ctx, args) => {
600
+ const raw = args.raw === true;
601
+ const result = await kernel.docs.get_many(ctx, argStr(args, "repo"), argStrArray(args, "paths"));
602
+ const items = result.items.map((v) => withInjectedSystemProps(v, raw));
603
+ const structured = { items, errors: result.errors };
604
+ return { structured, text: renderDocGetManyText(items, result.errors) };
605
+ },
606
+ },
607
+ {
608
+ name: "docs_get_version",
609
+ description: "Read a specific version by opaque `version_id` (current or historical). Same `$version` / `$content_hash` injection into `frontmatter_raw` as `docs_get` unless `raw: true`.",
610
+ inputSchema: {
611
+ type: "object",
612
+ properties: {
613
+ repo: { type: "string" },
614
+ version_id: { type: "string" },
615
+ raw: {
616
+ type: "boolean",
617
+ description: "Suppress server-injected `$*` system properties in frontmatter_raw.",
618
+ },
619
+ },
620
+ required: ["repo", "version_id"],
621
+ },
622
+ outputSchema: VERSION_SCHEMA,
623
+ handler: async (kernel, ctx, args) => {
624
+ const v = await kernel.docs.get_version(ctx, argStr(args, "repo"), argStr(args, "version_id"));
625
+ const out = withInjectedSystemProps(v, args.raw === true);
626
+ return { structured: out, text: renderVersion(out) };
627
+ },
628
+ },
629
+ {
630
+ name: "history_list",
631
+ description: "Scoped, document-spanning version history. Where `docs_history` lists one literal path, this takes a `path` GLOB (omitted = the whole repo) and interleaves matching documents' versions by version-log position. `ever: false` (default) anchors on the LIVE set — history of what lives at the glob now; `ever: true` also includes documents that once matched but moved away or were deleted. `since`/`until` are opaque version-id bounds; `order` is desc (newest-first) by default. A single literal `path` reproduces `docs_history`.",
632
+ inputSchema: {
633
+ type: "object",
634
+ properties: {
635
+ repo: { type: "string" },
636
+ path: { type: "string", description: "Gitignore-style glob; omit for the whole repo." },
637
+ ever: {
638
+ type: "boolean",
639
+ description: "Include documents that ever matched (moved-away / deleted). Default false.",
640
+ },
641
+ since: { type: "string", description: "Exclusive lower version-id bound." },
642
+ until: { type: "string", description: "Inclusive upper version-id bound." },
643
+ order: {
644
+ type: "string",
645
+ enum: ["asc", "desc"],
646
+ description: "Default desc (newest-first).",
647
+ },
648
+ limit: { type: "integer", minimum: 1 },
649
+ scope: {
650
+ type: "array",
651
+ description: "Read-visibility claims (ScopeClaim[]); the X-Mrplex-Scope header wins.",
652
+ items: { type: "object", additionalProperties: true },
653
+ },
654
+ },
655
+ required: ["repo"],
656
+ },
657
+ outputSchema: listResultSchema(VERSION_SCHEMA, "Matching versions, ordered by version-log position."),
658
+ handler: async (kernel, ctx, args) => {
659
+ const order = argStrOpt(args, "order");
660
+ const rows = await kernel.history.list(queryCtx(ctx, args), {
661
+ repo: argStr(args, "repo"),
662
+ path: argStrOpt(args, "path"),
663
+ ever: argBoolOpt(args, "ever"),
664
+ since: argStrOpt(args, "since"),
665
+ until: argStrOpt(args, "until"),
666
+ order: order === "asc" || order === "desc" ? order : undefined,
667
+ limit: argIntOpt(args, "limit"),
668
+ });
669
+ return { structured: wrapList(rows), text: renderVersionList(rows) };
670
+ },
671
+ },
672
+ {
673
+ name: "docs_history",
674
+ description: "Deprecated: use `history_list` (a literal `path` reproduces this). List versions of one document newest-first.",
675
+ inputSchema: {
676
+ type: "object",
677
+ properties: {
678
+ repo: { type: "string" },
679
+ path: { type: "string", description: EXACT_PATH_DOC },
680
+ limit: { type: "integer", minimum: 1 },
681
+ before: { type: "string", description: "ISO-8601 UTC" },
682
+ },
683
+ required: ["repo", "path"],
684
+ },
685
+ outputSchema: listResultSchema(VERSION_SCHEMA, "Versions newest-first."),
686
+ handler: async (kernel, ctx, args) => {
687
+ const rows = await kernel.docs.history(ctx, argStr(args, "repo"), argStr(args, "path"), {
688
+ limit: argIntOpt(args, "limit"),
689
+ before: argStrOpt(args, "before"),
690
+ });
691
+ return { structured: wrapList(rows), text: renderVersionList(rows) };
692
+ },
693
+ },
694
+ {
695
+ name: "docs_diff",
696
+ description: `Unified diff between two versions of the document at (repo, path). ${EXACT_PATH_DOC} Both versions must belong to that document — otherwise version_not_in_document.`,
697
+ inputSchema: {
698
+ type: "object",
699
+ properties: {
700
+ repo: { type: "string" },
701
+ path: { type: "string", description: EXACT_PATH_DOC },
702
+ from: { type: "string", description: "source version id" },
703
+ to: { type: "string", description: "target version id" },
704
+ },
705
+ required: ["repo", "path", "from", "to"],
706
+ },
707
+ outputSchema: DIFF_SCHEMA,
708
+ handler: async (kernel, ctx, args) => {
709
+ const d = await kernel.docs.diff(ctx, argStr(args, "repo"), argStr(args, "path"), argStr(args, "from"), argStr(args, "to"));
710
+ return { structured: d, text: d.patch };
711
+ },
712
+ },
713
+ {
714
+ name: "docs_create",
715
+ description: `Create a new document at (repo, path). ${EXACT_PATH_DOC} Fails with create_conflict if the path is occupied. Provide exactly one of \`frontmatter\` (JSON map) or \`frontmatter_raw\` (verbatim YAML).`,
716
+ inputSchema: {
717
+ type: "object",
718
+ properties: {
719
+ repo: { type: "string" },
720
+ path: { type: "string", description: EXACT_PATH_DOC },
721
+ body: { type: "string" },
722
+ frontmatter: { type: "object", additionalProperties: true },
723
+ frontmatter_raw: { type: "string" },
724
+ author: {
725
+ type: "string",
726
+ description: "Opaque author string. The X-Mrplex-Author header, if present, wins.",
727
+ },
728
+ },
729
+ required: ["repo", "path", "body"],
730
+ },
731
+ outputSchema: VERSION_SCHEMA,
732
+ handler: async (kernel, ctx, args) => {
733
+ const v = await kernel.docs.create(writeCtx(ctx, args), argStr(args, "repo"), argStr(args, "path"), {
734
+ frontmatter: args.frontmatter,
735
+ frontmatter_raw: argStrOpt(args, "frontmatter_raw"),
736
+ body: argStr(args, "body"),
737
+ });
738
+ return { structured: v, text: renderVersion(v) };
739
+ },
740
+ },
741
+ {
742
+ name: "docs_put",
743
+ description: "Update or move a document (optimistic concurrency). `path` may differ from prev's path (= move). Exactly one of `frontmatter` | `frontmatter_raw` if changing frontmatter; both may be omitted to keep prev's. `prev_version_id` may be omitted if `frontmatter_raw` embeds `$version: <id>` from a prior `docs_get`. Conflicts: stale_prev (someone else wrote first — re-read and retry), path_taken (move onto an occupied path).",
744
+ inputSchema: {
745
+ type: "object",
746
+ properties: {
747
+ repo: { type: "string" },
748
+ path: {
749
+ type: "string",
750
+ description: `Destination path (may differ from prev's path). ${EXACT_PATH_DOC}`,
751
+ },
752
+ prev_version_id: {
753
+ type: "string",
754
+ description: "Optional if `frontmatter_raw` contains `$version: <id>`; explicit value wins.",
755
+ },
756
+ body: { type: "string" },
757
+ frontmatter: { type: "object", additionalProperties: true },
758
+ frontmatter_raw: { type: "string" },
759
+ author: {
760
+ type: "string",
761
+ description: "Opaque author string. The X-Mrplex-Author header, if present, wins.",
762
+ },
763
+ },
764
+ required: ["repo", "path"],
765
+ },
766
+ outputSchema: VERSION_SCHEMA,
767
+ handler: async (kernel, ctx, args) => {
768
+ const input = {};
769
+ if (args.frontmatter !== undefined)
770
+ input.frontmatter = args.frontmatter;
771
+ if (typeof args.frontmatter_raw === "string")
772
+ input.frontmatter_raw = args.frontmatter_raw;
773
+ if (typeof args.body === "string")
774
+ input.body = args.body;
775
+ // Peel `$version` (and any other `$*`) out of raw frontmatter first — it
776
+ // supplies the prev_version_id fallback and must never reach storage.
777
+ let embeddedVersion;
778
+ if (input.frontmatter_raw !== undefined) {
779
+ const { raw: cleaned, props } = extractSystemProperties(input.frontmatter_raw);
780
+ input.frontmatter_raw = cleaned;
781
+ if (typeof props.version === "string" && props.version.length > 0) {
782
+ embeddedVersion = props.version;
783
+ }
784
+ }
785
+ const prev = argStrOpt(args, "prev_version_id") ?? embeddedVersion;
786
+ if (prev === undefined) {
787
+ throw new Error("prev_version_id is required (either as an argument or as `$version` in frontmatter_raw)");
788
+ }
789
+ const v = await kernel.docs.put(writeCtx(ctx, args), argStr(args, "repo"), prev, argStr(args, "path"), input);
790
+ return { structured: v, text: renderVersion(v) };
791
+ },
792
+ },
793
+ {
794
+ name: "docs_delete",
795
+ description: "Delete a document — moves it to `:deleted/…` (system namespace). Requires `prev_version_id` of the current version. Idempotent if already deleted. Conflicts with stale_prev if someone else wrote first.",
796
+ inputSchema: {
797
+ type: "object",
798
+ properties: {
799
+ repo: { type: "string" },
800
+ prev_version_id: { type: "string" },
801
+ author: {
802
+ type: "string",
803
+ description: "Opaque author string. The X-Mrplex-Author header, if present, wins.",
804
+ },
805
+ },
806
+ required: ["repo", "prev_version_id"],
807
+ },
808
+ outputSchema: VERSION_SCHEMA,
809
+ handler: async (kernel, ctx, args) => {
810
+ const v = await kernel.docs.delete(writeCtx(ctx, args), argStr(args, "repo"), argStr(args, "prev_version_id"));
811
+ return { structured: v, text: renderVersion(v) };
812
+ },
813
+ },
814
+ // ---- links (§11.2) ----
815
+ {
816
+ name: "links_backfill",
817
+ description: "Rebuild the link index for a repo (after a link-config change, or to repair a missing index).",
818
+ inputSchema: {
819
+ type: "object",
820
+ properties: { repo: { type: "string" } },
821
+ required: ["repo"],
822
+ },
823
+ outputSchema: LINKS_BACKFILL_SCHEMA,
824
+ handler: async (kernel, ctx, args) => {
825
+ const r = await kernel.links.backfill(ctx, argStr(args, "repo"));
826
+ return {
827
+ structured: r,
828
+ text: `backfill ${argStr(args, "repo")}: documents=${r.documents} edges=${r.edges}`,
829
+ };
830
+ },
831
+ },
832
+ {
833
+ name: "links_stale",
834
+ description: "List live docs whose written link text is stale vs. the target's current path (e.g. after a rename). Each row is (source_path, written, current).",
835
+ inputSchema: {
836
+ type: "object",
837
+ properties: { repo: { type: "string" } },
838
+ required: ["repo"],
839
+ },
840
+ outputSchema: listResultSchema(STALE_LINK_SCHEMA, "Stale links in live docs."),
841
+ handler: async (kernel, ctx, args) => {
842
+ const rows = await kernel.links.stale(ctx, argStr(args, "repo"));
843
+ const text = rows.length
844
+ ? rows.map((r) => `${r.source_path}: "${r.written}" → "${r.current}"`).join("\n")
845
+ : "no stale links";
846
+ return { structured: wrapList(rows), text };
847
+ },
848
+ },
849
+ {
850
+ name: "links_repair",
851
+ description: "Rewrite stale link text in place via optimistic `docs_put`. `dry_run: true` plans only (no writes). Per-document conflicts are skipped, not fatal.",
852
+ inputSchema: {
853
+ type: "object",
854
+ properties: { repo: { type: "string" }, dry_run: { type: "boolean" } },
855
+ required: ["repo"],
856
+ },
857
+ outputSchema: LINKS_REPAIR_SCHEMA,
858
+ handler: async (kernel, ctx, args) => {
859
+ const r = await kernel.links.repair(ctx, argStr(args, "repo"), {
860
+ dry_run: argBoolOpt(args, "dry_run") ?? false,
861
+ });
862
+ const text = `${r.dry_run ? "[dry-run] " : ""}repaired=${r.repaired.length} skipped=${r.skipped.length}`;
863
+ return { structured: r, text };
864
+ },
865
+ },
866
+ // ---- query ----
867
+ {
868
+ name: "query",
869
+ description: "Search current documents (history is not searched). Returns lean projected hits, NOT full " +
870
+ 'documents: default `select` is ["$path"], so each hit is only `{ "$path": "…" }` — no ' +
871
+ "body, no frontmatter, no version id. Pass `select` to project more (`$body`, `$repo`, " +
872
+ "`$version_id`, `$content_hash`, `$updated_at`, `$author`, `$prev_version_id`, " +
873
+ "`$next_version_id`, or bare frontmatter keys like `title`). Include `$repo` when querying " +
874
+ "more than one repo. To recover whole documents, call `docs_get` (one path) or `docs_get_many` " +
875
+ "(batch). Unmatched paths are " +
876
+ "omitted, not errors. Default `limit` is 50. " +
877
+ "Three composable modes that intersect (AND) when combined: `filter` (CEL over frontmatter " +
878
+ "and $-intrinsics), `text` (full-text over bodies), `semantic` (embedding similarity — a " +
879
+ 'natural-language string, e.g. "tiered SaaS pricing"). Ordered by semantic score when ' +
880
+ "`semantic` is present, else text relevance, else last-update time descending. When `semantic` " +
881
+ "is active, add `$semantic_score` to `select` to project cosine similarity (1 = identical, " +
882
+ "-1 = opposite); result order is the final rank. Requires an embed hook; else " +
883
+ "`semantic_unavailable`. Filter examples: " +
884
+ `status == "published" && "pricing" in list(tags)` +
885
+ " (list() matches scalar-or-list frontmatter uniformly) — " +
886
+ `$path.startsWith("guides/")` +
887
+ " ($-intrinsics: $path, $updated_at, $body, $content_hash) — " +
888
+ `$in("moc/**") && !$in("moc/contractors.md")` +
889
+ " (link-graph membership) — " +
890
+ "$links().size() == 0" +
891
+ " (leaf docs) — " +
892
+ `$backlinks().exists(d, d.status == "draft")` +
893
+ ". Call the `query_syntax` tool for the full filter-language reference.",
894
+ inputSchema: {
895
+ type: "object",
896
+ properties: {
897
+ repo: {
898
+ oneOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
899
+ description: 'Repo slug, gitignore-style glob (e.g. "team-*"), or a list of either. ' +
900
+ "Omitted = every repo the caller can see.",
901
+ },
902
+ filter: {
903
+ type: "string",
904
+ description: "CEL boolean expression. Bare identifiers are frontmatter keys (a missing key never " +
905
+ "matches); `$path` / `$updated_at` (ISO-8601 UTC) / `$body` / `$content_hash` are " +
906
+ "document intrinsics; " +
907
+ '`"x" in list(field)` handles scalar-or-list frontmatter; `$in(glob)` / `$has(glob)` ' +
908
+ "/ `$backlinks()` / `$links()` query the link graph. String functions: contains, " +
909
+ "startsWith, endsWith, matches, size. Full reference: the `query_syntax` tool.",
910
+ },
911
+ text: {
912
+ type: "string",
913
+ description: "Full-text search over document bodies. Portable syntax: space-separated terms " +
914
+ '(implicit AND) and "quoted phrases"; other operators are storage-backend-specific.',
915
+ },
916
+ semantic: {
917
+ type: "string",
918
+ description: 'Semantic search via embeddings — a natural-language query, e.g. "tiered SaaS pricing". ' +
919
+ "Composes with filter and text (AND). Requires an embed hook on the server; else " +
920
+ "semantic_unavailable. Add $semantic_score to select to project cosine similarity per " +
921
+ "hit (1 = identical, -1 = opposite). See query_syntax for the full semantic mode " +
922
+ "reference.",
923
+ },
924
+ limit: { type: "integer", minimum: 0, description: "Max results (default 50)." },
925
+ include_hidden: {
926
+ type: "boolean",
927
+ description: "Include docs under hidden path segments (e.g. `.drafts/…`).",
928
+ },
929
+ include_system: {
930
+ type: "boolean",
931
+ description: "Include docs under system path segments (e.g. `:deleted/…`) — how you browse " +
932
+ "trash to find documents to restore.",
933
+ },
934
+ select: {
935
+ type: "array",
936
+ items: { type: "string" },
937
+ description: 'Fields to project onto each hit. Default ["$path"] — that is ALL you get unless you ' +
938
+ "pass this. Bare keys name frontmatter (a missing key is simply absent); `$`-intrinsics " +
939
+ "name system fields: $path, $repo, $version_id, $prev_version_id, $next_version_id, " +
940
+ "$updated_at, $author, $body, $content_hash, $semantic_score (semantic queries only). " +
941
+ "Document bodies travel only when `$body` is " +
942
+ "selected. This is how you list cheaply; call `docs_get` or `docs_get_many` for full document(s).",
943
+ },
944
+ scope: {
945
+ type: "array",
946
+ description: "Read-visibility claims (ScopeClaim[]) narrowing what this query sees. The X-Mrplex-Scope header, if present, wins.",
947
+ items: { type: "object", additionalProperties: true },
948
+ },
949
+ },
950
+ },
951
+ outputSchema: listResultSchema(QUERY_HIT_SCHEMA, "Matching current documents, projected."),
952
+ handler: async (kernel, ctx, args) => {
953
+ const { scope: _scope, ...specArgs } = args;
954
+ const spec = specArgs;
955
+ try {
956
+ const rows = await kernel.query(queryCtx(ctx, args), spec);
957
+ return { structured: wrapList(rows), text: renderQueryHitList(rows) };
958
+ }
959
+ catch (err) {
960
+ // Teach through the error: a bad filter is the moment the caller
961
+ // most wants the language reference.
962
+ if (err instanceof KernelError && err.code === "filter_invalid") {
963
+ throw new KernelError("filter_invalid", {
964
+ ...err.data,
965
+ hint: "call the `query_syntax` tool for the full filter-language reference",
966
+ });
967
+ }
968
+ throw err;
969
+ }
970
+ },
971
+ },
972
+ // ---- graph ----
973
+ {
974
+ name: "graph",
975
+ description: "Explore how documents connect. BFS neighborhood expansion over the link graph: from a set " +
976
+ "of `roots`, expand under a `direction` lens up to `degrees` hops, returning the reached " +
977
+ "`documents` AND the `links` between them. Where `query` answers *which* documents match, " +
978
+ "`graph` answers *how* they connect. Unlike `query`, every document always includes `$path`, " +
979
+ "`$degrees`, `$links`, `$backlinks`; `select` only adds bare frontmatter keys (default " +
980
+ '["title"]). `filter` is CEL evaluated as VISIBILITY (not selection): a non-matching ' +
981
+ "document is hidden AND blocks paths through itself — plus the graph-only `$degrees` " +
982
+ 'intrinsic (min hops from the nearest root). Killer pattern: `$degrees <= 1 || type == "person"` ' +
983
+ "— expand everything one hop, but keep following person docs. Results are deterministic. " +
984
+ "Continue past the `frontier` by re-rooting a follow-up call at chosen frontier paths (no " +
985
+ "cursors). See the `query_syntax` tool for the filter language.",
986
+ inputSchema: {
987
+ type: "object",
988
+ properties: {
989
+ repo: { type: "string", description: "Repo slug (exactly one; links are repo-local)." },
990
+ roots: {
991
+ oneOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
992
+ description: "Root document(s): an exact path or gitignore-style glob, or a list of either. Every " +
993
+ "visible, filter-matching current document matching any pattern enters at $degrees 0. " +
994
+ "A glob matching nothing yields an empty result (not an error).",
995
+ },
996
+ direction: {
997
+ type: "string",
998
+ enum: ["out", "in", "both"],
999
+ description: 'Traversal lens (default "both"). "out": follow links source→target (what this doc ' +
1000
+ 'references, transitively). "in": target→source (the backlink neighborhood). "both": ' +
1001
+ "undirected (degrees-of-separation; co-citation appears at degrees 2+).",
1002
+ },
1003
+ degrees: {
1004
+ type: "integer",
1005
+ minimum: 0,
1006
+ description: "Max hops from the nearest root (default 1; 0 = roots only). Server-capped.",
1007
+ },
1008
+ fields: {
1009
+ type: "array",
1010
+ items: { type: "string" },
1011
+ description: 'Restrict BOTH traversal and output links to these relationship fields. `"$body"` is a ' +
1012
+ "valid member (untyped body links); other members are frontmatter field names.",
1013
+ },
1014
+ filter: {
1015
+ type: "string",
1016
+ description: "CEL boolean expression, same dialect as `query` (frontmatter keys, $-intrinsics, " +
1017
+ "$in/$has/$links()/$backlinks()) PLUS `$degrees` (min hops from the nearest root — " +
1018
+ "legal only here). Semantics: VISIBILITY — a non-matching document is not returned and " +
1019
+ "blocks paths through itself.",
1020
+ },
1021
+ select: {
1022
+ type: "array",
1023
+ items: { type: "string" },
1024
+ description: 'Frontmatter keys to project onto result documents as bare keys (default ["title"]). ' +
1025
+ "A missing key on a given doc is simply absent. Bare keys only (no $-intrinsics).",
1026
+ },
1027
+ max_documents: {
1028
+ type: "integer",
1029
+ minimum: 1,
1030
+ description: "Soft budget on documents (incl. roots; default 100, server hard-capped). Links are a " +
1031
+ "consequence, not budgeted. Truncation is deterministic (BFS order, $path tiebreak).",
1032
+ },
1033
+ scope: {
1034
+ type: "array",
1035
+ description: "Read-visibility claims (ScopeClaim[]) narrowing what this call sees. The X-Mrplex-Scope " +
1036
+ "header, if present, wins. An out-of-scope endpoint hides the doc, its links, and paths " +
1037
+ "through it.",
1038
+ items: { type: "object", additionalProperties: true },
1039
+ },
1040
+ },
1041
+ required: ["repo", "roots"],
1042
+ },
1043
+ outputSchema: GRAPH_RESULT_SCHEMA,
1044
+ handler: async (kernel, ctx, args) => {
1045
+ const { scope: _scope, ...specArgs } = args;
1046
+ const spec = specArgs;
1047
+ try {
1048
+ const result = await kernel.graph(queryCtx(ctx, args), spec);
1049
+ return {
1050
+ structured: result,
1051
+ text: renderGraphSummary(result),
1052
+ };
1053
+ }
1054
+ catch (err) {
1055
+ if (err instanceof KernelError && err.code === "filter_invalid") {
1056
+ throw new KernelError("filter_invalid", {
1057
+ ...err.data,
1058
+ hint: "call the `query_syntax` tool for the full filter-language reference ($degrees is graph-only)",
1059
+ });
1060
+ }
1061
+ throw err;
1062
+ }
1063
+ },
1064
+ },
1065
+ {
1066
+ name: "history_since",
1067
+ description: "The global change feed. Given an opaque cursor `after_version`, returns the longest " +
1068
+ "GAP-FREE contiguous run of change refs after it, plus `next_since` to resume. Each ref is a " +
1069
+ "lightweight pointer — `version_id`, `prev_version_id`, `repo`, `path`, `prev_path` (both ends " +
1070
+ "of a move/delete), `content_hash` (skip a fetch when you already have these bytes), a " +
1071
+ "server-derived `op` (create/update/move/delete), and `created_at`. Fetch bodies via " +
1072
+ "`docs_get_version` only when needed. Persist exactly `next_since`; feed it back to poll. A " +
1073
+ "short/empty page means caught-up or waiting on an in-flight write — just poll again. Pass " +
1074
+ '`after_version: ""` to start from the beginning of the log.',
1075
+ inputSchema: {
1076
+ type: "object",
1077
+ properties: {
1078
+ after_version: {
1079
+ type: "string",
1080
+ description: 'Opaque resume cursor. "" (empty) starts from the beginning of the log.',
1081
+ },
1082
+ repo: { type: "string", description: "Optional repo slug filter." },
1083
+ limit: {
1084
+ type: "integer",
1085
+ minimum: 1,
1086
+ description: "Max refs per page (server default applies when omitted).",
1087
+ },
1088
+ scope: {
1089
+ type: "array",
1090
+ description: "Read-visibility claims (ScopeClaim[]); the X-Mrplex-Scope header wins.",
1091
+ items: { type: "object", additionalProperties: true },
1092
+ },
1093
+ },
1094
+ required: ["after_version"],
1095
+ },
1096
+ outputSchema: {
1097
+ type: "object",
1098
+ properties: {
1099
+ refs: { type: "array", items: VERSION_REF_SCHEMA, description: "Settled change refs." },
1100
+ next_since: { type: "string", description: "Opaque cursor to resume the feed." },
1101
+ },
1102
+ required: ["refs", "next_since"],
1103
+ },
1104
+ handler: async (kernel, ctx, args) => {
1105
+ const input = {
1106
+ after_version: argStr(args, "after_version"),
1107
+ repo: argStrOpt(args, "repo"),
1108
+ limit: argIntOpt(args, "limit"),
1109
+ };
1110
+ const page = await kernel.history.since(queryCtx(ctx, args), input);
1111
+ return {
1112
+ structured: page,
1113
+ text: page.refs.map((r) => JSON.stringify(r)).join("\n"),
1114
+ };
1115
+ },
1116
+ },
1117
+ {
1118
+ name: "history_index",
1119
+ description: "Page the live document set of one repo as of a safe head R — the startup/reconciliation " +
1120
+ "enumeration a sync client runs before tailing. Returns lightweight {path, version_id, " +
1121
+ "content_hash} tuples in current-version-id order, keyset-paginated and bounded through R. " +
1122
+ "On the first call omit `through_version`; the server captures R and echoes it — pass it back " +
1123
+ "(plus `after_version` = the previous page's last version_id) on subsequent pages. System " +
1124
+ "(`:deleted/`) and hidden (`.`-prefixed) paths are excluded, as `query` defaults. The handoff " +
1125
+ "is exact: a base scan over (cursor, R] plus `history_since`(R) is gap-free, so a doc updated " +
1126
+ "mid-pagination simply arrives later on the feed.",
1127
+ inputSchema: {
1128
+ type: "object",
1129
+ properties: {
1130
+ repo: { type: "string", description: "Repo slug (the scan is per-repo)." },
1131
+ through_version: {
1132
+ type: "string",
1133
+ description: "The safe head R; omit on the first call (server captures + returns it).",
1134
+ },
1135
+ after_version: {
1136
+ type: "string",
1137
+ description: "Previous page's last version_id; omit on the first call.",
1138
+ },
1139
+ limit: {
1140
+ type: "integer",
1141
+ minimum: 1,
1142
+ description: "Max items per page (server default applies when omitted).",
1143
+ },
1144
+ scope: {
1145
+ type: "array",
1146
+ description: "Read-visibility claims (ScopeClaim[]); the X-Mrplex-Scope header wins.",
1147
+ items: { type: "object", additionalProperties: true },
1148
+ },
1149
+ },
1150
+ required: ["repo"],
1151
+ },
1152
+ outputSchema: {
1153
+ type: "object",
1154
+ properties: {
1155
+ items: {
1156
+ type: "array",
1157
+ description: "Live-set entries in current-version-id order.",
1158
+ items: {
1159
+ type: "object",
1160
+ properties: {
1161
+ path: { type: "string" },
1162
+ version_id: { type: "string" },
1163
+ content_hash: { type: "string" },
1164
+ },
1165
+ required: ["path", "version_id", "content_hash"],
1166
+ },
1167
+ },
1168
+ through_version: { type: "string", description: "The safe head R (echo on later pages)." },
1169
+ next_after_version: {
1170
+ type: "string",
1171
+ description: "Cursor for the next page; absent on the final page.",
1172
+ },
1173
+ },
1174
+ required: ["items", "through_version"],
1175
+ },
1176
+ handler: async (kernel, ctx, args) => {
1177
+ const page = await kernel.history.index(queryCtx(ctx, args), {
1178
+ repo: argStr(args, "repo"),
1179
+ through_version: argStrOpt(args, "through_version"),
1180
+ after_version: argStrOpt(args, "after_version"),
1181
+ limit: argIntOpt(args, "limit"),
1182
+ });
1183
+ return {
1184
+ structured: page,
1185
+ text: page.items.map((i) => JSON.stringify(i)).join("\n"),
1186
+ };
1187
+ },
1188
+ },
1189
+ {
1190
+ name: "query_syntax",
1191
+ description: "Reference documentation for the `query` / `graph` filter language and `query`'s result " +
1192
+ "shape: CEL syntax, $-intrinsics ($path, $updated_at, $body, $content_hash), `select` " +
1193
+ '(default ["$path"] only — not full documents), list() scalar-or-list polymorphism, ' +
1194
+ "link-graph predicates ($in, $has, $backlinks(), $links()), graph-only `$degrees`, " +
1195
+ "text-search syntax, semantic mode (`semantic` query param, `$semantic_score` in `select`). " +
1196
+ "Call this before writing a non-trivial filter, or after a filter_invalid error.",
1197
+ inputSchema: { type: "object", properties: {} },
1198
+ outputSchema: {
1199
+ type: "object",
1200
+ properties: {
1201
+ reference: {
1202
+ type: "string",
1203
+ description: "Markdown reference for the query filter language.",
1204
+ },
1205
+ },
1206
+ required: ["reference"],
1207
+ },
1208
+ handler: () => ({
1209
+ structured: { reference: QUERY_SYNTAX_DOC },
1210
+ text: QUERY_SYNTAX_DOC,
1211
+ }),
1212
+ },
1213
+ ];
1214
+ /** Convenient lookup by name. */
1215
+ export function toolByName(name) {
1216
+ return TOOL_REGISTRY.find((t) => t.name === name);
1217
+ }
1218
+ //# sourceMappingURL=tools.js.map