@bendyline/docblocks 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/README.md +76 -11
  2. package/dist/chunk-2D7Y4IR3.js +1293 -0
  3. package/dist/chunk-2D7Y4IR3.js.map +1 -0
  4. package/dist/chunk-5MYAICRE.js +957 -0
  5. package/dist/chunk-5MYAICRE.js.map +1 -0
  6. package/dist/chunk-75UUF6C4.js +802 -0
  7. package/dist/chunk-75UUF6C4.js.map +1 -0
  8. package/dist/chunk-AEHCVWU5.js +25 -0
  9. package/dist/chunk-AEHCVWU5.js.map +1 -0
  10. package/dist/chunk-AYZ4HN5M.js +20 -0
  11. package/dist/chunk-AYZ4HN5M.js.map +1 -0
  12. package/dist/chunk-EGNDADXB.js +111 -0
  13. package/dist/chunk-EGNDADXB.js.map +1 -0
  14. package/dist/chunk-EZKJZUNJ.js +13 -0
  15. package/dist/chunk-EZKJZUNJ.js.map +1 -0
  16. package/dist/chunk-FNW556FN.js +75 -0
  17. package/dist/chunk-FNW556FN.js.map +1 -0
  18. package/dist/chunk-GCPBEMMH.js +563 -0
  19. package/dist/chunk-GCPBEMMH.js.map +1 -0
  20. package/dist/chunk-J32GU3F3.js +1664 -0
  21. package/dist/chunk-J32GU3F3.js.map +1 -0
  22. package/dist/chunk-PGR7LK6D.js +1062 -0
  23. package/dist/chunk-PGR7LK6D.js.map +1 -0
  24. package/dist/chunk-RQJRQF46.js +787 -0
  25. package/dist/chunk-RQJRQF46.js.map +1 -0
  26. package/dist/{chunk-ME76RUMR.js → chunk-RVQHY3XU.js} +15 -1
  27. package/dist/chunk-RVQHY3XU.js.map +1 -0
  28. package/dist/chunk-SI2LU2RP.js +224 -0
  29. package/dist/chunk-SI2LU2RP.js.map +1 -0
  30. package/dist/chunk-W5UUUII5.js +142 -0
  31. package/dist/chunk-W5UUUII5.js.map +1 -0
  32. package/dist/document/document-session.d.ts +127 -0
  33. package/dist/document/document-session.d.ts.map +1 -0
  34. package/dist/document/document-session.js +749 -0
  35. package/dist/document/document-session.js.map +1 -0
  36. package/dist/document/filesystem-target.d.ts +17 -0
  37. package/dist/document/filesystem-target.d.ts.map +1 -0
  38. package/dist/document/filesystem-target.js +70 -0
  39. package/dist/document/filesystem-target.js.map +1 -0
  40. package/dist/document/index.d.ts +6 -0
  41. package/dist/document/index.d.ts.map +1 -0
  42. package/dist/document/index.js +4 -0
  43. package/dist/document/index.js.map +1 -0
  44. package/dist/document/recovery-journal.d.ts +125 -0
  45. package/dist/document/recovery-journal.d.ts.map +1 -0
  46. package/dist/document/recovery-journal.js +385 -0
  47. package/dist/document/recovery-journal.js.map +1 -0
  48. package/dist/document/types.d.ts +89 -0
  49. package/dist/document/types.d.ts.map +1 -0
  50. package/dist/document/types.js +9 -0
  51. package/dist/document/types.js.map +1 -0
  52. package/dist/filesystem/commit-lock.d.ts +3 -0
  53. package/dist/filesystem/commit-lock.d.ts.map +1 -0
  54. package/dist/filesystem/commit-lock.js +20 -0
  55. package/dist/filesystem/commit-lock.js.map +1 -0
  56. package/dist/filesystem/dbk-workspace.d.ts +34 -0
  57. package/dist/filesystem/dbk-workspace.d.ts.map +1 -0
  58. package/dist/filesystem/dbk-workspace.js +85 -0
  59. package/dist/filesystem/dbk-workspace.js.map +1 -0
  60. package/dist/filesystem/electron-provider-v2.d.ts +36 -0
  61. package/dist/filesystem/electron-provider-v2.d.ts.map +1 -0
  62. package/dist/filesystem/electron-provider-v2.js +503 -0
  63. package/dist/filesystem/electron-provider-v2.js.map +1 -0
  64. package/dist/filesystem/electron-provider.d.ts +6 -3
  65. package/dist/filesystem/electron-provider.d.ts.map +1 -1
  66. package/dist/filesystem/electron-provider.js +18 -13
  67. package/dist/filesystem/electron-provider.js.map +1 -1
  68. package/dist/filesystem/electron.d.ts +4 -0
  69. package/dist/filesystem/electron.d.ts.map +1 -0
  70. package/dist/filesystem/electron.js +4 -0
  71. package/dist/filesystem/electron.js.map +1 -0
  72. package/dist/filesystem/filesystem-content-container.d.ts +1 -1
  73. package/dist/filesystem/filesystem-content-container.d.ts.map +1 -1
  74. package/dist/filesystem/filesystem-content-container.js +50 -13
  75. package/dist/filesystem/filesystem-content-container.js.map +1 -1
  76. package/dist/filesystem/fs-error.d.ts +46 -0
  77. package/dist/filesystem/fs-error.d.ts.map +1 -0
  78. package/dist/filesystem/fs-error.js +170 -0
  79. package/dist/filesystem/fs-error.js.map +1 -0
  80. package/dist/filesystem/index.d.ts +15 -1
  81. package/dist/filesystem/index.d.ts.map +1 -1
  82. package/dist/filesystem/index.js +13 -0
  83. package/dist/filesystem/index.js.map +1 -1
  84. package/dist/filesystem/indexeddb-content-container.d.ts +3 -1
  85. package/dist/filesystem/indexeddb-content-container.d.ts.map +1 -1
  86. package/dist/filesystem/indexeddb-content-container.js +18 -11
  87. package/dist/filesystem/indexeddb-content-container.js.map +1 -1
  88. package/dist/filesystem/indexeddb-provider-v2.d.ts +96 -0
  89. package/dist/filesystem/indexeddb-provider-v2.d.ts.map +1 -0
  90. package/dist/filesystem/indexeddb-provider-v2.js +1241 -0
  91. package/dist/filesystem/indexeddb-provider-v2.js.map +1 -0
  92. package/dist/filesystem/indexeddb-provider.d.ts +21 -14
  93. package/dist/filesystem/indexeddb-provider.d.ts.map +1 -1
  94. package/dist/filesystem/indexeddb-provider.js +111 -261
  95. package/dist/filesystem/indexeddb-provider.js.map +1 -1
  96. package/dist/filesystem/indexeddb-store.d.ts +34 -0
  97. package/dist/filesystem/indexeddb-store.d.ts.map +1 -0
  98. package/dist/filesystem/indexeddb-store.js +165 -0
  99. package/dist/filesystem/indexeddb-store.js.map +1 -0
  100. package/dist/filesystem/indexeddb.d.ts +5 -0
  101. package/dist/filesystem/indexeddb.d.ts.map +1 -0
  102. package/dist/filesystem/indexeddb.js +5 -0
  103. package/dist/filesystem/indexeddb.js.map +1 -0
  104. package/dist/filesystem/legacy-v2-adapter.d.ts +62 -0
  105. package/dist/filesystem/legacy-v2-adapter.d.ts.map +1 -0
  106. package/dist/filesystem/legacy-v2-adapter.js +499 -0
  107. package/dist/filesystem/legacy-v2-adapter.js.map +1 -0
  108. package/dist/filesystem/memory-provider-v2.d.ts +92 -0
  109. package/dist/filesystem/memory-provider-v2.d.ts.map +1 -0
  110. package/dist/filesystem/memory-provider-v2.js +615 -0
  111. package/dist/filesystem/memory-provider-v2.js.map +1 -0
  112. package/dist/filesystem/memory-provider.d.ts +54 -0
  113. package/dist/filesystem/memory-provider.d.ts.map +1 -0
  114. package/dist/filesystem/memory-provider.js +132 -0
  115. package/dist/filesystem/memory-provider.js.map +1 -0
  116. package/dist/filesystem/memory.d.ts +4 -0
  117. package/dist/filesystem/memory.d.ts.map +1 -0
  118. package/dist/filesystem/memory.js +4 -0
  119. package/dist/filesystem/memory.js.map +1 -0
  120. package/dist/filesystem/move-entry.d.ts +9 -0
  121. package/dist/filesystem/move-entry.d.ts.map +1 -0
  122. package/dist/filesystem/move-entry.js +103 -0
  123. package/dist/filesystem/move-entry.js.map +1 -0
  124. package/dist/filesystem/move-error.d.ts +53 -0
  125. package/dist/filesystem/move-error.d.ts.map +1 -0
  126. package/dist/filesystem/move-error.js +99 -0
  127. package/dist/filesystem/move-error.js.map +1 -0
  128. package/dist/filesystem/native-provider-v2.d.ts +75 -0
  129. package/dist/filesystem/native-provider-v2.d.ts.map +1 -0
  130. package/dist/filesystem/native-provider-v2.js +849 -0
  131. package/dist/filesystem/native-provider-v2.js.map +1 -0
  132. package/dist/filesystem/native-provider.d.ts +9 -1
  133. package/dist/filesystem/native-provider.d.ts.map +1 -1
  134. package/dist/filesystem/native-provider.js +172 -37
  135. package/dist/filesystem/native-provider.js.map +1 -1
  136. package/dist/filesystem/native.d.ts +4 -0
  137. package/dist/filesystem/native.d.ts.map +1 -0
  138. package/dist/filesystem/native.js +4 -0
  139. package/dist/filesystem/native.js.map +1 -0
  140. package/dist/filesystem/types.d.ts +37 -4
  141. package/dist/filesystem/types.d.ts.map +1 -1
  142. package/dist/filesystem/types.js +11 -3
  143. package/dist/filesystem/types.js.map +1 -1
  144. package/dist/filesystem/v2.d.ts +172 -0
  145. package/dist/filesystem/v2.d.ts.map +1 -0
  146. package/dist/filesystem/v2.js +16 -0
  147. package/dist/filesystem/v2.js.map +1 -0
  148. package/dist/filesystem/workspace-path.d.ts +21 -0
  149. package/dist/filesystem/workspace-path.d.ts.map +1 -0
  150. package/dist/filesystem/workspace-path.js +80 -0
  151. package/dist/filesystem/workspace-path.js.map +1 -0
  152. package/dist/host/filesystem-v2.d.ts +45 -0
  153. package/dist/host/filesystem-v2.d.ts.map +1 -0
  154. package/dist/host/filesystem-v2.js +13 -0
  155. package/dist/host/filesystem-v2.js.map +1 -0
  156. package/dist/host/git.d.ts +200 -0
  157. package/dist/host/git.d.ts.map +1 -0
  158. package/dist/host/git.js +15 -0
  159. package/dist/host/git.js.map +1 -0
  160. package/dist/host/index.d.ts +4 -1
  161. package/dist/host/index.d.ts.map +1 -1
  162. package/dist/host/index.js +2 -0
  163. package/dist/host/index.js.map +1 -1
  164. package/dist/host/types.d.ts +114 -31
  165. package/dist/host/types.d.ts.map +1 -1
  166. package/dist/host/wire-policy.d.ts +22 -0
  167. package/dist/host/wire-policy.d.ts.map +1 -0
  168. package/dist/host/wire-policy.js +92 -0
  169. package/dist/host/wire-policy.js.map +1 -0
  170. package/dist/index.d.ts +2 -1
  171. package/dist/index.d.ts.map +1 -1
  172. package/dist/index.js +2 -1
  173. package/dist/index.js.map +1 -1
  174. package/dist/mcp/index.d.ts +4 -0
  175. package/dist/mcp/index.d.ts.map +1 -0
  176. package/dist/mcp/index.js +4 -0
  177. package/dist/mcp/index.js.map +1 -0
  178. package/dist/mcp/types.d.ts +466 -0
  179. package/dist/mcp/types.d.ts.map +1 -0
  180. package/dist/mcp/types.js +25 -0
  181. package/dist/mcp/types.js.map +1 -0
  182. package/dist/mcp/wire.d.ts +29 -0
  183. package/dist/mcp/wire.d.ts.map +1 -0
  184. package/dist/mcp/wire.js +1046 -0
  185. package/dist/mcp/wire.js.map +1 -0
  186. package/dist/mcp/zod.d.ts +5763 -0
  187. package/dist/mcp/zod.d.ts.map +1 -0
  188. package/dist/mcp/zod.js +908 -0
  189. package/dist/mcp/zod.js.map +1 -0
  190. package/dist/types-CX893hgH.d.ts +468 -0
  191. package/dist/types-CsX6EN2t.d.ts +301 -0
  192. package/dist/types-eOlt9J7n.d.ts +478 -0
  193. package/dist/vscode/index.d.ts +3 -0
  194. package/dist/vscode/index.d.ts.map +1 -0
  195. package/dist/vscode/index.js +3 -0
  196. package/dist/vscode/index.js.map +1 -0
  197. package/dist/vscode/messages.d.ts +204 -0
  198. package/dist/vscode/messages.d.ts.map +1 -0
  199. package/dist/vscode/messages.js +494 -0
  200. package/dist/vscode/messages.js.map +1 -0
  201. package/dist/workspace/index.d.ts +3 -2
  202. package/dist/workspace/index.d.ts.map +1 -1
  203. package/dist/workspace/index.js +2 -1
  204. package/dist/workspace/index.js.map +1 -1
  205. package/dist/workspace/reconcile-electron.d.ts +19 -0
  206. package/dist/workspace/reconcile-electron.d.ts.map +1 -0
  207. package/dist/workspace/reconcile-electron.js +42 -0
  208. package/dist/workspace/reconcile-electron.js.map +1 -0
  209. package/dist/workspace/types.d.ts +41 -1
  210. package/dist/workspace/types.d.ts.map +1 -1
  211. package/dist/workspace/types.js +0 -4
  212. package/dist/workspace/types.js.map +1 -1
  213. package/dist/workspace/workspace-manager.d.ts +17 -5
  214. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  215. package/dist/workspace/workspace-manager.js +55 -11
  216. package/dist/workspace/workspace-manager.js.map +1 -1
  217. package/dist/workspace-path-CWVrcuPL.d.ts +21 -0
  218. package/package.json +43 -2
  219. package/src/document/document-session.ts +834 -0
  220. package/src/document/filesystem-target.ts +109 -0
  221. package/src/document/index.ts +41 -0
  222. package/src/document/recovery-journal.ts +536 -0
  223. package/src/document/types.ts +116 -0
  224. package/src/filesystem/commit-lock.ts +29 -0
  225. package/src/filesystem/dbk-workspace.ts +135 -0
  226. package/src/filesystem/electron-provider-v2.ts +630 -0
  227. package/src/filesystem/electron-provider.ts +25 -14
  228. package/src/filesystem/electron.ts +3 -0
  229. package/src/filesystem/filesystem-content-container.ts +53 -15
  230. package/src/filesystem/fs-error.ts +234 -0
  231. package/src/filesystem/index.ts +119 -0
  232. package/src/filesystem/indexeddb-content-container.ts +20 -12
  233. package/src/filesystem/indexeddb-provider-v2.ts +1740 -0
  234. package/src/filesystem/indexeddb-provider.ts +126 -300
  235. package/src/filesystem/indexeddb-store.ts +207 -0
  236. package/src/filesystem/indexeddb.ts +12 -0
  237. package/src/filesystem/legacy-v2-adapter.ts +779 -0
  238. package/src/filesystem/memory-provider-v2.ts +896 -0
  239. package/src/filesystem/memory-provider.ts +180 -0
  240. package/src/filesystem/memory.ts +15 -0
  241. package/src/filesystem/move-entry.ts +142 -0
  242. package/src/filesystem/move-error.ts +159 -0
  243. package/src/filesystem/native-provider-v2.ts +1165 -0
  244. package/src/filesystem/native-provider.ts +194 -41
  245. package/src/filesystem/native.ts +16 -0
  246. package/src/filesystem/types.ts +49 -4
  247. package/src/filesystem/v2.ts +231 -0
  248. package/src/filesystem/workspace-path.ts +92 -0
  249. package/src/host/filesystem-v2.ts +104 -0
  250. package/src/host/git.ts +246 -0
  251. package/src/host/index.ts +46 -0
  252. package/src/host/types.ts +155 -34
  253. package/src/host/wire-policy.ts +103 -0
  254. package/src/index.ts +2 -1
  255. package/src/mcp/index.ts +3 -0
  256. package/src/mcp/types.ts +594 -0
  257. package/src/mcp/wire.ts +1280 -0
  258. package/src/mcp/zod.ts +1000 -0
  259. package/src/vscode/index.ts +2 -0
  260. package/src/vscode/messages.ts +704 -0
  261. package/src/workspace/index.ts +8 -1
  262. package/src/workspace/reconcile-electron.ts +65 -0
  263. package/src/workspace/types.ts +39 -1
  264. package/src/workspace/workspace-manager.ts +71 -10
  265. package/dist/chunk-GUNM43XZ.js +0 -63
  266. package/dist/chunk-GUNM43XZ.js.map +0 -1
  267. package/dist/chunk-IM7LCQJ6.js +0 -850
  268. package/dist/chunk-IM7LCQJ6.js.map +0 -1
  269. package/dist/chunk-ME76RUMR.js.map +0 -1
  270. package/dist/index-pgBHBQ9G.d.ts +0 -213
@@ -0,0 +1,1280 @@
1
+ import { tryParseWorkspacePath, type WorkspacePath } from '../filesystem/workspace-path.js';
2
+ import {
3
+ DOCBLOCKS_MCP_WIRE_VERSION,
4
+ type AppliedOption,
5
+ type ArtifactRef,
6
+ type AssetSummary,
7
+ type BlockSummary,
8
+ type BundleAssetRef,
9
+ type BundleAssetContentSource,
10
+ type ComparisonChange,
11
+ type ComparisonMetric,
12
+ type ComparisonResult,
13
+ type ConversionFidelity,
14
+ type ConversionResult,
15
+ type DiagnosticLocation,
16
+ type DiagnosticSeverity,
17
+ type DiagnosticStage,
18
+ type DocumentMetadataSummary,
19
+ type DocumentItemSummary,
20
+ type DocumentSource,
21
+ type DocumentStatistics,
22
+ type EngineVersion,
23
+ type InspectionResult,
24
+ type LinkSummary,
25
+ type MaterializationOptions,
26
+ type McpDiagnostic,
27
+ type McpErrorResult,
28
+ type OutlineEntry,
29
+ type PreviewItem,
30
+ type PreviewResult,
31
+ type SourceRange,
32
+ type ThemeSummary,
33
+ type TableSummary,
34
+ type ValidationResult,
35
+ type ValidationSummary,
36
+ } from './types.js';
37
+
38
+ /** Quantitative limits for untrusted MCP payloads. */
39
+ export const MCP_WIRE_LIMITS = Object.freeze({
40
+ identifierCharacters: 256,
41
+ formatCharacters: 64,
42
+ mimeTypeCharacters: 256,
43
+ labelCharacters: 1_024,
44
+ pathCharacters: 4_096,
45
+ uriCharacters: 8_192,
46
+ messageCharacters: 4_000,
47
+ excerptCharacters: 4_096,
48
+ documentCharacters: 20 * 1024 * 1024,
49
+ artifactBytes: 500 * 1024 * 1024,
50
+ bundleAssets: 256,
51
+ arrayEntries: 10_000,
52
+ previewItems: 1_000,
53
+ imageDimension: 32_768,
54
+ });
55
+
56
+ const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/u;
57
+ const ENGINE_NAME_PATTERN =
58
+ /^(?:[A-Za-z0-9][A-Za-z0-9._:-]*|@[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*)$/u;
59
+ const FORMAT_PATTERN = /^[a-z0-9][a-z0-9.+_-]*$/u;
60
+ const MIME_TYPE_PATTERN = /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/iu;
61
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
62
+ const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
63
+
64
+ export function parseArtifactRef(value: unknown): ArtifactRef | null {
65
+ if (
66
+ !isRecord(value) ||
67
+ !hasExactKeys(value, [
68
+ 'id',
69
+ 'uri',
70
+ 'format',
71
+ 'mimeType',
72
+ 'size',
73
+ 'sha256',
74
+ 'sourceFormat',
75
+ 'sourceSha256',
76
+ 'suggestedFilename',
77
+ 'appliedOptions',
78
+ 'engineVersions',
79
+ 'createdAt',
80
+ 'expiresAt',
81
+ ])
82
+ ) {
83
+ return null;
84
+ }
85
+
86
+ const {
87
+ id,
88
+ uri,
89
+ format,
90
+ mimeType,
91
+ size,
92
+ sha256,
93
+ sourceFormat,
94
+ sourceSha256,
95
+ suggestedFilename,
96
+ createdAt,
97
+ expiresAt,
98
+ } = value;
99
+ const appliedOptions = parseArray(value.appliedOptions, parseAppliedOption);
100
+ const engineVersions = parseArray(value.engineVersions, parseEngineVersion);
101
+ if (
102
+ !isIdentifier(id) ||
103
+ !isArtifactUri(uri, id) ||
104
+ !isFormat(format) ||
105
+ !isMimeType(mimeType) ||
106
+ !isBoundedInteger(size, 0, MCP_WIRE_LIMITS.artifactBytes) ||
107
+ !isSha256(sha256) ||
108
+ !(sourceFormat === null || isFormat(sourceFormat)) ||
109
+ !(sourceSha256 === null || isSha256(sourceSha256)) ||
110
+ !isSafeFilename(suggestedFilename) ||
111
+ appliedOptions === null ||
112
+ engineVersions === null ||
113
+ hasDuplicateNames(appliedOptions) ||
114
+ hasDuplicateNames(engineVersions) ||
115
+ !isIsoTimestamp(createdAt) ||
116
+ !(expiresAt === null || isIsoTimestamp(expiresAt)) ||
117
+ (expiresAt !== null && Date.parse(expiresAt) <= Date.parse(createdAt))
118
+ ) {
119
+ return null;
120
+ }
121
+
122
+ return {
123
+ id,
124
+ uri,
125
+ format,
126
+ mimeType,
127
+ size,
128
+ sha256,
129
+ sourceFormat,
130
+ sourceSha256,
131
+ suggestedFilename,
132
+ appliedOptions,
133
+ engineVersions,
134
+ createdAt,
135
+ expiresAt,
136
+ };
137
+ }
138
+
139
+ export function parseDocumentSource(value: unknown): DocumentSource | null {
140
+ if (!isRecord(value) || typeof value.kind !== 'string') return null;
141
+
142
+ switch (value.kind) {
143
+ case 'markdown': {
144
+ if (!hasExactKeys(value, ['kind', 'markdown', 'name'])) return null;
145
+ const { markdown, name } = value;
146
+ if (
147
+ !isBoundedString(markdown, MCP_WIRE_LIMITS.documentCharacters) ||
148
+ !isNullableBoundedString(name, MCP_WIRE_LIMITS.labelCharacters)
149
+ ) {
150
+ return null;
151
+ }
152
+ return { kind: 'markdown', markdown, name };
153
+ }
154
+ case 'file': {
155
+ if (!hasExactKeys(value, ['kind', 'rootId', 'path', 'format'])) return null;
156
+ const { rootId, path, format } = value;
157
+ const parsedPath = parseNonRootWorkspacePath(path);
158
+ if (!isIdentifier(rootId) || parsedPath === null || !(format === null || isFormat(format))) {
159
+ return null;
160
+ }
161
+ return { kind: 'file', rootId, path: parsedPath, format };
162
+ }
163
+ case 'artifact': {
164
+ if (!hasExactKeys(value, ['kind', 'uri']) || !isArtifactUri(value.uri)) return null;
165
+ return { kind: 'artifact', uri: value.uri };
166
+ }
167
+ case 'bundle': {
168
+ if (!hasExactKeys(value, ['kind', 'markdown', 'assets', 'name'])) return null;
169
+ const { markdown, name } = value;
170
+ if (
171
+ !isBoundedString(markdown, MCP_WIRE_LIMITS.documentCharacters) ||
172
+ !isNullableBoundedString(name, MCP_WIRE_LIMITS.labelCharacters)
173
+ ) {
174
+ return null;
175
+ }
176
+ const assets = parseArray(value.assets, parseBundleAssetRef, MCP_WIRE_LIMITS.bundleAssets);
177
+ if (assets === null || hasDuplicatePaths(assets)) return null;
178
+ return { kind: 'bundle', markdown, assets, name };
179
+ }
180
+ default:
181
+ return null;
182
+ }
183
+ }
184
+
185
+ export function parseMcpDiagnostic(value: unknown): McpDiagnostic | null {
186
+ if (
187
+ !isRecord(value) ||
188
+ !hasExactKeys(value, [
189
+ 'code',
190
+ 'severity',
191
+ 'stage',
192
+ 'format',
193
+ 'count',
194
+ 'message',
195
+ 'remediation',
196
+ 'retryable',
197
+ 'location',
198
+ ])
199
+ ) {
200
+ return null;
201
+ }
202
+
203
+ const { code, severity, stage, format, count, message, remediation, retryable } = value;
204
+ const location = parseNullable(value.location, parseDiagnosticLocation);
205
+ if (
206
+ !isIdentifier(code) ||
207
+ !isDiagnosticSeverity(severity) ||
208
+ !isDiagnosticStage(stage) ||
209
+ !(format === null || isFormat(format)) ||
210
+ !isBoundedInteger(count, 1, Number.MAX_SAFE_INTEGER) ||
211
+ !isBoundedString(message, MCP_WIRE_LIMITS.messageCharacters, 1) ||
212
+ !isNullableBoundedString(remediation, MCP_WIRE_LIMITS.messageCharacters) ||
213
+ typeof retryable !== 'boolean' ||
214
+ location === undefined
215
+ ) {
216
+ return null;
217
+ }
218
+
219
+ return {
220
+ code,
221
+ severity,
222
+ stage,
223
+ format,
224
+ count,
225
+ message,
226
+ remediation,
227
+ retryable,
228
+ location,
229
+ };
230
+ }
231
+
232
+ export function parseMcpErrorResult(value: unknown): McpErrorResult | null {
233
+ if (
234
+ !isRecord(value) ||
235
+ !hasExactKeys(value, ['version', 'kind', 'result', 'error']) ||
236
+ value.version !== DOCBLOCKS_MCP_WIRE_VERSION ||
237
+ value.kind !== 'error' ||
238
+ value.result !== null ||
239
+ !isRecord(value.error) ||
240
+ !hasExactKeys(value.error, [
241
+ 'code',
242
+ 'message',
243
+ 'stage',
244
+ 'format',
245
+ 'hint',
246
+ 'retryable',
247
+ 'operationLoad',
248
+ ])
249
+ ) {
250
+ return null;
251
+ }
252
+ const { code, message, stage, format, hint, retryable, operationLoad } = value.error;
253
+ if (
254
+ !isIdentifier(code) ||
255
+ !isBoundedString(message, MCP_WIRE_LIMITS.messageCharacters, 1) ||
256
+ !(stage === null || isDiagnosticStage(stage)) ||
257
+ !(format === null || isFormat(format)) ||
258
+ !isNullableBoundedString(hint, MCP_WIRE_LIMITS.messageCharacters) ||
259
+ typeof retryable !== 'boolean' ||
260
+ !(
261
+ operationLoad === null ||
262
+ (isRecord(operationLoad) &&
263
+ hasExactKeys(operationLoad, ['active', 'capacity']) &&
264
+ isBoundedInteger(operationLoad.active, 0, 32) &&
265
+ isBoundedInteger(operationLoad.capacity, 1, 32) &&
266
+ operationLoad.active <= operationLoad.capacity)
267
+ )
268
+ ) {
269
+ return null;
270
+ }
271
+ return {
272
+ version: DOCBLOCKS_MCP_WIRE_VERSION,
273
+ kind: 'error',
274
+ result: null,
275
+ error: {
276
+ code,
277
+ message,
278
+ stage,
279
+ format,
280
+ hint,
281
+ retryable,
282
+ operationLoad:
283
+ operationLoad === null
284
+ ? null
285
+ : {
286
+ active: operationLoad.active as number,
287
+ capacity: operationLoad.capacity as number,
288
+ },
289
+ },
290
+ };
291
+ }
292
+
293
+ export function parseConversionResult(value: unknown): ConversionResult | null {
294
+ if (
295
+ !isRecord(value) ||
296
+ !hasExactKeys(value, [
297
+ 'version',
298
+ 'kind',
299
+ 'sourceFormat',
300
+ 'targetFormat',
301
+ 'artifact',
302
+ 'fidelity',
303
+ 'appliedThemeId',
304
+ 'appliedTransformId',
305
+ 'sourceAssets',
306
+ 'sourceAssetCount',
307
+ 'diagnostics',
308
+ ]) ||
309
+ value.version !== DOCBLOCKS_MCP_WIRE_VERSION ||
310
+ value.kind !== 'conversion' ||
311
+ !isFormat(value.sourceFormat) ||
312
+ !isFormat(value.targetFormat) ||
313
+ !isConversionFidelity(value.fidelity) ||
314
+ !isNullableIdentifier(value.appliedThemeId) ||
315
+ !isNullableIdentifier(value.appliedTransformId) ||
316
+ !isBoundedInteger(value.sourceAssetCount, 0, MCP_WIRE_LIMITS.arrayEntries)
317
+ ) {
318
+ return null;
319
+ }
320
+
321
+ const artifact = parseArtifactRef(value.artifact);
322
+ const sourceAssets = parseArray(value.sourceAssets, parseAssetSummary);
323
+ const diagnostics = parseArray(value.diagnostics, parseMcpDiagnostic);
324
+ if (
325
+ artifact === null ||
326
+ sourceAssets === null ||
327
+ diagnostics === null ||
328
+ sourceAssets.length !== value.sourceAssetCount ||
329
+ hasDuplicatePaths(sourceAssets) ||
330
+ artifact.format !== value.targetFormat ||
331
+ (artifact.sourceFormat !== null && artifact.sourceFormat !== value.sourceFormat)
332
+ ) {
333
+ return null;
334
+ }
335
+
336
+ return {
337
+ version: DOCBLOCKS_MCP_WIRE_VERSION,
338
+ kind: 'conversion',
339
+ sourceFormat: value.sourceFormat,
340
+ targetFormat: value.targetFormat,
341
+ artifact,
342
+ fidelity: value.fidelity,
343
+ appliedThemeId: value.appliedThemeId,
344
+ appliedTransformId: value.appliedTransformId,
345
+ sourceAssets,
346
+ sourceAssetCount: value.sourceAssetCount,
347
+ diagnostics,
348
+ };
349
+ }
350
+
351
+ export function parseInspectionResult(value: unknown): InspectionResult | null {
352
+ if (
353
+ !isRecord(value) ||
354
+ !hasExactKeys(value, [
355
+ 'version',
356
+ 'kind',
357
+ 'sourceFormat',
358
+ 'metadata',
359
+ 'statistics',
360
+ 'outline',
361
+ 'blocks',
362
+ 'blockOffset',
363
+ 'nextCursor',
364
+ 'tables',
365
+ 'links',
366
+ 'items',
367
+ 'assets',
368
+ 'theme',
369
+ 'truncated',
370
+ 'detailsTruncated',
371
+ 'diagnostics',
372
+ ]) ||
373
+ value.version !== DOCBLOCKS_MCP_WIRE_VERSION ||
374
+ value.kind !== 'inspection' ||
375
+ !isFormat(value.sourceFormat) ||
376
+ !isBoundedInteger(value.blockOffset, 0, MCP_WIRE_LIMITS.arrayEntries) ||
377
+ !isNullableBoundedString(value.nextCursor, MCP_WIRE_LIMITS.identifierCharacters) ||
378
+ typeof value.truncated !== 'boolean' ||
379
+ typeof value.detailsTruncated !== 'boolean'
380
+ ) {
381
+ return null;
382
+ }
383
+
384
+ const metadata = parseDocumentMetadata(value.metadata);
385
+ const statistics = parseDocumentStatistics(value.statistics);
386
+ const outline = parseArray(value.outline, parseOutlineEntry);
387
+ const blocks = parseArray(value.blocks, parseBlockSummary);
388
+ const tables = parseArray(value.tables, parseTableSummary);
389
+ const links = parseArray(value.links, parseLinkSummary);
390
+ const items = parseArray(value.items, parseDocumentItemSummary);
391
+ const assets = parseArray(value.assets, parseAssetSummary);
392
+ const theme = parseNullable(value.theme, parseThemeSummary);
393
+ const diagnostics = parseArray(value.diagnostics, parseMcpDiagnostic);
394
+ if (
395
+ metadata === null ||
396
+ statistics === null ||
397
+ outline === null ||
398
+ blocks === null ||
399
+ tables === null ||
400
+ links === null ||
401
+ items === null ||
402
+ assets === null ||
403
+ theme === undefined ||
404
+ diagnostics === null ||
405
+ statistics.blockCount < value.blockOffset + blocks.length ||
406
+ statistics.assetCount < assets.length ||
407
+ (!value.truncated && statistics.blockCount !== value.blockOffset + blocks.length) ||
408
+ value.truncated !== (value.nextCursor !== null) ||
409
+ statistics.tableCount < tables.length ||
410
+ statistics.linkCount < links.length ||
411
+ hasDuplicateIds(outline) ||
412
+ hasDuplicateIds(blocks) ||
413
+ hasDuplicateIndexes(tables) ||
414
+ hasDuplicateIndexes(links) ||
415
+ hasDuplicateItemIndexes(items) ||
416
+ hasDuplicatePaths(assets) ||
417
+ outline.some((entry) => entry.blockIndex >= statistics.blockCount) ||
418
+ blocks.some(
419
+ (block) => block.sourceRange !== null && block.sourceRange.end > statistics.characterCount,
420
+ )
421
+ ) {
422
+ return null;
423
+ }
424
+
425
+ return {
426
+ version: DOCBLOCKS_MCP_WIRE_VERSION,
427
+ kind: 'inspection',
428
+ sourceFormat: value.sourceFormat,
429
+ metadata,
430
+ statistics,
431
+ outline,
432
+ blocks,
433
+ blockOffset: value.blockOffset,
434
+ nextCursor: value.nextCursor,
435
+ tables,
436
+ links,
437
+ items,
438
+ assets,
439
+ theme,
440
+ truncated: value.truncated,
441
+ detailsTruncated: value.detailsTruncated,
442
+ diagnostics,
443
+ };
444
+ }
445
+
446
+ export function parseValidationResult(value: unknown): ValidationResult | null {
447
+ if (
448
+ !isRecord(value) ||
449
+ !hasExactKeys(value, [
450
+ 'version',
451
+ 'kind',
452
+ 'sourceFormat',
453
+ 'targetFormat',
454
+ 'valid',
455
+ 'summary',
456
+ 'diagnostics',
457
+ ]) ||
458
+ value.version !== DOCBLOCKS_MCP_WIRE_VERSION ||
459
+ value.kind !== 'validation' ||
460
+ !isFormat(value.sourceFormat) ||
461
+ !(value.targetFormat === null || isFormat(value.targetFormat)) ||
462
+ typeof value.valid !== 'boolean'
463
+ ) {
464
+ return null;
465
+ }
466
+
467
+ const summary = parseValidationSummary(value.summary);
468
+ const diagnostics = parseArray(value.diagnostics, parseMcpDiagnostic);
469
+ if (summary === null || diagnostics === null) return null;
470
+ const actualSummary = summarizeDiagnostics(diagnostics);
471
+ if (
472
+ summary.errorCount !== actualSummary.errorCount ||
473
+ summary.warningCount !== actualSummary.warningCount ||
474
+ summary.infoCount !== actualSummary.infoCount ||
475
+ value.valid !== (summary.errorCount === 0)
476
+ ) {
477
+ return null;
478
+ }
479
+
480
+ return {
481
+ version: DOCBLOCKS_MCP_WIRE_VERSION,
482
+ kind: 'validation',
483
+ sourceFormat: value.sourceFormat,
484
+ targetFormat: value.targetFormat,
485
+ valid: value.valid,
486
+ summary,
487
+ diagnostics,
488
+ };
489
+ }
490
+
491
+ export function parsePreviewResult(value: unknown): PreviewResult | null {
492
+ if (
493
+ !isRecord(value) ||
494
+ !hasExactKeys(value, [
495
+ 'version',
496
+ 'kind',
497
+ 'sourceFormat',
498
+ 'previewBasis',
499
+ 'totalItems',
500
+ 'items',
501
+ 'truncated',
502
+ 'diagnostics',
503
+ ]) ||
504
+ value.version !== DOCBLOCKS_MCP_WIRE_VERSION ||
505
+ value.kind !== 'preview' ||
506
+ !isFormat(value.sourceFormat) ||
507
+ !(
508
+ value.previewBasis === 'source-render' ||
509
+ value.previewBasis === 'reconstructed-import' ||
510
+ value.previewBasis === 'native-extracted'
511
+ ) ||
512
+ !isBoundedInteger(value.totalItems, 0, Number.MAX_SAFE_INTEGER) ||
513
+ typeof value.truncated !== 'boolean'
514
+ ) {
515
+ return null;
516
+ }
517
+
518
+ const items = parseArray(value.items, parsePreviewItem, MCP_WIRE_LIMITS.previewItems);
519
+ const diagnostics = parseArray(value.diagnostics, parseMcpDiagnostic);
520
+ if (
521
+ items === null ||
522
+ diagnostics === null ||
523
+ value.totalItems < items.length ||
524
+ (!value.truncated && value.totalItems !== items.length) ||
525
+ hasDuplicatePreviewIndexes(items) ||
526
+ items.some(
527
+ (item) =>
528
+ item.artifact.sourceFormat !== null && item.artifact.sourceFormat !== value.sourceFormat,
529
+ )
530
+ ) {
531
+ return null;
532
+ }
533
+
534
+ return {
535
+ version: DOCBLOCKS_MCP_WIRE_VERSION,
536
+ kind: 'preview',
537
+ sourceFormat: value.sourceFormat,
538
+ previewBasis: value.previewBasis,
539
+ totalItems: value.totalItems,
540
+ items,
541
+ truncated: value.truncated,
542
+ diagnostics,
543
+ };
544
+ }
545
+
546
+ export function parseComparisonResult(value: unknown): ComparisonResult | null {
547
+ if (
548
+ !isRecord(value) ||
549
+ !hasExactKeys(value, [
550
+ 'version',
551
+ 'kind',
552
+ 'leftFormat',
553
+ 'rightFormat',
554
+ 'equivalent',
555
+ 'score',
556
+ 'changes',
557
+ 'metrics',
558
+ 'diagnostics',
559
+ ]) ||
560
+ value.version !== DOCBLOCKS_MCP_WIRE_VERSION ||
561
+ value.kind !== 'comparison' ||
562
+ !isFormat(value.leftFormat) ||
563
+ !isFormat(value.rightFormat) ||
564
+ typeof value.equivalent !== 'boolean' ||
565
+ !isBoundedNumber(value.score, 0, 1)
566
+ ) {
567
+ return null;
568
+ }
569
+
570
+ const changes = parseArray(value.changes, parseComparisonChange);
571
+ const metrics = parseArray(value.metrics, parseComparisonMetric);
572
+ const diagnostics = parseArray(value.diagnostics, parseMcpDiagnostic);
573
+ if (
574
+ changes === null ||
575
+ metrics === null ||
576
+ diagnostics === null ||
577
+ hasDuplicateNames(metrics) ||
578
+ (value.equivalent &&
579
+ (value.score !== 1 || changes.some((change) => change.status !== 'retained')))
580
+ ) {
581
+ return null;
582
+ }
583
+
584
+ return {
585
+ version: DOCBLOCKS_MCP_WIRE_VERSION,
586
+ kind: 'comparison',
587
+ leftFormat: value.leftFormat,
588
+ rightFormat: value.rightFormat,
589
+ equivalent: value.equivalent,
590
+ score: value.score,
591
+ changes,
592
+ metrics,
593
+ diagnostics,
594
+ };
595
+ }
596
+
597
+ export function parseMaterializationOptions(value: unknown): MaterializationOptions | null {
598
+ if (
599
+ !isRecord(value) ||
600
+ !hasExactKeys(value, ['rootId', 'path', 'ifExists', 'expectedSha256']) ||
601
+ !isIdentifier(value.rootId)
602
+ ) {
603
+ return null;
604
+ }
605
+ const path = parseNonRootWorkspacePath(value.path);
606
+ if (path === null) return null;
607
+
608
+ if (value.ifExists === 'error' && value.expectedSha256 === null) {
609
+ return { rootId: value.rootId, path, ifExists: 'error', expectedSha256: null };
610
+ }
611
+ if (value.ifExists === 'replace' && isSha256(value.expectedSha256)) {
612
+ return {
613
+ rootId: value.rootId,
614
+ path,
615
+ ifExists: 'replace',
616
+ expectedSha256: value.expectedSha256,
617
+ };
618
+ }
619
+ return null;
620
+ }
621
+
622
+ function parseBundleAssetRef(value: unknown): BundleAssetRef | null {
623
+ if (
624
+ !isRecord(value) ||
625
+ !hasExactKeys(value, ['path', 'source', 'mimeType', 'altText', 'credit', 'license'])
626
+ ) {
627
+ return null;
628
+ }
629
+ const path = parseNonRootWorkspacePath(value.path);
630
+ const source = parseBundleAssetContentSource(value.source);
631
+ if (
632
+ path === null ||
633
+ source === null ||
634
+ !(value.mimeType === null || isMimeType(value.mimeType)) ||
635
+ !isNullableBoundedString(value.altText, MCP_WIRE_LIMITS.labelCharacters) ||
636
+ !isNullableBoundedString(value.credit, MCP_WIRE_LIMITS.labelCharacters) ||
637
+ !isNullableBoundedString(value.license, MCP_WIRE_LIMITS.labelCharacters)
638
+ ) {
639
+ return null;
640
+ }
641
+ return {
642
+ path,
643
+ source,
644
+ mimeType: value.mimeType,
645
+ altText: value.altText,
646
+ credit: value.credit,
647
+ license: value.license,
648
+ };
649
+ }
650
+
651
+ function parseBundleAssetContentSource(value: unknown): BundleAssetContentSource | null {
652
+ if (!isRecord(value) || typeof value.kind !== 'string') return null;
653
+ if (value.kind === 'file') {
654
+ if (!hasExactKeys(value, ['kind', 'rootId', 'path']) || !isIdentifier(value.rootId)) {
655
+ return null;
656
+ }
657
+ const path = parseNonRootWorkspacePath(value.path);
658
+ return path === null ? null : { kind: 'file', rootId: value.rootId, path };
659
+ }
660
+ if (value.kind === 'artifact') {
661
+ return hasExactKeys(value, ['kind', 'uri']) && isArtifactUri(value.uri)
662
+ ? { kind: 'artifact', uri: value.uri }
663
+ : null;
664
+ }
665
+ return null;
666
+ }
667
+
668
+ function parseDiagnosticLocation(value: unknown): DiagnosticLocation | null {
669
+ if (!isRecord(value) || typeof value.kind !== 'string') return null;
670
+ switch (value.kind) {
671
+ case 'source':
672
+ return hasExactKeys(value, ['kind', 'line', 'column']) &&
673
+ isBoundedInteger(value.line, 1, Number.MAX_SAFE_INTEGER) &&
674
+ isBoundedInteger(value.column, 1, Number.MAX_SAFE_INTEGER)
675
+ ? { kind: 'source', line: value.line, column: value.column }
676
+ : null;
677
+ case 'block':
678
+ return hasExactKeys(value, ['kind', 'blockId', 'nodeType']) &&
679
+ isIdentifier(value.blockId) &&
680
+ isIdentifier(value.nodeType)
681
+ ? { kind: 'block', blockId: value.blockId, nodeType: value.nodeType }
682
+ : null;
683
+ case 'asset': {
684
+ if (!hasExactKeys(value, ['kind', 'path'])) return null;
685
+ const path = parseNonRootWorkspacePath(value.path);
686
+ return path === null ? null : { kind: 'asset', path };
687
+ }
688
+ case 'item':
689
+ return hasExactKeys(value, ['kind', 'itemKind', 'index']) &&
690
+ isPreviewItemKind(value.itemKind) &&
691
+ isBoundedInteger(value.index, 0, Number.MAX_SAFE_INTEGER)
692
+ ? { kind: 'item', itemKind: value.itemKind, index: value.index }
693
+ : null;
694
+ default:
695
+ return null;
696
+ }
697
+ }
698
+
699
+ function parseAppliedOption(value: unknown): AppliedOption | null {
700
+ if (
701
+ !isRecord(value) ||
702
+ !hasExactKeys(value, ['name', 'value']) ||
703
+ !isIdentifier(value.name) ||
704
+ !isAppliedOptionValue(value.value)
705
+ ) {
706
+ return null;
707
+ }
708
+ return { name: value.name, value: value.value };
709
+ }
710
+
711
+ function parseEngineVersion(value: unknown): EngineVersion | null {
712
+ if (
713
+ !isRecord(value) ||
714
+ !hasExactKeys(value, ['name', 'version']) ||
715
+ !isEngineName(value.name) ||
716
+ !isBoundedString(value.version, MCP_WIRE_LIMITS.identifierCharacters, 1)
717
+ ) {
718
+ return null;
719
+ }
720
+ return { name: value.name, version: value.version };
721
+ }
722
+
723
+ function parseDocumentMetadata(value: unknown): DocumentMetadataSummary | null {
724
+ if (
725
+ !isRecord(value) ||
726
+ !hasExactKeys(value, ['title', 'author', 'description']) ||
727
+ !isNullableBoundedString(value.title, MCP_WIRE_LIMITS.labelCharacters) ||
728
+ !isNullableBoundedString(value.author, MCP_WIRE_LIMITS.labelCharacters) ||
729
+ !isNullableBoundedString(value.description, MCP_WIRE_LIMITS.messageCharacters)
730
+ ) {
731
+ return null;
732
+ }
733
+ return { title: value.title, author: value.author, description: value.description };
734
+ }
735
+
736
+ function parseDocumentStatistics(value: unknown): DocumentStatistics | null {
737
+ if (
738
+ !isRecord(value) ||
739
+ !hasExactKeys(value, [
740
+ 'blockCount',
741
+ 'wordCount',
742
+ 'characterCount',
743
+ 'tableCount',
744
+ 'linkCount',
745
+ 'assetCount',
746
+ 'pageCount',
747
+ 'slideCount',
748
+ 'sheetCount',
749
+ ]) ||
750
+ !isNonNegativeInteger(value.blockCount) ||
751
+ !isNonNegativeInteger(value.wordCount) ||
752
+ !isNonNegativeInteger(value.characterCount) ||
753
+ !isNonNegativeInteger(value.tableCount) ||
754
+ !isNonNegativeInteger(value.linkCount) ||
755
+ !isNonNegativeInteger(value.assetCount) ||
756
+ !isNullableNonNegativeInteger(value.pageCount) ||
757
+ !isNullableNonNegativeInteger(value.slideCount) ||
758
+ !isNullableNonNegativeInteger(value.sheetCount)
759
+ ) {
760
+ return null;
761
+ }
762
+ return {
763
+ blockCount: value.blockCount,
764
+ wordCount: value.wordCount,
765
+ characterCount: value.characterCount,
766
+ tableCount: value.tableCount,
767
+ linkCount: value.linkCount,
768
+ assetCount: value.assetCount,
769
+ pageCount: value.pageCount,
770
+ slideCount: value.slideCount,
771
+ sheetCount: value.sheetCount,
772
+ };
773
+ }
774
+
775
+ function parseOutlineEntry(value: unknown): OutlineEntry | null {
776
+ if (
777
+ !isRecord(value) ||
778
+ !hasExactKeys(value, ['id', 'title', 'level', 'blockIndex']) ||
779
+ !isIdentifier(value.id) ||
780
+ !isBoundedString(value.title, MCP_WIRE_LIMITS.labelCharacters, 1) ||
781
+ !isBoundedInteger(value.level, 1, 6) ||
782
+ !isNonNegativeInteger(value.blockIndex)
783
+ ) {
784
+ return null;
785
+ }
786
+ return { id: value.id, title: value.title, level: value.level, blockIndex: value.blockIndex };
787
+ }
788
+
789
+ function parseBlockSummary(value: unknown): BlockSummary | null {
790
+ if (
791
+ !isRecord(value) ||
792
+ !hasExactKeys(value, ['id', 'type', 'text', 'wordCount', 'templateId', 'sourceRange']) ||
793
+ !isIdentifier(value.id) ||
794
+ !isIdentifier(value.type) ||
795
+ !isBoundedString(value.text, MCP_WIRE_LIMITS.excerptCharacters) ||
796
+ !isNonNegativeInteger(value.wordCount) ||
797
+ !isNullableIdentifier(value.templateId)
798
+ ) {
799
+ return null;
800
+ }
801
+ const sourceRange = parseNullable(value.sourceRange, parseSourceRange);
802
+ if (sourceRange === undefined) return null;
803
+ return {
804
+ id: value.id,
805
+ type: value.type,
806
+ text: value.text,
807
+ wordCount: value.wordCount,
808
+ templateId: value.templateId,
809
+ sourceRange,
810
+ };
811
+ }
812
+
813
+ function parseSourceRange(value: unknown): SourceRange | null {
814
+ if (
815
+ !isRecord(value) ||
816
+ !hasExactKeys(value, ['start', 'end']) ||
817
+ !isNonNegativeInteger(value.start) ||
818
+ !isNonNegativeInteger(value.end) ||
819
+ value.end < value.start
820
+ ) {
821
+ return null;
822
+ }
823
+ return { start: value.start, end: value.end };
824
+ }
825
+
826
+ function parseTableSummary(value: unknown): TableSummary | null {
827
+ if (
828
+ !isRecord(value) ||
829
+ !hasExactKeys(value, ['index', 'rowCount', 'columnCount', 'headers']) ||
830
+ !isNonNegativeInteger(value.index) ||
831
+ !isNonNegativeInteger(value.rowCount) ||
832
+ !isNonNegativeInteger(value.columnCount)
833
+ ) {
834
+ return null;
835
+ }
836
+ const headers = parseArray(value.headers, (entry) =>
837
+ isBoundedString(entry, MCP_WIRE_LIMITS.labelCharacters) ? entry : null,
838
+ );
839
+ if (headers === null || headers.length > value.columnCount) return null;
840
+ return {
841
+ index: value.index,
842
+ rowCount: value.rowCount,
843
+ columnCount: value.columnCount,
844
+ headers,
845
+ };
846
+ }
847
+
848
+ function parseLinkSummary(value: unknown): LinkSummary | null {
849
+ if (
850
+ !isRecord(value) ||
851
+ !hasExactKeys(value, ['index', 'text', 'target']) ||
852
+ !isNonNegativeInteger(value.index) ||
853
+ !isBoundedString(value.text, MCP_WIRE_LIMITS.labelCharacters) ||
854
+ !isBoundedString(value.target, MCP_WIRE_LIMITS.uriCharacters, 1)
855
+ ) {
856
+ return null;
857
+ }
858
+ return { index: value.index, text: value.text, target: value.target };
859
+ }
860
+
861
+ function parseDocumentItemSummary(value: unknown): DocumentItemSummary | null {
862
+ if (
863
+ !isRecord(value) ||
864
+ !hasExactKeys(value, ['kind', 'index', 'label', 'blockIds']) ||
865
+ !isPreviewItemKind(value.kind) ||
866
+ !isNonNegativeInteger(value.index) ||
867
+ !isNullableBoundedString(value.label, MCP_WIRE_LIMITS.labelCharacters)
868
+ ) {
869
+ return null;
870
+ }
871
+ const blockIds = parseArray(value.blockIds, (entry) => (isIdentifier(entry) ? entry : null));
872
+ if (blockIds === null || new Set(blockIds).size !== blockIds.length) return null;
873
+ return { kind: value.kind, index: value.index, label: value.label, blockIds };
874
+ }
875
+
876
+ function parseAssetSummary(value: unknown): AssetSummary | null {
877
+ if (
878
+ !isRecord(value) ||
879
+ !hasExactKeys(value, ['path', 'mimeType', 'size', 'sha256', 'altText', 'credit', 'license'])
880
+ ) {
881
+ return null;
882
+ }
883
+ const path = parseNonRootWorkspacePath(value.path);
884
+ if (
885
+ path === null ||
886
+ !isMimeType(value.mimeType) ||
887
+ !isBoundedInteger(value.size, 0, MCP_WIRE_LIMITS.artifactBytes) ||
888
+ !(value.sha256 === null || isSha256(value.sha256)) ||
889
+ !isNullableBoundedString(value.altText, MCP_WIRE_LIMITS.labelCharacters) ||
890
+ !isNullableBoundedString(value.credit, MCP_WIRE_LIMITS.labelCharacters) ||
891
+ !isNullableBoundedString(value.license, MCP_WIRE_LIMITS.labelCharacters)
892
+ ) {
893
+ return null;
894
+ }
895
+ return {
896
+ path,
897
+ mimeType: value.mimeType,
898
+ size: value.size,
899
+ sha256: value.sha256,
900
+ altText: value.altText,
901
+ credit: value.credit,
902
+ license: value.license,
903
+ };
904
+ }
905
+
906
+ function parseThemeSummary(value: unknown): ThemeSummary | null {
907
+ if (
908
+ !isRecord(value) ||
909
+ !hasExactKeys(value, ['id', 'name', 'source', 'layouts']) ||
910
+ !isIdentifier(value.id) ||
911
+ !isBoundedString(value.name, MCP_WIRE_LIMITS.labelCharacters, 1) ||
912
+ !(value.source === 'built-in' || value.source === 'document' || value.source === 'inferred')
913
+ ) {
914
+ return null;
915
+ }
916
+ const layouts = parseStringArray(value.layouts, MCP_WIRE_LIMITS.labelCharacters);
917
+ if (layouts === null || new Set(layouts).size !== layouts.length) return null;
918
+ return { id: value.id, name: value.name, source: value.source, layouts };
919
+ }
920
+
921
+ function parseValidationSummary(value: unknown): ValidationSummary | null {
922
+ if (
923
+ !isRecord(value) ||
924
+ !hasExactKeys(value, ['errorCount', 'warningCount', 'infoCount']) ||
925
+ !isNonNegativeInteger(value.errorCount) ||
926
+ !isNonNegativeInteger(value.warningCount) ||
927
+ !isNonNegativeInteger(value.infoCount)
928
+ ) {
929
+ return null;
930
+ }
931
+ return {
932
+ errorCount: value.errorCount,
933
+ warningCount: value.warningCount,
934
+ infoCount: value.infoCount,
935
+ };
936
+ }
937
+
938
+ function parsePreviewItem(value: unknown): PreviewItem | null {
939
+ if (
940
+ !isRecord(value) ||
941
+ !hasExactKeys(value, ['kind', 'index', 'label', 'artifact', 'width', 'height']) ||
942
+ !isPreviewItemKind(value.kind) ||
943
+ !isNonNegativeInteger(value.index) ||
944
+ !isNullableBoundedString(value.label, MCP_WIRE_LIMITS.labelCharacters) ||
945
+ !isBoundedInteger(value.width, 1, MCP_WIRE_LIMITS.imageDimension) ||
946
+ !isBoundedInteger(value.height, 1, MCP_WIRE_LIMITS.imageDimension)
947
+ ) {
948
+ return null;
949
+ }
950
+ const artifact = parseArtifactRef(value.artifact);
951
+ if (artifact === null || !artifact.mimeType.startsWith('image/')) return null;
952
+ return {
953
+ kind: value.kind,
954
+ index: value.index,
955
+ label: value.label,
956
+ artifact,
957
+ width: value.width,
958
+ height: value.height,
959
+ };
960
+ }
961
+
962
+ function parseComparisonChange(value: unknown): ComparisonChange | null {
963
+ if (
964
+ !isRecord(value) ||
965
+ !hasExactKeys(value, ['category', 'status', 'path', 'message']) ||
966
+ !isComparisonCategory(value.category) ||
967
+ !isComparisonStatus(value.status) ||
968
+ !isNullableBoundedString(value.path, MCP_WIRE_LIMITS.pathCharacters) ||
969
+ !isBoundedString(value.message, MCP_WIRE_LIMITS.messageCharacters, 1)
970
+ ) {
971
+ return null;
972
+ }
973
+ return {
974
+ category: value.category,
975
+ status: value.status,
976
+ path: value.path,
977
+ message: value.message,
978
+ };
979
+ }
980
+
981
+ function parseComparisonMetric(value: unknown): ComparisonMetric | null {
982
+ if (
983
+ !isRecord(value) ||
984
+ !hasExactKeys(value, ['name', 'leftValue', 'rightValue', 'similarity']) ||
985
+ !isIdentifier(value.name) ||
986
+ !isFiniteNumber(value.leftValue) ||
987
+ !isFiniteNumber(value.rightValue) ||
988
+ !isBoundedNumber(value.similarity, 0, 1)
989
+ ) {
990
+ return null;
991
+ }
992
+ return {
993
+ name: value.name,
994
+ leftValue: value.leftValue,
995
+ rightValue: value.rightValue,
996
+ similarity: value.similarity,
997
+ };
998
+ }
999
+
1000
+ function summarizeDiagnostics(diagnostics: readonly McpDiagnostic[]): ValidationSummary {
1001
+ let errorCount = 0;
1002
+ let warningCount = 0;
1003
+ let infoCount = 0;
1004
+ for (const diagnostic of diagnostics) {
1005
+ if (diagnostic.severity === 'error') errorCount += diagnostic.count;
1006
+ else if (diagnostic.severity === 'warning') warningCount += diagnostic.count;
1007
+ else infoCount += diagnostic.count;
1008
+ }
1009
+ return { errorCount, warningCount, infoCount };
1010
+ }
1011
+
1012
+ function parseArray<T>(
1013
+ value: unknown,
1014
+ parser: (entry: unknown) => T | null,
1015
+ maximumEntries: number = MCP_WIRE_LIMITS.arrayEntries,
1016
+ ): T[] | null {
1017
+ if (!Array.isArray(value) || value.length > maximumEntries) return null;
1018
+ const parsed: T[] = [];
1019
+ for (const entry of value) {
1020
+ const result = parser(entry);
1021
+ if (result === null) return null;
1022
+ parsed.push(result);
1023
+ }
1024
+ return parsed;
1025
+ }
1026
+
1027
+ function parseStringArray(value: unknown, maximumCharacters: number): string[] | null {
1028
+ if (!Array.isArray(value) || value.length > MCP_WIRE_LIMITS.arrayEntries) return null;
1029
+ const parsed: string[] = [];
1030
+ for (const entry of value) {
1031
+ if (!isBoundedString(entry, maximumCharacters, 1)) return null;
1032
+ parsed.push(entry);
1033
+ }
1034
+ return parsed;
1035
+ }
1036
+
1037
+ function parseNullable<T>(
1038
+ value: unknown,
1039
+ parser: (entry: unknown) => T | null,
1040
+ ): T | null | undefined {
1041
+ if (value === null) return null;
1042
+ return parser(value) ?? undefined;
1043
+ }
1044
+
1045
+ function parseNonRootWorkspacePath(value: unknown): WorkspacePath | null {
1046
+ if (
1047
+ !isBoundedString(value, MCP_WIRE_LIMITS.pathCharacters, 1) ||
1048
+ value.includes('\\') ||
1049
+ value.startsWith('/') ||
1050
+ value.endsWith('/') ||
1051
+ value.includes('//')
1052
+ ) {
1053
+ return null;
1054
+ }
1055
+ const parsed = tryParseWorkspacePath(value);
1056
+ return parsed !== null && parsed !== '' && parsed === value ? parsed : null;
1057
+ }
1058
+
1059
+ function isArtifactUri(value: unknown, expectedId?: string): value is string {
1060
+ if (!isBoundedString(value, MCP_WIRE_LIMITS.uriCharacters, 1)) return false;
1061
+ try {
1062
+ const url = new URL(value);
1063
+ if (
1064
+ url.protocol !== 'docblocks:' ||
1065
+ url.hostname !== 'artifacts' ||
1066
+ url.username !== '' ||
1067
+ url.password !== '' ||
1068
+ url.port !== '' ||
1069
+ url.search !== '' ||
1070
+ url.hash !== ''
1071
+ ) {
1072
+ return false;
1073
+ }
1074
+ const id = url.pathname.slice(1);
1075
+ return (
1076
+ !id.includes('/') &&
1077
+ isIdentifier(id) &&
1078
+ value === `docblocks://artifacts/${id}` &&
1079
+ (expectedId === undefined || id === expectedId)
1080
+ );
1081
+ } catch {
1082
+ return false;
1083
+ }
1084
+ }
1085
+
1086
+ function isSafeFilename(value: unknown): value is string {
1087
+ return (
1088
+ isBoundedString(value, MCP_WIRE_LIMITS.labelCharacters, 1) &&
1089
+ value !== '.' &&
1090
+ value !== '..' &&
1091
+ !value.includes('/') &&
1092
+ !value.includes('\\')
1093
+ );
1094
+ }
1095
+
1096
+ function isIsoTimestamp(value: unknown): value is string {
1097
+ if (typeof value !== 'string' || !ISO_TIMESTAMP_PATTERN.test(value)) return false;
1098
+ const timestamp = Date.parse(value);
1099
+ return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
1100
+ }
1101
+
1102
+ function isIdentifier(value: unknown): value is string {
1103
+ return (
1104
+ isBoundedString(value, MCP_WIRE_LIMITS.identifierCharacters, 1) &&
1105
+ IDENTIFIER_PATTERN.test(value)
1106
+ );
1107
+ }
1108
+
1109
+ function isEngineName(value: unknown): value is string {
1110
+ return (
1111
+ isBoundedString(value, MCP_WIRE_LIMITS.identifierCharacters, 1) &&
1112
+ ENGINE_NAME_PATTERN.test(value)
1113
+ );
1114
+ }
1115
+
1116
+ function isNullableIdentifier(value: unknown): value is string | null {
1117
+ return value === null || isIdentifier(value);
1118
+ }
1119
+
1120
+ function isFormat(value: unknown): value is string {
1121
+ return isBoundedString(value, MCP_WIRE_LIMITS.formatCharacters, 1) && FORMAT_PATTERN.test(value);
1122
+ }
1123
+
1124
+ function isMimeType(value: unknown): value is string {
1125
+ return (
1126
+ isBoundedString(value, MCP_WIRE_LIMITS.mimeTypeCharacters, 3) && MIME_TYPE_PATTERN.test(value)
1127
+ );
1128
+ }
1129
+
1130
+ function isSha256(value: unknown): value is string {
1131
+ return typeof value === 'string' && SHA256_PATTERN.test(value);
1132
+ }
1133
+
1134
+ function isAppliedOptionValue(value: unknown): value is string | number | boolean | null {
1135
+ return (
1136
+ value === null ||
1137
+ typeof value === 'boolean' ||
1138
+ isBoundedString(value, MCP_WIRE_LIMITS.messageCharacters) ||
1139
+ isFiniteNumber(value)
1140
+ );
1141
+ }
1142
+
1143
+ function isDiagnosticSeverity(value: unknown): value is DiagnosticSeverity {
1144
+ return value === 'info' || value === 'warning' || value === 'error';
1145
+ }
1146
+
1147
+ function isDiagnosticStage(value: unknown): value is DiagnosticStage {
1148
+ return (
1149
+ value === 'resolve' ||
1150
+ value === 'import' ||
1151
+ value === 'parse' ||
1152
+ value === 'inspect' ||
1153
+ value === 'validate' ||
1154
+ value === 'transform' ||
1155
+ value === 'convert' ||
1156
+ value === 'render' ||
1157
+ value === 'export' ||
1158
+ value === 'materialize' ||
1159
+ value === 'compare'
1160
+ );
1161
+ }
1162
+
1163
+ function isConversionFidelity(value: unknown): value is ConversionFidelity {
1164
+ return (
1165
+ value === 'semantic' ||
1166
+ value === 'editable-native' ||
1167
+ value === 'rendered-fidelity' ||
1168
+ value === 'hybrid'
1169
+ );
1170
+ }
1171
+
1172
+ function isPreviewItemKind(value: unknown): value is PreviewItem['kind'] {
1173
+ return value === 'page' || value === 'slide' || value === 'sheet' || value === 'frame';
1174
+ }
1175
+
1176
+ function isComparisonCategory(value: unknown): value is ComparisonChange['category'] {
1177
+ return (
1178
+ value === 'text' ||
1179
+ value === 'structure' ||
1180
+ value === 'table' ||
1181
+ value === 'media' ||
1182
+ value === 'theme' ||
1183
+ value === 'layout' ||
1184
+ value === 'metadata' ||
1185
+ value === 'timing' ||
1186
+ value === 'accessibility'
1187
+ );
1188
+ }
1189
+
1190
+ function isComparisonStatus(value: unknown): value is ComparisonChange['status'] {
1191
+ return (
1192
+ value === 'retained' ||
1193
+ value === 'changed' ||
1194
+ value === 'degraded' ||
1195
+ value === 'omitted' ||
1196
+ value === 'added'
1197
+ );
1198
+ }
1199
+
1200
+ function hasDuplicateNames(values: readonly { readonly name: string }[]): boolean {
1201
+ return new Set(values.map((value) => value.name)).size !== values.length;
1202
+ }
1203
+
1204
+ function hasDuplicateIds(values: readonly { readonly id: string }[]): boolean {
1205
+ return new Set(values.map((value) => value.id)).size !== values.length;
1206
+ }
1207
+
1208
+ function hasDuplicatePaths(values: readonly { readonly path: WorkspacePath }[]): boolean {
1209
+ return new Set(values.map((value) => value.path)).size !== values.length;
1210
+ }
1211
+
1212
+ function hasDuplicateIndexes(values: readonly { readonly index: number }[]): boolean {
1213
+ return new Set(values.map((value) => value.index)).size !== values.length;
1214
+ }
1215
+
1216
+ function hasDuplicateItemIndexes(values: readonly DocumentItemSummary[]): boolean {
1217
+ return new Set(values.map((value) => `${value.kind}:${value.index}`)).size !== values.length;
1218
+ }
1219
+
1220
+ function hasDuplicatePreviewIndexes(values: readonly PreviewItem[]): boolean {
1221
+ const keys = values.map((value) => `${value.kind}:${value.index}`);
1222
+ return new Set(keys).size !== keys.length;
1223
+ }
1224
+
1225
+ function isRecord(value: unknown): value is Record<string, unknown> {
1226
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1227
+ }
1228
+
1229
+ function hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
1230
+ const actual = Object.keys(value);
1231
+ return (
1232
+ actual.length === keys.length &&
1233
+ keys.every((key) => Object.prototype.hasOwnProperty.call(value, key))
1234
+ );
1235
+ }
1236
+
1237
+ function isBoundedString(
1238
+ value: unknown,
1239
+ maximumCharacters: number,
1240
+ minimumCharacters = 0,
1241
+ ): value is string {
1242
+ return (
1243
+ typeof value === 'string' &&
1244
+ value.length >= minimumCharacters &&
1245
+ value.length <= maximumCharacters &&
1246
+ ![...value].some((character) => {
1247
+ const codePoint = character.codePointAt(0) ?? 0;
1248
+ return codePoint === 0 || codePoint === 0x7f;
1249
+ })
1250
+ );
1251
+ }
1252
+
1253
+ function isNullableBoundedString(
1254
+ value: unknown,
1255
+ maximumCharacters: number,
1256
+ ): value is string | null {
1257
+ return value === null || isBoundedString(value, maximumCharacters);
1258
+ }
1259
+
1260
+ function isFiniteNumber(value: unknown): value is number {
1261
+ return typeof value === 'number' && Number.isFinite(value);
1262
+ }
1263
+
1264
+ function isBoundedNumber(value: unknown, minimum: number, maximum: number): value is number {
1265
+ return isFiniteNumber(value) && value >= minimum && value <= maximum;
1266
+ }
1267
+
1268
+ function isBoundedInteger(value: unknown, minimum: number, maximum: number): value is number {
1269
+ return (
1270
+ Number.isSafeInteger(value) && typeof value === 'number' && value >= minimum && value <= maximum
1271
+ );
1272
+ }
1273
+
1274
+ function isNonNegativeInteger(value: unknown): value is number {
1275
+ return isBoundedInteger(value, 0, Number.MAX_SAFE_INTEGER);
1276
+ }
1277
+
1278
+ function isNullableNonNegativeInteger(value: unknown): value is number | null {
1279
+ return value === null || isNonNegativeInteger(value);
1280
+ }