@erdemtuna/doc-review 0.9.0 → 0.11.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 (103) hide show
  1. package/README.md +58 -222
  2. package/{src → lib}/SKILL.md +5 -2
  3. package/lib/anchor-text.js +155 -0
  4. package/lib/atomic-write.js +54 -0
  5. package/lib/changes-controller.js +184 -0
  6. package/lib/chrome-api.js +78 -0
  7. package/lib/chrome-client.js +1700 -0
  8. package/lib/chrome-session.js +73 -0
  9. package/lib/chrome.html +53 -0
  10. package/lib/cli.js +255 -0
  11. package/lib/click-target.js +28 -0
  12. package/lib/comment-anchor.js +23 -0
  13. package/lib/comment-target.js +164 -0
  14. package/lib/comments-controller.js +109 -0
  15. package/lib/contextual-controller.js +33 -0
  16. package/lib/contracts/feedback.js +1 -0
  17. package/lib/contracts/frame.js +7 -0
  18. package/lib/contracts/history.js +1 -0
  19. package/lib/contracts/index.js +2 -0
  20. package/lib/contracts/page.js +23 -0
  21. package/lib/controller-store.js +96 -0
  22. package/lib/document-execution.js +115 -0
  23. package/lib/edit-limits.js +24 -0
  24. package/lib/editing.js +107 -0
  25. package/lib/execution-client.js +19 -0
  26. package/lib/feedback-controller.js +113 -0
  27. package/lib/feedback-panel-controller.js +161 -0
  28. package/lib/frame-channel.js +42 -0
  29. package/lib/frame-controller.js +313 -0
  30. package/lib/frame-host.js +136 -0
  31. package/lib/frame-policy.js +50 -0
  32. package/lib/history-client.js +112 -0
  33. package/lib/history-coordinator.js +217 -0
  34. package/lib/history-policy.js +43 -0
  35. package/lib/history-server.js +464 -0
  36. package/lib/html-transform.js +52 -0
  37. package/lib/icons.js +325 -0
  38. package/{src → lib}/markdown.js +25 -26
  39. package/lib/paths.js +83 -0
  40. package/lib/poll-transport.js +220 -0
  41. package/lib/positioning.js +97 -0
  42. package/lib/recovery-controller.js +123 -0
  43. package/lib/review-controller.js +93 -0
  44. package/lib/review-mode.js +66 -0
  45. package/lib/revision-diff.js +482 -0
  46. package/lib/revision-schema.js +235 -0
  47. package/lib/revision-store.js +190 -0
  48. package/lib/save-controller.js +303 -0
  49. package/lib/sdk.js +2629 -0
  50. package/lib/semantic-snapshot.js +268 -0
  51. package/lib/serialize.js +32 -0
  52. package/lib/server-entry.js +26 -0
  53. package/lib/server-lock.js +109 -0
  54. package/lib/server.js +1501 -0
  55. package/lib/setup-guidance.js +119 -0
  56. package/{src → lib}/setup.js +44 -55
  57. package/lib/state.js +733 -0
  58. package/lib/toolbar-controller.js +32 -0
  59. package/lib/ui/THIRD_PARTY_NOTICES.md +1254 -0
  60. package/lib/ui/chrome.css +4881 -0
  61. package/lib/ui/chrome.js +52 -0
  62. package/lib/view-identity.js +112 -0
  63. package/package.json +50 -14
  64. package/src/anchor-text.js +0 -160
  65. package/src/atomic-write.js +0 -53
  66. package/src/chrome-client.js +0 -2747
  67. package/src/chrome-session.js +0 -85
  68. package/src/chrome.css +0 -652
  69. package/src/chrome.html +0 -196
  70. package/src/cli.js +0 -271
  71. package/src/click-target.js +0 -26
  72. package/src/comment-anchor.js +0 -22
  73. package/src/comment-target.js +0 -164
  74. package/src/comparison-view.js +0 -337
  75. package/src/document-execution.js +0 -103
  76. package/src/edit-limits.js +0 -23
  77. package/src/editing.js +0 -99
  78. package/src/execution-client.js +0 -63
  79. package/src/frame-channel.js +0 -44
  80. package/src/frame-policy.js +0 -54
  81. package/src/history-client.js +0 -104
  82. package/src/history-coordinator.js +0 -186
  83. package/src/history-policy.js +0 -43
  84. package/src/history-server.js +0 -463
  85. package/src/html-transform.js +0 -62
  86. package/src/icons.js +0 -251
  87. package/src/paths.js +0 -91
  88. package/src/poll-transport.js +0 -222
  89. package/src/positioning.js +0 -107
  90. package/src/review-mode.js +0 -59
  91. package/src/revision-diff.js +0 -440
  92. package/src/revision-schema.js +0 -219
  93. package/src/revision-store.js +0 -177
  94. package/src/sdk.js +0 -2607
  95. package/src/semantic-snapshot.js +0 -230
  96. package/src/serialize.js +0 -32
  97. package/src/server-entry.js +0 -26
  98. package/src/server-lock.js +0 -101
  99. package/src/server.js +0 -1466
  100. package/src/setup-guidance.js +0 -128
  101. package/src/state.js +0 -732
  102. package/src/view-identity.js +0 -99
  103. /package/{src → lib}/document-trust.js +0 -0
package/lib/icons.js ADDED
@@ -0,0 +1,325 @@
1
+ // Generated by scripts/generate-icons.js from lucide-static. Do not edit.
2
+ export const ICON_NODES = Object.freeze({
3
+ "eye": [
4
+ [
5
+ "path",
6
+ {
7
+ "d": "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"
8
+ }
9
+ ],
10
+ [
11
+ "circle",
12
+ {
13
+ "cx": "12",
14
+ "cy": "12",
15
+ "r": "3"
16
+ }
17
+ ]
18
+ ],
19
+ "pencil": [
20
+ [
21
+ "path",
22
+ {
23
+ "d": "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
24
+ }
25
+ ],
26
+ [
27
+ "path",
28
+ {
29
+ "d": "m15 5 4 4"
30
+ }
31
+ ]
32
+ ],
33
+ "plus": [
34
+ [
35
+ "path",
36
+ {
37
+ "d": "M5 12h14"
38
+ }
39
+ ],
40
+ [
41
+ "path",
42
+ {
43
+ "d": "M12 5v14"
44
+ }
45
+ ]
46
+ ],
47
+ "minus": [
48
+ [
49
+ "path",
50
+ {
51
+ "d": "M5 12h14"
52
+ }
53
+ ]
54
+ ],
55
+ "locate": [
56
+ [
57
+ "line",
58
+ {
59
+ "x1": "2",
60
+ "x2": "5",
61
+ "y1": "12",
62
+ "y2": "12"
63
+ }
64
+ ],
65
+ [
66
+ "line",
67
+ {
68
+ "x1": "19",
69
+ "x2": "22",
70
+ "y1": "12",
71
+ "y2": "12"
72
+ }
73
+ ],
74
+ [
75
+ "line",
76
+ {
77
+ "x1": "12",
78
+ "x2": "12",
79
+ "y1": "2",
80
+ "y2": "5"
81
+ }
82
+ ],
83
+ [
84
+ "line",
85
+ {
86
+ "x1": "12",
87
+ "x2": "12",
88
+ "y1": "19",
89
+ "y2": "22"
90
+ }
91
+ ],
92
+ [
93
+ "circle",
94
+ {
95
+ "cx": "12",
96
+ "cy": "12",
97
+ "r": "7"
98
+ }
99
+ ]
100
+ ],
101
+ "chevronDown": [
102
+ [
103
+ "path",
104
+ {
105
+ "d": "m6 9 6 6 6-6"
106
+ }
107
+ ]
108
+ ],
109
+ "chevronRight": [
110
+ [
111
+ "path",
112
+ {
113
+ "d": "m9 18 6-6-6-6"
114
+ }
115
+ ]
116
+ ],
117
+ "messageSquarePlus": [
118
+ [
119
+ "path",
120
+ {
121
+ "d": "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
122
+ }
123
+ ],
124
+ [
125
+ "path",
126
+ {
127
+ "d": "M12 8v6"
128
+ }
129
+ ],
130
+ [
131
+ "path",
132
+ {
133
+ "d": "M9 11h6"
134
+ }
135
+ ]
136
+ ],
137
+ "messages": [
138
+ [
139
+ "path",
140
+ {
141
+ "d": "M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"
142
+ }
143
+ ],
144
+ [
145
+ "path",
146
+ {
147
+ "d": "M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1"
148
+ }
149
+ ]
150
+ ],
151
+ "send": [
152
+ [
153
+ "path",
154
+ {
155
+ "d": "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"
156
+ }
157
+ ],
158
+ [
159
+ "path",
160
+ {
161
+ "d": "m21.854 2.147-10.94 10.939"
162
+ }
163
+ ]
164
+ ],
165
+ "trash": [
166
+ [
167
+ "path",
168
+ {
169
+ "d": "M10 11v6"
170
+ }
171
+ ],
172
+ [
173
+ "path",
174
+ {
175
+ "d": "M14 11v6"
176
+ }
177
+ ],
178
+ [
179
+ "path",
180
+ {
181
+ "d": "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"
182
+ }
183
+ ],
184
+ [
185
+ "path",
186
+ {
187
+ "d": "M3 6h18"
188
+ }
189
+ ],
190
+ [
191
+ "path",
192
+ {
193
+ "d": "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
194
+ }
195
+ ]
196
+ ],
197
+ "moreHorizontal": [
198
+ [
199
+ "circle",
200
+ {
201
+ "cx": "12",
202
+ "cy": "12",
203
+ "r": "1"
204
+ }
205
+ ],
206
+ [
207
+ "circle",
208
+ {
209
+ "cx": "19",
210
+ "cy": "12",
211
+ "r": "1"
212
+ }
213
+ ],
214
+ [
215
+ "circle",
216
+ {
217
+ "cx": "5",
218
+ "cy": "12",
219
+ "r": "1"
220
+ }
221
+ ]
222
+ ],
223
+ "x": [
224
+ [
225
+ "path",
226
+ {
227
+ "d": "M18 6 6 18"
228
+ }
229
+ ],
230
+ [
231
+ "path",
232
+ {
233
+ "d": "m6 6 12 12"
234
+ }
235
+ ]
236
+ ],
237
+ "check": [
238
+ [
239
+ "path",
240
+ {
241
+ "d": "M20 6 9 17l-5-5"
242
+ }
243
+ ]
244
+ ],
245
+ "sun": [
246
+ [
247
+ "circle",
248
+ {
249
+ "cx": "12",
250
+ "cy": "12",
251
+ "r": "4"
252
+ }
253
+ ],
254
+ [
255
+ "path",
256
+ {
257
+ "d": "M12 2v2"
258
+ }
259
+ ],
260
+ [
261
+ "path",
262
+ {
263
+ "d": "M12 20v2"
264
+ }
265
+ ],
266
+ [
267
+ "path",
268
+ {
269
+ "d": "m4.93 4.93 1.41 1.41"
270
+ }
271
+ ],
272
+ [
273
+ "path",
274
+ {
275
+ "d": "m17.66 17.66 1.41 1.41"
276
+ }
277
+ ],
278
+ [
279
+ "path",
280
+ {
281
+ "d": "M2 12h2"
282
+ }
283
+ ],
284
+ [
285
+ "path",
286
+ {
287
+ "d": "M20 12h2"
288
+ }
289
+ ],
290
+ [
291
+ "path",
292
+ {
293
+ "d": "m6.34 17.66-1.41 1.41"
294
+ }
295
+ ],
296
+ [
297
+ "path",
298
+ {
299
+ "d": "m19.07 4.93-1.41 1.41"
300
+ }
301
+ ]
302
+ ],
303
+ "moon": [
304
+ [
305
+ "path",
306
+ {
307
+ "d": "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"
308
+ }
309
+ ]
310
+ ]
311
+ });
312
+ const escape = (value) => String(value).replace(/[&<>"]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[char]);
313
+ export function iconMarkup(name, { title = "", className = "icon", size = 16 } = {}) {
314
+ const nodes = ICON_NODES[name];
315
+ if (!nodes)
316
+ throw new Error(`Unknown icon: ${name}`);
317
+ const body = nodes.map(([tag, attrs]) => `<${tag} ${Object.entries(attrs).map(([key, value]) => `${key}="${escape(value)}"`).join(" ")}></${tag}>`).join("");
318
+ const label = title ? ` role="img" aria-label="${escape(title)}"` : ' aria-hidden="true"';
319
+ return `<svg class="${escape(className)}" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"${label}>${body}</svg>`;
320
+ }
321
+ export function createIcon(name, options = {}) {
322
+ const template = document.createElement("template");
323
+ template.innerHTML = iconMarkup(name, options);
324
+ return template.content.firstElementChild;
325
+ }
@@ -1,8 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { marked, Renderer } from "marked";
3
-
4
3
  export const isMarkdown = (file) => /\.(md|markdown)$/i.test(file);
5
-
6
4
  /**
7
5
  * Readable defaults for rendered Markdown. This HTML is a viewing surface
8
6
  * only — it is never written back to disk, so the styling can be opinionated.
@@ -35,45 +33,46 @@ const STYLE = `
35
33
  img { max-width: 100%; height: auto; }
36
34
  hr { border: none; border-top: 1px solid #eceae3; margin: 2.2em 0; }
37
35
  `;
38
-
39
- const escapeHtml = (value) =>
40
- String(value)
36
+ const escapeHtml = (value) => String(value)
41
37
  .replace(/&/g, "&amp;")
42
38
  .replace(/</g, "&lt;")
43
39
  .replace(/>/g, "&gt;");
44
-
45
40
  function safeUrl(value, { image = false } = {}) {
46
- const url = String(value || "").trim();
47
- const probe = url.replace(/[\u0000-\u0020\u007f]+/g, "");
48
- const match = /^([a-z][a-z0-9+.-]*):/i.exec(probe);
49
- if (!match) return url;
50
- const scheme = match[1].toLowerCase();
51
- if (scheme === "http" || scheme === "https") return url;
52
- if (!image && scheme === "mailto") return url;
53
- if (image && /^data:image\/(?:avif|gif|jpe?g|png|webp);base64,/i.test(probe)) return url;
54
- return null;
41
+ const url = String(value || "").trim();
42
+ const probe = url.replace(/[\u0000-\u0020\u007f]+/g, "");
43
+ const match = /^([a-z][a-z0-9+.-]*):/i.exec(probe);
44
+ if (!match)
45
+ return url;
46
+ const scheme = match[1].toLowerCase();
47
+ if (scheme === "http" || scheme === "https")
48
+ return url;
49
+ if (!image && scheme === "mailto")
50
+ return url;
51
+ if (image && /^data:image\/(?:avif|gif|jpe?g|png|webp);base64,/i.test(probe))
52
+ return url;
53
+ return null;
55
54
  }
56
-
57
55
  // Markdown can contain arbitrary HTML. Show that source as text so event
58
56
  // handlers, embeds, SVG, and future browser features can never become active.
59
57
  const INERT_RENDERER = new Renderer();
60
58
  INERT_RENDERER.html = ({ text }) => escapeHtml(text);
61
59
  INERT_RENDERER.link = function (token) {
62
- const href = safeUrl(token.href);
63
- if (!href) return this.parser.parseInline(token.tokens);
64
- return Renderer.prototype.link.call(this, { ...token, href });
60
+ const href = safeUrl(token.href);
61
+ if (!href)
62
+ return this.parser.parseInline(token.tokens);
63
+ return Renderer.prototype.link.call(this, { ...token, href });
65
64
  };
66
65
  INERT_RENDERER.image = function (token) {
67
- const href = safeUrl(token.href, { image: true });
68
- if (!href) return escapeHtml(token.text || "");
69
- return Renderer.prototype.image.call(this, { ...token, href });
66
+ const href = safeUrl(token.href, { image: true });
67
+ if (!href)
68
+ return escapeHtml(token.text || "");
69
+ return Renderer.prototype.image.call(this, { ...token, href });
70
70
  };
71
-
72
71
  /** Render a Markdown file into a standalone review page. */
73
72
  export function renderMarkdownPage(mdText, file) {
74
- const body = marked.parse(mdText, { gfm: true, async: false, renderer: INERT_RENDERER });
75
- const title = path.basename(file);
76
- return `<!DOCTYPE html>
73
+ const body = marked.parse(mdText, { gfm: true, async: false, renderer: INERT_RENDERER });
74
+ const title = path.basename(file);
75
+ return `<!DOCTYPE html>
77
76
  <html lang="en">
78
77
  <head>
79
78
  <meta charset="utf-8">
package/lib/paths.js ADDED
@@ -0,0 +1,83 @@
1
+ import { createHash } from "node:crypto";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
4
+ import fs from "node:fs";
5
+ // Bump this when the CLI and detached server no longer share the same request
6
+ // contract. A new CLI must not silently reuse an older background server.
7
+ export const SERVER_PROTOCOL = 17;
8
+ export function serverProtocolMatches(protocol) {
9
+ return Number(protocol) === SERVER_PROTOCOL;
10
+ }
11
+ export function stateDir() {
12
+ const override = process.env.DOC_REVIEW_STATE_DIR;
13
+ return override ? path.resolve(override) : path.join(homedir(), ".doc-review");
14
+ }
15
+ export function statePath() {
16
+ return path.join(stateDir(), "state.json");
17
+ }
18
+ export function serverPath() {
19
+ return path.join(stateDir(), "server.json");
20
+ }
21
+ export function serverLockPath() {
22
+ return path.join(stateDir(), "server.lock");
23
+ }
24
+ export function ensureStateDir() {
25
+ // Comments and the server token live here; keep it private to the user.
26
+ fs.mkdirSync(stateDir(), { recursive: true, mode: 0o700 });
27
+ }
28
+ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
29
+ /**
30
+ * Return a canonical loopback URL, or null when the input is a filesystem path.
31
+ * HTTP-looking non-loopback targets fail loudly instead of becoming odd paths.
32
+ */
33
+ export function localUrl(target) {
34
+ const value = String(target || "");
35
+ if (!/^https?:\/\//i.test(value))
36
+ return null;
37
+ let parsed;
38
+ try {
39
+ parsed = new URL(value);
40
+ }
41
+ catch {
42
+ throw new Error(`Invalid localhost URL: ${value}`);
43
+ }
44
+ if (!LOOPBACK_HOSTS.has(parsed.hostname)) {
45
+ throw new Error("doc-review URL support is limited to localhost, 127.0.0.1, and [::1].");
46
+ }
47
+ if (parsed.username || parsed.password) {
48
+ throw new Error("Localhost review URLs cannot contain credentials.");
49
+ }
50
+ parsed.hash = "";
51
+ return parsed.href;
52
+ }
53
+ /** Canonical identity for a page: the real path of the file on disk. */
54
+ export function pageKey(file) {
55
+ let real = path.resolve(file);
56
+ try {
57
+ real = fs.realpathSync(real);
58
+ }
59
+ catch {
60
+ // File may not exist yet; the resolved path is still a stable identity.
61
+ }
62
+ return createHash("sha256").update(real).digest("hex").slice(0, 16);
63
+ }
64
+ /** Stable identity for either an existing file target or a localhost URL. */
65
+ export function targetKey(target) {
66
+ const url = localUrl(target);
67
+ if (!url)
68
+ return pageKey(target);
69
+ return createHash("sha256").update(`url:${url}`).digest("hex").slice(0, 16);
70
+ }
71
+ export function canonicalTarget(target) {
72
+ const url = localUrl(target);
73
+ return url ? { kind: "url", value: url } : { kind: "file", value: realFile(target) };
74
+ }
75
+ export function realFile(file) {
76
+ const resolved = path.resolve(file);
77
+ try {
78
+ return fs.realpathSync(resolved);
79
+ }
80
+ catch {
81
+ return resolved;
82
+ }
83
+ }