visualproof 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 (146) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +99 -2
  3. package/dist/assets/fonts/JuliaMono-Regular.ttf +0 -0
  4. package/dist/assets/fonts/JuliaMono-RegularItalic.ttf +0 -0
  5. package/dist/assets/fonts/JuliaMono-SemiBold.ttf +0 -0
  6. package/dist/assets/fonts/LICENSE-JuliaMono.txt +93 -0
  7. package/dist/assets/latex/visualproof.sty +308 -0
  8. package/dist/assets/webfonts/LICENSE-Jost.txt +93 -0
  9. package/dist/assets/webfonts/LICENSE-SourceSans3.txt +93 -0
  10. package/dist/assets/webfonts/LICENSE-SourceSerif4.txt +93 -0
  11. package/dist/assets/webfonts/jost-latin-ext-wght-normal.woff2 +0 -0
  12. package/dist/assets/webfonts/jost-latin-wght-normal.woff2 +0 -0
  13. package/dist/assets/webfonts/source-sans-3-greek-wght-italic.woff2 +0 -0
  14. package/dist/assets/webfonts/source-sans-3-greek-wght-normal.woff2 +0 -0
  15. package/dist/assets/webfonts/source-sans-3-latin-ext-wght-italic.woff2 +0 -0
  16. package/dist/assets/webfonts/source-sans-3-latin-ext-wght-normal.woff2 +0 -0
  17. package/dist/assets/webfonts/source-sans-3-latin-wght-italic.woff2 +0 -0
  18. package/dist/assets/webfonts/source-sans-3-latin-wght-normal.woff2 +0 -0
  19. package/dist/assets/webfonts/source-serif-4-greek-wght-italic.woff2 +0 -0
  20. package/dist/assets/webfonts/source-serif-4-greek-wght-normal.woff2 +0 -0
  21. package/dist/assets/webfonts/source-serif-4-latin-ext-wght-italic.woff2 +0 -0
  22. package/dist/assets/webfonts/source-serif-4-latin-ext-wght-normal.woff2 +0 -0
  23. package/dist/assets/webfonts/source-serif-4-latin-wght-italic.woff2 +0 -0
  24. package/dist/assets/webfonts/source-serif-4-latin-wght-normal.woff2 +0 -0
  25. package/dist/extractor/Extract.lean +1180 -0
  26. package/dist/extractor/Usage.lean +900 -0
  27. package/dist/mcp/visualproof-mcp.mjs +476 -0
  28. package/dist/server/access.js +95 -0
  29. package/dist/server/agent.js +3270 -0
  30. package/dist/server/agentdocs.js +35 -0
  31. package/dist/server/agentlibrary.js +202 -0
  32. package/dist/server/agentnotes.js +701 -0
  33. package/dist/server/agentviews.js +703 -0
  34. package/dist/server/askfollow.js +286 -0
  35. package/dist/server/boardspots.js +159 -0
  36. package/dist/server/dirs.js +175 -0
  37. package/dist/server/doctor.js +186 -0
  38. package/dist/server/events.js +198 -0
  39. package/dist/server/export.js +420 -0
  40. package/dist/server/extract.js +204 -0
  41. package/dist/server/fetchurl.js +321 -0
  42. package/dist/server/figures.js +158 -0
  43. package/dist/server/history.js +780 -0
  44. package/dist/server/historyapi.js +304 -0
  45. package/dist/server/hover.js +595 -0
  46. package/dist/server/identity.js +114 -0
  47. package/dist/server/invites.js +241 -0
  48. package/dist/server/layoutthread.js +51 -0
  49. package/dist/server/library.js +600 -0
  50. package/dist/server/libraryroute.js +211 -0
  51. package/dist/server/live.js +1101 -0
  52. package/dist/server/lostfound.js +199 -0
  53. package/dist/server/main.js +1406 -0
  54. package/dist/server/mcptools.js +496 -0
  55. package/dist/server/pastdoc.js +73 -0
  56. package/dist/server/pipeline.js +159 -0
  57. package/dist/server/poppler.js +251 -0
  58. package/dist/server/presence.js +252 -0
  59. package/dist/server/project.js +222 -0
  60. package/dist/server/prompt.js +53 -0
  61. package/dist/server/readonly.js +152 -0
  62. package/dist/server/readonlygate.js +123 -0
  63. package/dist/server/renames.js +268 -0
  64. package/dist/server/review.js +334 -0
  65. package/dist/server/reviewapi.js +188 -0
  66. package/dist/server/snapshot.js +50 -0
  67. package/dist/server/sourcestore.js +473 -0
  68. package/dist/server/status.js +360 -0
  69. package/dist/server/talk.js +476 -0
  70. package/dist/server/tokens.js +221 -0
  71. package/dist/server/usage.js +501 -0
  72. package/dist/server/versions.js +82 -0
  73. package/dist/server/view.js +164 -0
  74. package/dist/server/views.js +1167 -0
  75. package/dist/shared/askfollow.js +46 -0
  76. package/dist/shared/board.js +332 -0
  77. package/dist/shared/build.js +2 -0
  78. package/dist/shared/collab.js +241 -0
  79. package/dist/shared/comment.js +285 -0
  80. package/dist/shared/context.js +157 -0
  81. package/dist/shared/doc/authors.js +98 -0
  82. package/dist/shared/doc/build.js +358 -0
  83. package/dist/shared/doc/latex.js +455 -0
  84. package/dist/shared/doc/markdown.js +159 -0
  85. package/dist/shared/doc/model.js +37 -0
  86. package/dist/shared/doc/order.js +124 -0
  87. package/dist/shared/doc/outline.js +45 -0
  88. package/dist/shared/doc/proof.js +272 -0
  89. package/dist/shared/doc/sections.js +93 -0
  90. package/dist/shared/doc/settings.js +75 -0
  91. package/dist/shared/edgecodec.js +102 -0
  92. package/dist/shared/editor.js +64 -0
  93. package/dist/shared/excerpt.js +87 -0
  94. package/dist/shared/graph.js +16 -0
  95. package/dist/shared/items.js +92 -0
  96. package/dist/shared/keys.js +203 -0
  97. package/dist/shared/layout.js +926 -0
  98. package/dist/shared/lexer.js +130 -0
  99. package/dist/shared/library.js +711 -0
  100. package/dist/shared/lostfound.js +150 -0
  101. package/dist/shared/moves.js +244 -0
  102. package/dist/shared/notecard.js +295 -0
  103. package/dist/shared/noteplace.js +505 -0
  104. package/dist/shared/palette.js +46 -0
  105. package/dist/shared/pastgraph.js +103 -0
  106. package/dist/shared/places.js +80 -0
  107. package/dist/shared/projectnotes.js +244 -0
  108. package/dist/shared/query.js +818 -0
  109. package/dist/shared/review.js +642 -0
  110. package/dist/shared/reviewqueue.js +333 -0
  111. package/dist/shared/ring.js +366 -0
  112. package/dist/shared/scope.js +255 -0
  113. package/dist/shared/scopeedit.js +81 -0
  114. package/dist/shared/search.js +191 -0
  115. package/dist/shared/separate.js +178 -0
  116. package/dist/shared/sharesnap.js +281 -0
  117. package/dist/shared/sticky.js +501 -0
  118. package/dist/shared/timeline.js +73 -0
  119. package/dist/shared/titles.js +300 -0
  120. package/dist/shared/viewgraph.js +157 -0
  121. package/dist/shared/views.js +859 -0
  122. package/dist/web/bundle/app.js +52671 -0
  123. package/dist/web/bundle/edges.js +407 -0
  124. package/dist/web/bundle/layout.js +1010 -0
  125. package/dist/web/bundle/pdfworker.js +48322 -0
  126. package/dist/web/index.html +257 -0
  127. package/dist/web/src/motion.js +430 -0
  128. package/dist/web/src/past.js +224 -0
  129. package/dist/web/src/scene.js +804 -0
  130. package/dist/web/style.css +2548 -0
  131. package/docs/agent-api/README.md +91 -0
  132. package/docs/agent-api/advice.md +108 -0
  133. package/docs/agent-api/export.md +120 -0
  134. package/docs/agent-api/following.md +140 -0
  135. package/docs/agent-api/history.md +142 -0
  136. package/docs/agent-api/library.md +65 -0
  137. package/docs/agent-api/mcp.md +104 -0
  138. package/docs/agent-api/notes.md +93 -0
  139. package/docs/agent-api/reading.md +166 -0
  140. package/docs/agent-api/requests.md +94 -0
  141. package/docs/agent-api/talking.md +100 -0
  142. package/docs/agent-api/tcb.md +135 -0
  143. package/docs/agent-api/views.md +125 -0
  144. package/docs/agent-api/words.md +56 -0
  145. package/package.json +39 -4
  146. package/test/toolchains.json +5 -0
@@ -0,0 +1,360 @@
1
+ // The compilation status of every project file, read from the outside.
2
+ //
3
+ // What Lake leaves behind, as observed on Lake 4.33 (see docs/LAKE.md):
4
+ // - a successful build writes Foo.olean and Foo.trace; the trace's `log` keeps the warnings,
5
+ // `declaration uses 'sorry'` among them;
6
+ // - a failed build deletes Foo.olean and leaves the previous Foo.trace untouched; the error
7
+ // text is printed and not stored anywhere;
8
+ // - modules downstream of a failure are not rebuilt and keep their old .olean;
9
+ // - there is no build lock. A compile in progress is a `lean … -o <libDir>/Foo.olean` process;
10
+ // - the trace records the hash of the source it was built from. Lake rebuilds when that hash
11
+ // changes, not when the mtime does, so "stale" here compares the same hash (`lakeTextHash`).
12
+ import { execFile } from "node:child_process";
13
+ import { existsSync, readFileSync, statSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ const IMPORT_RE = /^\s*(?:public\s+|private\s+)?(?:meta\s+)?import\s+(?:all\s+)?([^\s-]+)/;
16
+ /** The imports in a file's header: everything up to the first line that is not blank, a
17
+ * comment, `module`, `prelude` or an import. */
18
+ export function parseImports(text) {
19
+ const out = [];
20
+ let inBlock = false;
21
+ for (const raw of text.split("\n")) {
22
+ // strip comments: block comments may span lines, line comments end the line
23
+ let rest = raw;
24
+ let code = "";
25
+ while (rest) {
26
+ if (inBlock) {
27
+ const end = rest.indexOf("-/");
28
+ if (end < 0)
29
+ break;
30
+ inBlock = false;
31
+ rest = rest.slice(end + 2);
32
+ continue;
33
+ }
34
+ const block = rest.indexOf("/-");
35
+ const line = rest.indexOf("--");
36
+ if (line >= 0 && (block < 0 || line < block)) {
37
+ code += rest.slice(0, line);
38
+ break;
39
+ }
40
+ if (block >= 0) {
41
+ code += rest.slice(0, block);
42
+ rest = rest.slice(block + 2);
43
+ inBlock = true;
44
+ continue;
45
+ }
46
+ code += rest;
47
+ break;
48
+ }
49
+ const line = code.trim();
50
+ if (!line || line === "module" || line === "prelude")
51
+ continue;
52
+ const m = IMPORT_RE.exec(line);
53
+ if (!m)
54
+ break;
55
+ out.push(m[1]);
56
+ }
57
+ return out;
58
+ }
59
+ /** Project modules reachable from the roots, by their source headers: this works even when the
60
+ * build is broken, which is when it matters. */
61
+ export function scanModules(p) {
62
+ const mods = new Map();
63
+ const todo = [...p.roots];
64
+ while (todo.length) {
65
+ const name = todo.pop();
66
+ if (mods.has(name))
67
+ continue;
68
+ const file = p.files.get(name);
69
+ if (!file)
70
+ continue; // outside the project
71
+ let imports = [];
72
+ try {
73
+ imports = parseImports(readFileSync(join(p.root, file), "utf8"));
74
+ }
75
+ catch {
76
+ // deleted between the scan and now: it has no imports
77
+ }
78
+ const own = imports.filter((m) => p.files.has(m));
79
+ mods.set(name, { name, file, imports: own });
80
+ todo.push(...own);
81
+ }
82
+ return mods;
83
+ }
84
+ /** Modules a lean process is compiling right now, for this project's build directory. */
85
+ export function compiling(p) {
86
+ return new Promise((resolve) => {
87
+ execFile("ps", ["axo", "command"], { maxBuffer: 16 << 20 }, (err, stdout) => {
88
+ const out = new Set();
89
+ if (err)
90
+ return resolve(out);
91
+ const marker = ` -o ${p.libDir}/`;
92
+ for (const line of stdout.split("\n")) {
93
+ const at = line.indexOf(marker);
94
+ if (at < 0)
95
+ continue;
96
+ const rest = line.slice(at + marker.length);
97
+ const path = rest.slice(0, rest.indexOf(".olean"));
98
+ if (path)
99
+ out.add(path.split("/").join("."));
100
+ }
101
+ resolve(out);
102
+ });
103
+ });
104
+ }
105
+ const M = 0xc6a4a7935bd1e995n;
106
+ const MASK = (1n << 64n) - 1n;
107
+ /** Lean's `String.hash`: MurmurHash64A over the UTF-8 bytes, seed 11. */
108
+ function murmur64(buf, seed) {
109
+ let h = (seed ^ (BigInt(buf.length) * M)) & MASK;
110
+ const n8 = buf.length & ~7;
111
+ for (let i = 0; i < n8; i += 8) {
112
+ let k = buf.readBigUInt64LE(i);
113
+ k = (k * M) & MASK;
114
+ k ^= k >> 47n;
115
+ k = (k * M) & MASK;
116
+ h ^= k;
117
+ h = (h * M) & MASK;
118
+ }
119
+ const rem = buf.length & 7;
120
+ if (rem) {
121
+ for (let j = rem - 1; j >= 0; j--)
122
+ h ^= BigInt(buf[n8 + j]) << BigInt(8 * j);
123
+ h = (h * M) & MASK;
124
+ }
125
+ h ^= h >> 47n;
126
+ h = (h * M) & MASK;
127
+ h ^= h >> 47n;
128
+ return h;
129
+ }
130
+ /** `lean_uint64_mix_hash` from lean.h. */
131
+ function mixHash(h, k) {
132
+ k = (k * M) & MASK;
133
+ k ^= k >> 47n;
134
+ k ^= M;
135
+ h ^= k;
136
+ return (h * M) & MASK;
137
+ }
138
+ /** Lake's `Hash.ofText`: line endings normalised, then `mixHash nil (hash s)` with nil = 1723. */
139
+ export function lakeTextHash(text) {
140
+ const h = mixHash(1723n, murmur64(Buffer.from(text.replace(/\r\n/g, "\n"), "utf8"), 11n));
141
+ return h.toString(16).padStart(16, "0");
142
+ }
143
+ const srcHashCache = new Map();
144
+ /** The Lake hash of a source file, recomputed only when its mtime or size moves. */
145
+ function sourceHash(file) {
146
+ try {
147
+ const st = statSync(file);
148
+ const key = `${st.mtimeMs}:${st.size}`;
149
+ const hit = srcHashCache.get(file);
150
+ if (hit && hit.key === key)
151
+ return hit.hash;
152
+ const hash = lakeTextHash(readFileSync(file, "utf8"));
153
+ srcHashCache.set(file, { key, hash });
154
+ return hash;
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ }
160
+ function mtime(f) {
161
+ try {
162
+ return statSync(f).mtimeMs;
163
+ }
164
+ catch {
165
+ return null;
166
+ }
167
+ }
168
+ const traceCache = new Map();
169
+ function readTrace(file, at, source) {
170
+ const hit = traceCache.get(file);
171
+ if (hit && hit.mtime === at)
172
+ return hit;
173
+ const v = { mtime: at, warnings: 0, sorry: false, builtFrom: null };
174
+ try {
175
+ const t = JSON.parse(readFileSync(file, "utf8"));
176
+ for (const e of (t.log ?? [])) {
177
+ if (e.level === "warning" || e.level === "error")
178
+ v.warnings++;
179
+ if (/declaration uses '?`?sorry/.test(e.message))
180
+ v.sorry = true;
181
+ }
182
+ for (const inp of t.inputs ?? []) {
183
+ if (Array.isArray(inp) && typeof inp[0] === "string" && inp[0].endsWith(source))
184
+ v.builtFrom = inp[1];
185
+ }
186
+ }
187
+ catch {
188
+ // a trace being written: read it next time
189
+ }
190
+ traceCache.set(file, v);
191
+ return v;
192
+ }
193
+ function probe(p, m) {
194
+ const base = join(p.libDir, ...m.name.split("."));
195
+ const trace = mtime(base + ".trace");
196
+ const t = trace !== null ? readTrace(base + ".trace", trace, "/" + m.file) : { warnings: 0, sorry: false, builtFrom: null };
197
+ return { olean: mtime(base + ".olean"), trace, source: sourceHash(join(p.root, m.file)), ...t };
198
+ }
199
+ /** The Lake hash of the text a module's present build was compiled from, if it has a build: what
200
+ * the graph says about the module's declarations (their places in the file among them) is about
201
+ * that text. */
202
+ export function compiledTextHash(p, module, file) {
203
+ const base = join(p.libDir, ...module.split("."));
204
+ const trace = mtime(base + ".trace");
205
+ if (trace === null || mtime(base + ".olean") === null)
206
+ return null;
207
+ return readTrace(base + ".trace", trace, "/" + file).builtFrom;
208
+ }
209
+ /** A fingerprint of the build outputs, cheap enough to take every second. */
210
+ export function outputStamp(p, mods) {
211
+ const parts = [];
212
+ for (const m of mods.values()) {
213
+ const base = join(p.libDir, ...m.name.split("."));
214
+ parts.push(`${m.name}:${mtime(base + ".olean")}:${mtime(base + ".trace")}`);
215
+ }
216
+ return parts.join("|");
217
+ }
218
+ /** The same for the sources: an edit is not a build, but it changes what the status says. */
219
+ export function sourceStamp(p, mods) {
220
+ const parts = [];
221
+ for (const m of mods.values())
222
+ parts.push(`${m.name}:${mtime(join(p.root, m.file))}`);
223
+ return parts.join("|");
224
+ }
225
+ export class BuildTracker {
226
+ p;
227
+ /** when outputs were last seen changing, to bridge the gaps between compile processes */
228
+ lastChange = 0;
229
+ lastStamp = "";
230
+ buildStart = 0;
231
+ expected = null;
232
+ constructor(p) {
233
+ this.p = p;
234
+ }
235
+ async status(mods) {
236
+ const p = this.p;
237
+ const now = Date.now();
238
+ const running = await compiling(p);
239
+ // only outputs moving count as a build: an edit to a source is not one
240
+ const st = outputStamp(p, mods);
241
+ if (st !== this.lastStamp) {
242
+ if (this.lastStamp)
243
+ this.lastChange = now;
244
+ this.lastStamp = st;
245
+ }
246
+ const active = running.size > 0 || now - this.lastChange < 2500;
247
+ const probes = new Map();
248
+ for (const m of mods.values())
249
+ probes.set(m.name, probe(p, m));
250
+ // own state first, then what imports say about it
251
+ const own = new Map();
252
+ for (const m of mods.values()) {
253
+ const pr = probes.get(m.name);
254
+ if (running.has(m.name))
255
+ own.set(m.name, "building");
256
+ else if (pr.olean === null)
257
+ own.set(m.name, pr.trace !== null ? "failed" : "unbuilt");
258
+ else if (pr.builtFrom !== null && pr.source !== null && pr.builtFrom !== pr.source)
259
+ own.set(m.name, "stale");
260
+ else
261
+ own.set(m.name, pr.sorry ? "sorry" : "ok");
262
+ }
263
+ const broken = (s) => s === "failed" || s === "unbuilt";
264
+ const changed = (s) => s === "stale" || s === "building";
265
+ // a file is blocked by any broken file under it, and out of date if a file under it changed
266
+ // since its own build: Lake will rebuild it once the one underneath is rebuilt
267
+ const memo = new Map();
268
+ const below = (name, seen) => {
269
+ const hit = memo.get(name);
270
+ if (hit)
271
+ return hit;
272
+ const out = { broken: new Set(), changed: new Set() };
273
+ if (seen.has(name))
274
+ return out;
275
+ seen.add(name);
276
+ for (const i of mods.get(name)?.imports ?? []) {
277
+ if (broken(own.get(i)))
278
+ out.broken.add(i);
279
+ if (changed(own.get(i)))
280
+ out.changed.add(i);
281
+ const sub = below(i, seen);
282
+ for (const b of sub.broken)
283
+ out.broken.add(b);
284
+ for (const c of sub.changed)
285
+ out.changed.add(c);
286
+ }
287
+ memo.set(name, out);
288
+ return out;
289
+ };
290
+ const files = [];
291
+ for (const m of mods.values()) {
292
+ const pr = probes.get(m.name);
293
+ let state = own.get(m.name);
294
+ const under = below(m.name, new Set());
295
+ let because;
296
+ if (state === "ok" || state === "sorry" || state === "stale") {
297
+ if (under.broken.size) {
298
+ state = "blocked";
299
+ because = [...under.broken].sort();
300
+ }
301
+ else if (under.changed.size && state !== "stale") {
302
+ state = "stale";
303
+ because = [...under.changed].sort();
304
+ }
305
+ }
306
+ if (active && (state === "stale" || state === "blocked"))
307
+ state = "queued";
308
+ files.push({
309
+ module: m.name,
310
+ file: m.file,
311
+ state,
312
+ warnings: pr.warnings,
313
+ ...(because ? { because } : {}),
314
+ ...(pr.olean !== null ? { builtAt: Math.round(pr.olean) } : {}),
315
+ });
316
+ }
317
+ // progress: when a build starts, remember what it has to do; count what it has done since
318
+ let progress = null;
319
+ if (active) {
320
+ if (!this.expected) {
321
+ this.buildStart = now - 3000;
322
+ this.expected = new Set(files.filter((f) => f.state !== "ok" && f.state !== "sorry").map((f) => f.module));
323
+ }
324
+ for (const f of files)
325
+ if (f.state === "building" || f.state === "queued")
326
+ this.expected.add(f.module);
327
+ let done = 0;
328
+ for (const name of this.expected) {
329
+ const o = probes.get(name)?.olean ?? 0;
330
+ if (o >= this.buildStart && !running.has(name))
331
+ done++;
332
+ }
333
+ progress = { done, total: this.expected.size, current: [...running].sort() };
334
+ }
335
+ else {
336
+ this.expected = null;
337
+ }
338
+ const counts = { ok: 0, sorry: 0, failed: 0, unbuilt: 0, building: 0, queued: 0, stale: 0, blocked: 0 };
339
+ for (const f of files)
340
+ counts[f.state]++;
341
+ return { active, progress, files, counts };
342
+ }
343
+ }
344
+ /** The modules that can be imported right now: built, with everything under them built. */
345
+ export function importable(p, mods) {
346
+ const ok = new Map();
347
+ const check = (name, seen) => {
348
+ const hit = ok.get(name);
349
+ if (hit !== undefined)
350
+ return hit;
351
+ if (seen.has(name))
352
+ return true;
353
+ seen.add(name);
354
+ const has = existsSync(join(p.libDir, ...name.split(".")) + ".olean");
355
+ const res = has && (mods.get(name)?.imports ?? []).every((i) => check(i, seen));
356
+ ok.set(name, res);
357
+ return res;
358
+ };
359
+ return [...mods.keys()].filter((m) => check(m, new Set()));
360
+ }