@erdemtuna/doc-review 0.7.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Yang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Doc Review
2
+
3
+ Edit HTML and Markdown files directly, leave comments like a Google Doc, and send all your feedback to your AI agent at once.
4
+
5
+ [Read the original Human Review launch post](https://creatoreconomy.so/p/use-my-human-review-skill-to-edit-html-markdown-visually)
6
+
7
+ https://github.com/user-attachments/assets/7cab09c9-eaa0-4e8b-984d-2925e810b5c2
8
+
9
+ ## Problem
10
+
11
+ Giving AI feedback on files in chat is painful.
12
+
13
+ Sometimes you want to change one sentence yourself. Instead, you end up typing:
14
+
15
+ > In the third paragraph, change X to Y. Cut the third card because it repeats the first one. Also rewrite the CTA.
16
+
17
+ Then the agent changes the file and you have to check whether it understood every instruction. This gets even harder when you’re reviewing a long plan, Markdown document, landing page, or multi-page website.
18
+
19
+ ## How to install /doc-review
20
+
21
+ The easiest way to install the skill is to paste this into ChatGPT, Claude Code, Codex, or your favorite coding agent:
22
+
23
+ ```text
24
+ Install the /doc-review skill globally from https://github.com/erdemtuna/doc-review
25
+ ```
26
+
27
+ You can also install it with `npx`:
28
+
29
+ ```sh
30
+ npx -y @erdemtuna/doc-review setup --global
31
+ ```
32
+
33
+ This fork uses the `doc-review` command, `/doc-review` skill, and
34
+ `~/.doc-review` state directory. It does not install a `human-review` alias or
35
+ migrate upstream state and skill files; remove those old files manually if you
36
+ no longer need them.
37
+
38
+ ## How to use /doc-review
39
+
40
+ ![Doc Review visual editor](assets/doc-review.png)
41
+
42
+ Open an HTML or Markdown file:
43
+
44
+ ```text
45
+ /doc-review (your file)
46
+ ```
47
+
48
+ Review a page running on localhost:
49
+
50
+ ```text
51
+ /doc-review (localhost URL)
52
+ ```
53
+
54
+ Doc Review opens the file in your browser. Make direct edits, leave comments, and click Send. Your agent receives all your feedback in one batch, updates the source, and refreshes the page for another review.
55
+
56
+ Note: For HTML files, direct edits and resizes save automatically. For Markdown and localhost pages, click Send so your agent can apply them to the source.
57
+
58
+ File and rendered Markdown reviews run with authored scripts and inline handlers
59
+ blocked, an opaque iframe origin, and no popup or download permission. Their
60
+ per-render message capability is rotated for every load and navigation. It is
61
+ kept out of document URLs, HTML attributes, authored DOM, and global JavaScript
62
+ state; the single-use artifact URL loads a same-origin bootstrap module under a
63
+ nonce-based CSP. Relative assets, including a same-artifact `<base>`, resolve
64
+ beside the reviewed file, while review navigation always stays relative to the
65
+ source file. External bases are ignored.
66
+
67
+ Localhost reviews keep `allow-same-origin`, popup, and download compatibility so
68
+ application behavior still works. Because localhost application scripts are
69
+ trusted in that mode, the render capability provides correlation and stale
70
+ message rejection rather than an authorization boundary. The authenticated
71
+ parent still validates links and never exposes the raw-file save route to a
72
+ localhost review.
73
+
74
+ Agent polling returns an immutable `batch_id`. After applying the batch, the
75
+ agent acknowledges that exact receipt and keeps waiting:
76
+
77
+ ```sh
78
+ npx -y @erdemtuna/doc-review poll path/to/file.html --ack b_0123456789abcdef --timeout 600
79
+ ```
80
+
81
+ A stale or repeated batch ID is harmless: it never clears newer feedback. The
82
+ complete acknowledgement command is included in each response's `next_step`.
83
+
84
+ ## What this skill lets you do
85
+
86
+ - **Edit text directly and tweak basic formatting** (e.g., bold, italic).
87
+ - **Make bulleted and numbered lists** — type `- ` or `1. ` at the start of a line, or press ⌘⇧8 / ⌘⇧7. Tab and Shift+Tab indent and outdent.
88
+ - **Add links** — select text and press ⌘K. ⌘K inside an existing link edits or removes it.
89
+ - **Resize images** by dragging their corner, and **move images** by dragging them to a new spot.
90
+ - **Rearrange the page** — hover any block and drag the handle on its left edge to move the whole block somewhere else.
91
+ - **Paste images** from your clipboard — file reviews save them beside the document; localhost reviews stage them for the agent to place in the app source.
92
+ - **Select a phrase and leave a comment** anchored to the exact text.
93
+ - **Comment on an image, chart, or section** by clicking the element.
94
+ - **Remove elements** without explaining the deletion in chat.
95
+ - **Command-click links** to review multiple pages without losing your feedback.
96
+ - **Send every edit and comment at once** instead of writing a long chat message.
97
+
98
+ Doc Review works well for editing AI-generated plans, updating landing pages, reviewing localhost apps, and removing extra copy from a UX.
99
+
100
+ ## What’s inside
101
+
102
+ - [`cli.js`](src/cli.js) contains the `doc-review`, `poll`, `status`, and `setup` commands.
103
+ - [`server.js`](src/server.js) runs the local review session.
104
+ - [`sdk.js`](src/sdk.js) handles editing, comments, highlights, and feedback.
105
+ - [`chrome-client.js`](src/chrome-client.js) contains the visual review interface.
106
+ - [`markdown.js`](src/markdown.js) renders Markdown files for review.
107
+ - [`SKILL.md`](src/SKILL.md) teaches Claude Code, Codex, and other agents how to use Doc Review.
108
+
109
+ Everything runs on your computer. Doc Review doesn’t require an account, cloud service, database, or API key.
110
+
111
+ ## Upstream project
112
+
113
+ Doc Review is an independent fork of [Human Review](https://github.com/petergyang/human-review), originally created by Peter Yang. The fork preserves the upstream MIT license and continues from the upstream `v0.6.1` release.
114
+
115
+ ## License
116
+
117
+ MIT
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@erdemtuna/doc-review",
3
+ "version": "0.7.0",
4
+ "description": "Review and edit agent-generated files and localhost pages in the browser, then send the whole batch back to your agent.",
5
+ "author": "Peter Yang",
6
+ "homepage": "https://github.com/erdemtuna/doc-review#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/erdemtuna/doc-review.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/erdemtuna/doc-review/issues"
13
+ },
14
+ "type": "module",
15
+ "bin": {
16
+ "doc-review": "src/cli.js"
17
+ },
18
+ "files": [
19
+ "src",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "scripts": {
27
+ "test": "npm run test:unit",
28
+ "test:unit": "node --test",
29
+ "test:browser": "playwright test",
30
+ "test:all": "npm run test:unit && npm run test:browser",
31
+ "start": "node src/cli.js"
32
+ },
33
+ "keywords": [
34
+ "html",
35
+ "markdown",
36
+ "localhost",
37
+ "nextjs",
38
+ "review",
39
+ "comments",
40
+ "agent",
41
+ "claude-code",
42
+ "codex",
43
+ "cli"
44
+ ],
45
+ "license": "MIT",
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org"
49
+ },
50
+ "devDependencies": {
51
+ "@playwright/test": "^1.62.1",
52
+ "jsdom": "^29.1.1"
53
+ },
54
+ "dependencies": {
55
+ "marked": "^18.0.7"
56
+ }
57
+ }
package/src/SKILL.md ADDED
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: doc-review
3
+ description: Open an HTML file, Markdown file, or localhost page in the browser so the user can edit text directly and leave comments on specific parts, then send all edits and comments back to you. Use after writing or updating something the user will read — specs, plans, reports, newsletter drafts, landing pages, slide decks, and locally running web pages.
4
+ ---
5
+
6
+ # doc-review
7
+
8
+ The user reviews your HTML, Markdown, or localhost page in a real browser: they fix small things
9
+ by typing, select anything to comment on it, and send you the whole batch at once.
10
+
11
+ Markdown files open rendered. Their quotes and edits reference the rendered text,
12
+ and the file itself is never touched — apply every change to the Markdown source,
13
+ keeping its formatting syntax.
14
+
15
+ ## The loop
16
+
17
+ 1. Write or update the HTML or Markdown file, or start the local page being reviewed.
18
+ 2. Open it for the user:
19
+
20
+ ```sh
21
+ npx -y @erdemtuna/doc-review path/to/file.html
22
+ ```
23
+
24
+ For a page served by a local development server, open the real route instead
25
+ of recreating it as a separate HTML file:
26
+
27
+ ```sh
28
+ npx -y @erdemtuna/doc-review http://localhost:3000/wiki
29
+ ```
30
+
31
+ 3. Wait for feedback. This blocks until they hit Send, or the timeout passes:
32
+
33
+ ```sh
34
+ npx -y @erdemtuna/doc-review poll path/to/file.html --timeout 600
35
+ ```
36
+
37
+ Keep this command in the foreground. Do not end your turn while it is waiting.
38
+ If your shell returns a process or session handle, keep waiting on that handle
39
+ until the command exits. If it prints `{"status":"timeout"}`, no feedback has
40
+ arrived yet — run the same poll command again to keep waiting. Feedback is
41
+ saved even if a poll dies, so nothing is ever lost.
42
+
43
+ If it prints `{"status":"closed"}`, the user ended the review from the
44
+ browser — stop polling and do not run the poll command again. Unsent
45
+ feedback is kept and ships the next time this target is reviewed.
46
+
47
+ 4. Apply what comes back, then wait again. Copy `batch_id` from the response;
48
+ only that exact receipt can clear the batch you handled:
49
+
50
+ ```sh
51
+ npx -y @erdemtuna/doc-review poll path/to/file.html --ack b_0123456789abcdef --timeout 600
52
+ ```
53
+
54
+ The response's `next_step` contains the complete acknowledgement command.
55
+ Never acknowledge a different or guessed ID.
56
+
57
+ Repeat 3–4 until the user says they are done.
58
+
59
+ Not sure whether feedback is already waiting — say, at the start of a new turn?
60
+ This answers instantly without blocking:
61
+
62
+ ```sh
63
+ npx -y @erdemtuna/doc-review status path/to/file.html
64
+ ```
65
+
66
+ It prints `{"status": "feedback-waiting"}` when a batch is ready for a poll,
67
+ plus counts of unsent comments and edits still in the browser.
68
+
69
+ ## What you get
70
+
71
+ One batch covers every page the user visited, grouped by file or localhost URL.
72
+
73
+ ```json
74
+ {
75
+ "batch_id": "b_0123456789abcdef",
76
+ "status": "feedback",
77
+ "pages": [
78
+ {
79
+ "file": "/abs/path/to/page.html",
80
+ "comments": [
81
+ { "id": "c_1", "kind": "selection", "quote": "the exact text they selected",
82
+ "anchor": { "prefix": "...", "quote": "...", "suffix": "..." },
83
+ "feedback": "what they want changed" }
84
+ ],
85
+ "edits": [
86
+ { "label": "Problem body", "kind": "edited",
87
+ "before": "the original wording",
88
+ "after": "their exact new wording",
89
+ "after_html": "their exact new wording with <strong>formatting</strong>" }
90
+ ]
91
+ }
92
+ ],
93
+ "overall_note": "feedback not tied to any one page",
94
+ "next_step": "Apply this feedback, then run: npx -y @erdemtuna/doc-review poll ... --ack b_0123456789abcdef --timeout 600"
95
+ }
96
+ ```
97
+
98
+ ## Rules
99
+
100
+ - **`edits` are changes the user already made.** `after` is their exact wording —
101
+ carry it across verbatim and never revert it. If the HTML was generated from
102
+ something else (MDX, Markdown, a template), apply `after` to the **source** too,
103
+ or their fix disappears on the next build.
104
+ - When `before_html`/`after_html` are present, the user changed formatting, not
105
+ just words — bold, italic, underline, links. Use the HTML version to carry the
106
+ formatting into the source, translated to its syntax (e.g. `<strong>` → `**`
107
+ in Markdown/MDX).
108
+ - A page with `kind: "url"` was edited directly in the review UI. Its `file`
109
+ and `url` fields name the localhost route, not a writable file. Find the
110
+ matching project source (such as MDX, TSX, or a template), apply every edit
111
+ and deletion there, then acknowledge so the route reloads. Never write the
112
+ rendered HTTP response back into the app.
113
+ - When an edit's `after_html` contains `<img src="assets/...">`, the user pasted
114
+ an image: the file already exists in an `assets/` folder next to the reviewed
115
+ file. Keep that relative path — in Markdown, reference it as
116
+ `![](assets/...)`. Never regenerate or inline the image.
117
+ - On a localhost page, a pasted image arrives under `staged_assets`. Copy its
118
+ local `path` into the app's appropriate asset folder, replace the temporary
119
+ preview URL in `after_html`, and preserve the image at the user's insertion
120
+ point. Never leave the temporary preview URL in source.
121
+ - An edit with `kind: "moved"` means the user relocated that whole block.
122
+ Reposition it in the source without rewriting its content: it now sits right
123
+ after the block whose text starts with `moved_after`, and right before the
124
+ block whose text starts with `moved_before`. An empty `moved_after` means it
125
+ is now the first block in its container.
126
+ - Find each comment by its `quote`; that exact string is in the file.
127
+ - `kind: "element"` points at a whole block, so `quote` is its label, not body text.
128
+ - Fix every page in `pages`, not just the first.
129
+ - **Do not write a reply.** There is no chat. The user sees your work when the page
130
+ reloads, which happens on its own the moment you save the file.
131
+
132
+ ## Better edit labels (optional)
133
+
134
+ Name the sections you author and the user's edit list uses your names instead of
135
+ guessing from the DOM:
136
+
137
+ ```html
138
+ <p data-block="Problem body">…</p>
139
+ <div data-container="Metrics callout">…</div>
140
+ ```
141
+
142
+ `data-block` names a region for the edit list. `data-container` also makes the block
143
+ clickable as a comment target.
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Quote-with-context anchoring, in the spirit of the W3C TextQuoteSelector.
3
+ *
4
+ * These functions are pure string maths so they can be unit-tested in Node and
5
+ * shipped verbatim to the browser, where the DOM half of anchoring lives.
6
+ */
7
+
8
+ export const CONTEXT_PAD = 32;
9
+
10
+ /** Capture `quote` plus surrounding context so it can be re-found after edits. */
11
+ export function buildContext(text, start, end, pad = CONTEXT_PAD) {
12
+ return {
13
+ prefix: text.slice(Math.max(0, start - pad), start),
14
+ quote: text.slice(start, end),
15
+ suffix: text.slice(end, Math.min(text.length, end + pad)),
16
+ };
17
+ }
18
+
19
+ function commonSuffixLength(a, b) {
20
+ let n = 0;
21
+ while (n < a.length && n < b.length && a[a.length - 1 - n] === b[b.length - 1 - n]) n += 1;
22
+ return n;
23
+ }
24
+
25
+ function commonPrefixLength(a, b) {
26
+ let n = 0;
27
+ while (n < a.length && n < b.length && a[n] === b[n]) n += 1;
28
+ return n;
29
+ }
30
+
31
+ function occurrences(text, quote) {
32
+ const found = [];
33
+ let at = text.indexOf(quote);
34
+ while (at !== -1) {
35
+ found.push(at);
36
+ at = text.indexOf(quote, at + 1);
37
+ }
38
+ return found;
39
+ }
40
+
41
+ function bestHit(text, hits, quote, prefix, suffix) {
42
+ let best = hits[0];
43
+ let bestScore = -1;
44
+ for (const at of hits) {
45
+ const before = text.slice(Math.max(0, at - prefix.length), at);
46
+ const after = text.slice(at + quote.length, at + quote.length + suffix.length);
47
+ const score = commonSuffixLength(prefix, before) + commonPrefixLength(suffix, after);
48
+ if (score > bestScore) {
49
+ bestScore = score;
50
+ best = at;
51
+ }
52
+ }
53
+ return { at: best, score: bestScore };
54
+ }
55
+
56
+ const collapse = (s) => String(s || "").replace(/\s+/g, " ");
57
+
58
+ /** Collapse whitespace runs, keeping a map from each kept char to its original index. */
59
+ function collapseWithMap(text) {
60
+ let flat = "";
61
+ const map = [];
62
+ let pendingWs = -1;
63
+ for (let i = 0; i < text.length; i += 1) {
64
+ if (/\s/.test(text[i])) {
65
+ if (pendingWs === -1) pendingWs = i;
66
+ continue;
67
+ }
68
+ if (pendingWs !== -1 && flat) {
69
+ flat += " ";
70
+ map.push(pendingWs);
71
+ }
72
+ pendingWs = -1;
73
+ flat += text[i];
74
+ map.push(i);
75
+ }
76
+ return { flat, map };
77
+ }
78
+
79
+ /**
80
+ * Locate `ctx.quote` in `text`, using prefix/suffix to disambiguate repeats.
81
+ * Returns `{ start, end, exact }` or null when the quote is gone entirely.
82
+ */
83
+ export function findQuote(text, ctx) {
84
+ const quote = ctx && ctx.quote;
85
+ if (!quote) return null;
86
+
87
+ const hits = occurrences(text, quote);
88
+ if (hits.length === 1) {
89
+ return { start: hits[0], end: hits[0] + quote.length, exact: true };
90
+ }
91
+ if (hits.length > 1) {
92
+ const { at, score } = bestHit(text, hits, quote, ctx.prefix || "", ctx.suffix || "");
93
+ return { start: at, end: at + quote.length, exact: score > 0 };
94
+ }
95
+
96
+ // Reformatting (a prettier run, an agent rewrite) reflows whitespace without
97
+ // changing any words. Match again on whitespace-collapsed text and map the
98
+ // hit back to real offsets, so those comments survive instead of orphaning.
99
+ const { flat, map } = collapseWithMap(text);
100
+ const flatQuote = collapse(quote).trim();
101
+ if (!flatQuote || !map.length) return null;
102
+ const flatHits = occurrences(flat, flatQuote);
103
+ if (!flatHits.length) return null;
104
+ const { at } = bestHit(flat, flatHits, flatQuote, collapse(ctx.prefix || ""), collapse(ctx.suffix || ""));
105
+ return { start: map[at], end: map[at + flatQuote.length - 1] + 1, exact: false };
106
+ }
107
+
108
+ /** Collapse runs of whitespace for display in a comment card. */
109
+ export function tidy(text, limit = 0) {
110
+ const flat = String(text == null ? "" : text)
111
+ .replace(/\s+/g, " ")
112
+ .trim();
113
+ if (!limit || flat.length <= limit) return flat;
114
+ return `${flat.slice(0, limit - 1).trimEnd()}…`;
115
+ }