@dev.fast/whiteboard 0.0.0-stage → 0.2.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 (247) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -2
  3. package/THIRD_PARTY_NOTICES.md +93 -0
  4. package/dist/account-alias-DOHN1RSH.js +973 -0
  5. package/dist/agent-cli-ChOq0ZuO.js +117 -0
  6. package/dist/agent-cli-DzWfZr6G.js +2 -0
  7. package/dist/agent-client-CPQo7iTI.js +305 -0
  8. package/dist/authoring-tools-Cr6Kpjsx.js +234 -0
  9. package/dist/build-info.json +1 -0
  10. package/dist/cli-hVgHcdsv.js +47 -0
  11. package/dist/cli-runner-D8y2_luU.js +5095 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +128 -0
  14. package/dist/client-DFoh50W3.js +950 -0
  15. package/dist/desktop-discovery-DBO-PM2V.js +164 -0
  16. package/dist/error-message-OtiDonty.js +6 -0
  17. package/dist/fs-utils-BMPLt0cr.js +22 -0
  18. package/dist/fuzzy-match-BoAmcyak.js +30 -0
  19. package/dist/headless-host-BJxf4dUU.js +94 -0
  20. package/dist/input-error-OLgB_h31.js +11 -0
  21. package/dist/local-data-CxLk75rx.js +4491 -0
  22. package/dist/mcp-QeF8vkdf.js +140 -0
  23. package/dist/package-paths-B6-zxvIO.js +47 -0
  24. package/dist/process-error-telemetry-v9e5h6D7.js +3952 -0
  25. package/dist/profile-4ry2f3AM.js +76 -0
  26. package/dist/profile-Bh5FIrmf.js +2 -0
  27. package/dist/request-origin-D_4QIdoN.js +811 -0
  28. package/dist/review-agent-traces-DNJFYzc8.js +3866 -0
  29. package/dist/review-home-paths-6zZH-1c9.js +2560 -0
  30. package/dist/review-telemetry-DEFnQsQ2.js +1195 -0
  31. package/dist/runtime-CKiWhGrf.js +52 -0
  32. package/dist/runtime.d.ts +9 -0
  33. package/dist/runtime.js +2 -0
  34. package/dist/s3-SzLpeCG6.js +345 -0
  35. package/dist/s3-config-BgxkSoOy.js +2 -0
  36. package/dist/s3-config-CAxhO9u_.js +676 -0
  37. package/dist/server/desktop-host.d.ts +4 -0
  38. package/dist/server/desktop-host.js +1702 -0
  39. package/dist/server-discovery-Dbzn3w6Z.js +66 -0
  40. package/dist/sharing/index.d.ts +1977 -0
  41. package/dist/sharing/index.js +2 -0
  42. package/dist/src-CGP5ytbV.js +283 -0
  43. package/dist/src-CmdiBL20.js +3676 -0
  44. package/dist/src-DnwdaQ2r.js +865 -0
  45. package/dist/src-X9phtB2j.js +68 -0
  46. package/dist/stored-document-migration-DHeMpLMb.js +2671 -0
  47. package/dist/tool-failure-C8zB73HV.js +98 -0
  48. package/dist/tutorial-trace-DrBvkaeL.js +43 -0
  49. package/instructions/authoring.md +36 -0
  50. package/instructions/file-lenses.md +13 -0
  51. package/instructions/scratchpad.md +23 -0
  52. package/instructions/trace-archaeology.md +113 -0
  53. package/onboarding.md +8 -0
  54. package/package.json +106 -3
  55. package/src/agent-selection.ts +100 -0
  56. package/src/agent-session-ref.ts +80 -0
  57. package/src/ask/agents.ts +351 -0
  58. package/src/ask/checkout-files.ts +47 -0
  59. package/src/ask/file-refs.ts +111 -0
  60. package/src/ask/pi-mcp.ts +154 -0
  61. package/src/ask/protocol.ts +244 -0
  62. package/src/ask/thread-state.ts +350 -0
  63. package/src/ask/thread.ts +1400 -0
  64. package/src/ask/threads.ts +219 -0
  65. package/src/ask/watch.ts +79 -0
  66. package/src/cli-install.ts +1041 -0
  67. package/src/cli-runner.ts +1461 -0
  68. package/src/cli-runtime-info.ts +35 -0
  69. package/src/cli.ts +222 -0
  70. package/src/connect-prompts.ts +248 -0
  71. package/src/cursor-deeplink.ts +11 -0
  72. package/src/desktop-discovery.ts +355 -0
  73. package/src/diff-selection-migration.ts +63 -0
  74. package/src/embedded-posthog-key.ts +6 -0
  75. package/src/error-telemetry.ts +247 -0
  76. package/src/evidence.ts +14 -0
  77. package/src/exception-telemetry.ts +126 -0
  78. package/src/fixtures/blocks/call_stack_diff.json +27 -0
  79. package/src/fixtures/blocks/callout.json +11 -0
  80. package/src/fixtures/blocks/code.json +9 -0
  81. package/src/fixtures/blocks/code_peek.json +8 -0
  82. package/src/fixtures/blocks/database_lens.json +51 -0
  83. package/src/fixtures/blocks/divider.json +1 -0
  84. package/src/fixtures/blocks/fixtures.ts +33 -0
  85. package/src/fixtures/blocks/flow_diagram.json +36 -0
  86. package/src/fixtures/blocks/ids.ts +8 -0
  87. package/src/fixtures/blocks/image.json +9 -0
  88. package/src/fixtures/blocks/markdown.json +7 -0
  89. package/src/fixtures/blocks/section.json +11 -0
  90. package/src/fixtures/blocks/sequence.json +31 -0
  91. package/src/fixtures/blocks/software_map.json +7 -0
  92. package/src/fixtures/blocks/trace_quote.json +9 -0
  93. package/src/fixtures/blocks/tutorial.json +51 -0
  94. package/src/fs-utils.ts +32 -0
  95. package/src/fuzzy-match.ts +94 -0
  96. package/src/install.ts +29 -0
  97. package/src/legacy-skills.ts +133 -0
  98. package/src/lens-selection.ts +230 -0
  99. package/src/markdown-latex-math.ts +230 -0
  100. package/src/markdown.ts +81 -0
  101. package/src/package-paths.ts +53 -0
  102. package/src/posthog-capture-client.ts +610 -0
  103. package/src/review-api/README.md +295 -0
  104. package/src/review-api/activity.ts +340 -0
  105. package/src/review-api/agent-cli.ts +219 -0
  106. package/src/review-api/agent-client.ts +188 -0
  107. package/src/review-api/anchor-quotes.ts +88 -0
  108. package/src/review-api/ask-history.ts +242 -0
  109. package/src/review-api/authoring-tools.ts +211 -0
  110. package/src/review-api/blocks/call_stack_diff.ts +75 -0
  111. package/src/review-api/blocks/callout.ts +24 -0
  112. package/src/review-api/blocks/code.ts +9 -0
  113. package/src/review-api/blocks/code_peek.ts +13 -0
  114. package/src/review-api/blocks/database_lens.ts +150 -0
  115. package/src/review-api/blocks/definition.ts +40 -0
  116. package/src/review-api/blocks/divider.ts +6 -0
  117. package/src/review-api/blocks/flow_diagram.ts +111 -0
  118. package/src/review-api/blocks/image.ts +10 -0
  119. package/src/review-api/blocks/index.ts +90 -0
  120. package/src/review-api/blocks/markdown.ts +17 -0
  121. package/src/review-api/blocks/section.ts +24 -0
  122. package/src/review-api/blocks/sequence.ts +56 -0
  123. package/src/review-api/blocks/software_map.ts +9 -0
  124. package/src/review-api/blocks/trace_quote.ts +10 -0
  125. package/src/review-api/blocks/tutorial.ts +39 -0
  126. package/src/review-api/checkout-fs.ts +14 -0
  127. package/src/review-api/client.ts +1 -0
  128. package/src/review-api/comparison-coverage.ts +304 -0
  129. package/src/review-api/component-reference.ts +30 -0
  130. package/src/review-api/diff-lenses.ts +175 -0
  131. package/src/review-api/document-headings.ts +51 -0
  132. package/src/review-api/document-text.ts +200 -0
  133. package/src/review-api/document.ts +906 -0
  134. package/src/review-api/file-lenses.ts +100 -0
  135. package/src/review-api/http.ts +1877 -0
  136. package/src/review-api/image-decode.ts +29 -0
  137. package/src/review-api/input-error.ts +9 -0
  138. package/src/review-api/instructions.ts +89 -0
  139. package/src/review-api/lens-alignment.ts +52 -0
  140. package/src/review-api/local-data.ts +1837 -0
  141. package/src/review-api/map-input.ts +154 -0
  142. package/src/review-api/mcp-client-agent.ts +32 -0
  143. package/src/review-api/mcp.ts +240 -0
  144. package/src/review-api/origin.ts +43 -0
  145. package/src/review-api/profile.ts +146 -0
  146. package/src/review-api/public-tools.ts +102 -0
  147. package/src/review-api/pull-request.ts +389 -0
  148. package/src/review-api/read-schemas.ts +85 -0
  149. package/src/review-api/recovery.ts +2 -0
  150. package/src/review-api/request-origin.ts +33 -0
  151. package/src/review-api/review-progress.ts +382 -0
  152. package/src/review-api/status-tool.ts +10 -0
  153. package/src/review-api/store-schema.ts +22 -0
  154. package/src/review-api/store.ts +1647 -0
  155. package/src/review-api/tool-failure.ts +64 -0
  156. package/src/review-api/trace-schema.ts +13 -0
  157. package/src/review-api/traces.ts +119 -0
  158. package/src/review-api/unsupported-files.integration.ts +91 -0
  159. package/src/review-api/workspaces.ts +692 -0
  160. package/src/review-api/worktree-source.ts +170 -0
  161. package/src/review-api/worktree-structural.integration.ts +320 -0
  162. package/src/review-app-launcher.ts +432 -0
  163. package/src/review-app-picker.ts +168 -0
  164. package/src/review-app.ts +134 -0
  165. package/src/review-bundled-tools.ts +249 -0
  166. package/src/review-checkout-paths.ts +37 -0
  167. package/src/review-diff-files.ts +89 -0
  168. package/src/review-head-checkout.ts +300 -0
  169. package/src/review-home-paths.ts +73 -0
  170. package/src/review-info.ts +59 -0
  171. package/src/review-instances.ts +113 -0
  172. package/src/review-logger.ts +183 -0
  173. package/src/review-preferences.ts +88 -0
  174. package/src/review-prepare.ts +291 -0
  175. package/src/review-stack.ts +112 -0
  176. package/src/review-telemetry.ts +1066 -0
  177. package/src/runtime.ts +74 -0
  178. package/src/server/account-alias.ts +30 -0
  179. package/src/server/bounded-stream.ts +34 -0
  180. package/src/server/bug-report.ts +301 -0
  181. package/src/server/client-error-budget.ts +45 -0
  182. package/src/server/crash-report.ts +273 -0
  183. package/src/server/desktop-host-shutdown.ts +64 -0
  184. package/src/server/desktop-host.ts +190 -0
  185. package/src/server/desktop-server.ts +703 -0
  186. package/src/server/diffr-config.ts +509 -0
  187. package/src/server/diffr-languages.ts +97 -0
  188. package/src/server/global-verb-relay.ts +190 -0
  189. package/src/server/headless-host.ts +149 -0
  190. package/src/server/hono-http.ts +162 -0
  191. package/src/server/http-json.ts +24 -0
  192. package/src/server/json-review-reporting.ts +174 -0
  193. package/src/server/process-error-telemetry.ts +174 -0
  194. package/src/server/review-api-parsers.ts +90 -0
  195. package/src/server/review-lifecycle-telemetry.ts +73 -0
  196. package/src/server/review-open-watchdog.ts +46 -0
  197. package/src/server/review-server-core.ts +290 -0
  198. package/src/server/structural-comparisons.ts +153 -0
  199. package/src/server/structural-diff.ts +186 -0
  200. package/src/server/tutorial-service.ts +241 -0
  201. package/src/server/ui-telemetry.ts +198 -0
  202. package/src/server-discovery.ts +95 -0
  203. package/src/session-markers.ts +132 -0
  204. package/src/sharing/auth.ts +41 -0
  205. package/src/sharing/cli.ts +83 -0
  206. package/src/sharing/client.ts +334 -0
  207. package/src/sharing/export.ts +195 -0
  208. package/src/sharing/host.ts +316 -0
  209. package/src/sharing/import.ts +787 -0
  210. package/src/sharing/index.ts +16 -0
  211. package/src/sharing/repository.ts +145 -0
  212. package/src/sharing/routes.ts +34 -0
  213. package/src/slug.ts +23 -0
  214. package/src/software-map-diff-counts.ts +517 -0
  215. package/src/software-map-model.ts +1147 -0
  216. package/src/software-map-topology-diff.ts +260 -0
  217. package/src/source.ts +92 -0
  218. package/src/startup-trace.ts +232 -0
  219. package/src/stored-document-migration.ts +151 -0
  220. package/src/telemetry-clean-text.ts +257 -0
  221. package/src/telemetry-config.ts +298 -0
  222. package/src/telemetry-debug-sink.ts +38 -0
  223. package/src/telemetry.ts +13 -0
  224. package/src/trace-cli.ts +156 -0
  225. package/src/trace-storage-cli.ts +509 -0
  226. package/src/tutorial-conversation.ts +18 -0
  227. package/src/ui-telemetry-events.ts +765 -0
  228. package/src/unified-diff.ts +71 -0
  229. package/src/viewed-coverage.ts +259 -0
  230. package/src/windows-cli.ts +125 -0
  231. package/tutorial/document.json +275 -0
  232. package/tutorial/runtime-manifest.json +12 -0
  233. package/tutorial/sample-service/package.json +9 -0
  234. package/tutorial/sample-service/src/api/checkout-api.ts +17 -0
  235. package/tutorial/sample-service/src/app.ts +26 -0
  236. package/tutorial/sample-service/src/database/schema.ts +10 -0
  237. package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +15 -0
  238. package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +24 -0
  239. package/tutorial/sample-service/src/inventory/inventory-service.ts +11 -0
  240. package/tutorial/sample-service/src/orders/order-service.ts +34 -0
  241. package/tutorial/sample-service/src/orders/order.ts +21 -0
  242. package/tutorial/sample-service/src/orders/orders-repository.ts +24 -0
  243. package/tutorial/sample-service/src/payments/payment-gateway.ts +12 -0
  244. package/tutorial/sample-service/src/shipping/shipping-gateway.ts +15 -0
  245. package/tutorial/sample-service/tsconfig.json +12 -0
  246. package/tutorial/software-map.json +139 -0
  247. package/tutorial/trace.json +20 -0
@@ -0,0 +1,382 @@
1
+ import {
2
+ type LensSource,
3
+ comparisonKey,
4
+ resolveDiffSelection,
5
+ selectionKey,
6
+ } from "@review/lens-selection.js";
7
+ import { type FileLineRange, fileLineRangeSchema } from "@review/source.js";
8
+ import { type CoverageFile, emptyCoverage } from "@review/viewed-coverage.js";
9
+ import { z } from "zod";
10
+
11
+ import type { ComparisonCoverage } from "./comparison-coverage.js";
12
+ import {
13
+ type DiffLens,
14
+ type Lens,
15
+ UNCATEGORIZED_LENS_ID,
16
+ lensSelections,
17
+ } from "./diff-lenses.js";
18
+ import { type Pins, anchorPins, selectionReferences } from "./document.js";
19
+ import { resolveFileLens, uncategorizedSources } from "./file-lenses.js";
20
+ import type { LocalReviewData } from "./local-data.js";
21
+ import type { ReviewStore, Snapshot } from "./store.js";
22
+
23
+ export const coverageModeSchema = z
24
+ .enum(["structural", "textual"])
25
+ .default("structural");
26
+
27
+ export const progressUpdateSchema = z.strictObject({
28
+ mode: coverageModeSchema,
29
+ version: z.number().int().nonnegative(),
30
+ files: z
31
+ .array(
32
+ z.strictObject({
33
+ path: z.string(),
34
+ fingerprint: z.string().min(1),
35
+ sources: z.array(fileLineRangeSchema),
36
+ }),
37
+ )
38
+ .max(10000),
39
+ viewed: z.boolean(),
40
+ });
41
+
42
+ export interface ReviewProgress {
43
+ complete?: boolean;
44
+ unavailableSelections?: Record<string, string>;
45
+ files: CoverageFile[];
46
+ /** Changed files of the comparisons that references with their own pins
47
+ * named, by `comparisonKey` and then path. They are not the document's
48
+ * comparison, so they are not in `files`; a node attached to one still
49
+ * counts its changed lines, under its own pins only. A comparison that
50
+ * changed nothing at a cited path has an entry with no file for it. */
51
+ referenceFiles?: Record<string, Record<string, CoverageFile>>;
52
+ resolvedSelections: Record<string, FileLineRange[]>;
53
+ /** The review's file lenses, then the automatic "Uncategorized changes". */
54
+ lenses: (DiffLens & { unavailable?: string; pending?: boolean })[];
55
+ /** Untracked files a live worktree review leaves out of its comparison. */
56
+ untrackedFiles?: number;
57
+ }
58
+
59
+ /** Coverage survives new pins only when both complete file contents are unchanged. */
60
+ export async function reviewProgress(
61
+ store: ReviewStore,
62
+ data: LocalReviewData,
63
+ snapshot: Snapshot,
64
+ signal: AbortSignal = new AbortController().signal,
65
+ mode: "structural" | "textual" = "structural",
66
+ partial?: ComparisonCoverage,
67
+ ): Promise<ReviewProgress> {
68
+ // A shared review is read-only and never enters the store, so it has no
69
+ // persisted marks to look up.
70
+ const marks: ReturnType<ReviewStore["viewedCoverage"]> = snapshot.shared
71
+ ? new Map()
72
+ : store.viewedCoverage(snapshot.reviewId);
73
+
74
+ const pins = snapshot.pins
75
+ ? (await data.resolveSource(snapshot)).pins
76
+ : undefined;
77
+
78
+ // A document without pins of its own has no changed files; its references
79
+ // each resolve against the comparison their own pins name.
80
+ const comparison: ComparisonCoverage = pins
81
+ ? (partial ?? (await data.coverage(snapshot.reviewId, pins, mode)))
82
+ : { files: [], fileSources: new Map(), alignments: new Map() };
83
+
84
+ signal.throwIfAborted();
85
+
86
+ const documentKey = JSON.stringify(pins);
87
+ const comparisons = new Map<string, Promise<ComparisonCoverage>>();
88
+
89
+ /** The comparison a reference's own pins name, shared across references. */
90
+ const ownComparison = (
91
+ own: Pins,
92
+ ): Promise<ComparisonCoverage> | undefined => {
93
+ const key = JSON.stringify(own);
94
+ let loading = comparisons.get(key);
95
+
96
+ if (loading) return loading;
97
+
98
+ if (partial) {
99
+ const state = data.coverageSnapshot(snapshot.reviewId, own, mode);
100
+
101
+ if (state.pending) return undefined;
102
+ }
103
+
104
+ loading = data.coverage(snapshot.reviewId, own, mode);
105
+ comparisons.set(key, loading);
106
+
107
+ return loading;
108
+ };
109
+
110
+ const withViewed = (file: CoverageFile): CoverageFile => ({
111
+ ...file,
112
+ viewed:
113
+ marks.get(file.path)?.fingerprint === file.fingerprint
114
+ ? marks.get(file.path)!.coverage
115
+ : emptyCoverage(),
116
+ });
117
+
118
+ const files = comparison.files.map(withViewed);
119
+ const referenceFiles: Record<string, Record<string, CoverageFile>> = {};
120
+
121
+ const fileSources = new Map(comparison.fileSources);
122
+ const alignments = new Map(comparison.alignments);
123
+ const resolvedSelections: Record<string, FileLineRange[]> = {};
124
+
125
+ const unavailableSelections: Record<string, string> = {};
126
+ const pendingSources = new Set<string>();
127
+
128
+ const resolve = async (source: LensSource): Promise<FileLineRange[]> => {
129
+ const key = selectionKey(source);
130
+
131
+ if (resolvedSelections[key]) return resolvedSelections[key];
132
+ const sourcePins = anchorPins(source, pins);
133
+ const own = JSON.stringify(sourcePins) !== documentKey;
134
+ let ownFiles = files;
135
+ let ownAlignments = alignments;
136
+
137
+ if (own) {
138
+ const loading = ownComparison(sourcePins);
139
+
140
+ if (!loading) {
141
+ pendingSources.add(key);
142
+
143
+ return [];
144
+ }
145
+
146
+ const loaded = await loading;
147
+ ownFiles = loaded.files;
148
+ ownAlignments = loaded.alignments;
149
+ }
150
+
151
+ const file = ownFiles.find(
152
+ (file) => source.file === file.path || source.file === file.previousPath,
153
+ );
154
+
155
+ if (own) {
156
+ const group = (referenceFiles[comparisonKey(sourcePins)] ??= {});
157
+
158
+ if (file) group[file.path] ??= withViewed(file);
159
+ }
160
+
161
+ let rows = file && ownAlignments.get(file.path);
162
+
163
+ // Context-only files are absent from the changed-file stream. Read both pins
164
+ // and require equality rather than inventing correspondence for missing diffs.
165
+ if (!file && partial && !own) {
166
+ pendingSources.add(key);
167
+
168
+ return [];
169
+ }
170
+
171
+ if (!file) {
172
+ const [base, head] = await Promise.all([
173
+ data.file(sourcePins, "base", source.file),
174
+ data.file(sourcePins, "head", source.file),
175
+ ]);
176
+
177
+ if (base.text !== head.text)
178
+ throw new Error("Changed context file has no alignment.");
179
+ rows = Array.from(
180
+ { length: base.text.split("\n").length },
181
+ (_, line) => [line, line] as const,
182
+ );
183
+ }
184
+
185
+ if (!rows) throw new Error("File has no text alignment.");
186
+
187
+ const result = resolveDiffSelection(
188
+ source,
189
+ rows,
190
+ file ?? { path: source.file },
191
+ );
192
+
193
+ resolvedSelections[key] = result;
194
+
195
+ return result;
196
+ };
197
+
198
+ const resolveAvailable = async (source: LensSource) => {
199
+ try {
200
+ return await resolve(source);
201
+ } catch {
202
+ unavailableSelections[selectionKey(source)] =
203
+ "Source links need updating at these pins";
204
+
205
+ return [];
206
+ }
207
+ };
208
+
209
+ const lenses: ReviewProgress["lenses"] = await Promise.all(
210
+ (snapshot.lenses ?? []).map(async (authored) => {
211
+ const lens = { id: authored.id, title: authored.title };
212
+
213
+ try {
214
+ // Explicit ranges use the same pin validation as diagram evidence.
215
+ await Promise.all(
216
+ lensSelections([authored]).map(({ source }) =>
217
+ resolveAvailable(source),
218
+ ),
219
+ );
220
+
221
+ const resolved = resolveFileLens(
222
+ authored,
223
+ files,
224
+ fileSources,
225
+ (source) => resolvedSelections[selectionKey(source)] ?? [],
226
+ );
227
+
228
+ return {
229
+ ...lens,
230
+ ...resolved,
231
+ pending: !!partial,
232
+ unavailable:
233
+ partial || resolved.fileCount
234
+ ? undefined
235
+ : "No files match these targets",
236
+ };
237
+ } catch {
238
+ return {
239
+ ...lens,
240
+ sources: [],
241
+ unavailable: "Source links need updating at these pins",
242
+ };
243
+ }
244
+ }),
245
+ );
246
+
247
+ // Peeks share alignment resolution without claiming lens coverage.
248
+ await Promise.all(
249
+ selectionReferences(snapshot.document, { tolerant: true }).map(
250
+ async ({ source }) => {
251
+ try {
252
+ await resolveAvailable(source);
253
+ } catch {
254
+ /* An unavailable peek does not hide other content. */
255
+ }
256
+ },
257
+ ),
258
+ );
259
+
260
+ const uncategorized = partial
261
+ ? []
262
+ : uncategorizedSources(
263
+ files,
264
+ lenses
265
+ .filter((lens) => !lens.unavailable)
266
+ .flatMap((lens) => lens.sources),
267
+ );
268
+
269
+ lenses.push({
270
+ id: UNCATEGORIZED_LENS_ID,
271
+ title: "Uncategorized changes",
272
+ sources: uncategorized,
273
+ wholeFiles: false,
274
+ fileCount: new Set(
275
+ uncategorized.map(
276
+ (source) =>
277
+ files.find(
278
+ (file) =>
279
+ source.file ===
280
+ (source.side === "base"
281
+ ? (file.previousPath ?? file.path)
282
+ : file.path),
283
+ )?.path ?? source.file,
284
+ ),
285
+ ).size,
286
+ pending: !!partial,
287
+ unavailable:
288
+ partial || uncategorized.length
289
+ ? undefined
290
+ : "All changed lines are covered by lenses",
291
+ });
292
+
293
+ const progress: ReviewProgress = {
294
+ complete: !partial,
295
+ files,
296
+ referenceFiles,
297
+ lenses,
298
+ resolvedSelections,
299
+ unavailableSelections,
300
+ };
301
+
302
+ const untrackedFiles = pins ? await data.untrackedFiles(pins) : undefined;
303
+
304
+ if (untrackedFiles) progress.untrackedFiles = untrackedFiles;
305
+
306
+ return progress;
307
+ }
308
+
309
+ /** The most files an uncategorized report names; the rest are counted. */
310
+ const REPORT_FILES = 50;
311
+
312
+ export interface UncategorizedReport {
313
+ /** Changed lines, counting each side, that no lens selects. */
314
+ lines: number;
315
+ files: {
316
+ path: string;
317
+ lines: number;
318
+ ranges: Pick<FileLineRange, "side" | "fromLine" | "toLine">[];
319
+ }[];
320
+ /** Files past the first REPORT_FILES, left out of `files`. */
321
+ moreFiles?: number;
322
+ }
323
+
324
+ /** What a lens author still has to place: the changed lines no lens selects,
325
+ * grouped by file. */
326
+ export function uncategorizedReport(
327
+ progress: ReviewProgress,
328
+ ): UncategorizedReport {
329
+ const sources =
330
+ progress.lenses.find((lens) => lens.id === UNCATEGORIZED_LENS_ID)
331
+ ?.sources ?? [];
332
+
333
+ const byPath = new Map<string, UncategorizedReport["files"][number]>();
334
+
335
+ for (const source of sources) {
336
+ const path =
337
+ progress.files.find(
338
+ (file) =>
339
+ source.file ===
340
+ (source.side === "base"
341
+ ? (file.previousPath ?? file.path)
342
+ : file.path),
343
+ )?.path ?? source.file;
344
+
345
+ const entry = byPath.get(path) ?? { path, lines: 0, ranges: [] };
346
+ entry.lines += source.toLine - source.fromLine + 1;
347
+ entry.ranges.push({
348
+ side: source.side,
349
+ fromLine: source.fromLine,
350
+ toLine: source.toLine,
351
+ });
352
+ byPath.set(path, entry);
353
+ }
354
+
355
+ const files = [...byPath.values()];
356
+
357
+ const report: UncategorizedReport = {
358
+ lines: files.reduce((total, file) => total + file.lines, 0),
359
+ files: files.slice(0, REPORT_FILES),
360
+ };
361
+
362
+ if (files.length > REPORT_FILES)
363
+ report.moreFiles = files.length - REPORT_FILES;
364
+
365
+ return report;
366
+ }
367
+
368
+ /** Each lens as authored, with what it resolves to, for a lens author. */
369
+ export function lensReport(lenses: readonly Lens[], progress: ReviewProgress) {
370
+ return {
371
+ lenses: lenses.map((lens) => {
372
+ const resolved = progress.lenses.find((item) => item.id === lens.id);
373
+
374
+ return {
375
+ ...lens,
376
+ fileCount: resolved?.fileCount ?? 0,
377
+ ...(resolved?.unavailable && { unavailable: resolved.unavailable }),
378
+ };
379
+ }),
380
+ uncategorized: uncategorizedReport(progress),
381
+ };
382
+ }
@@ -0,0 +1,10 @@
1
+ /** Listed by the MCP adapter even while no Desktop is reachable. Its public
2
+ * name is kept: it names the app instance, not a session. */
3
+ export const REVIEW_STATUS_TOOL = {
4
+ name: "whiteboard_status",
5
+ description:
6
+ "Name the Whiteboard instance this session talks to: key (stable, preview or dev-<checkout>), channel, checkout, appVersion, cliVersion, instanceId, url, home and desktopAvailable. Call it before changing anything when the user mentions Preview, a checkout or a dev build.",
7
+ inputSchema: { type: "object" as const, properties: {} },
8
+ method: "GET" as const,
9
+ path: "/status",
10
+ };
@@ -0,0 +1,22 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+
3
+ export function initializeReviewStoreSchema(db: DatabaseSync): void {
4
+ db.exec(`
5
+ PRAGMA journal_mode=WAL;
6
+ PRAGMA foreign_keys=ON;
7
+ CREATE TABLE IF NOT EXISTS reviews(id TEXT PRIMARY KEY, version INTEGER NOT NULL, next_id INTEGER NOT NULL);
8
+ CREATE TABLE IF NOT EXISTS versions(review_id TEXT REFERENCES reviews(id), version INTEGER, snapshot TEXT NOT NULL,
9
+ PRIMARY KEY(review_id,version));
10
+ DROP TABLE IF EXISTS receipts;
11
+ CREATE TABLE IF NOT EXISTS review_attention(review_id TEXT PRIMARY KEY REFERENCES reviews(id), viewed_at TEXT, dismissed_at TEXT);
12
+ CREATE TABLE IF NOT EXISTS repositories(id TEXT PRIMARY KEY, path TEXT NOT NULL UNIQUE, name TEXT NOT NULL);
13
+ CREATE TABLE IF NOT EXISTS resources(id TEXT PRIMARY KEY, repository_id TEXT NOT NULL REFERENCES repositories(id),
14
+ kind TEXT NOT NULL, mime_type TEXT NOT NULL, data BLOB NOT NULL);
15
+ CREATE TABLE IF NOT EXISTS review_coverage(review_id TEXT REFERENCES reviews(id), file TEXT, fingerprint TEXT NOT NULL, coverage TEXT NOT NULL,
16
+ PRIMARY KEY(review_id,file));
17
+ DROP TABLE IF EXISTS review_viewed;
18
+ CREATE TABLE IF NOT EXISTS comparison_stats(identity TEXT PRIMARY KEY, stats TEXT NOT NULL);
19
+ CREATE TABLE IF NOT EXISTS server_identity(one INTEGER PRIMARY KEY CHECK(one=1), id TEXT NOT NULL);
20
+ DROP TABLE IF EXISTS authoring_drafts;
21
+ `);
22
+ }