@erdemtuna/doc-review 0.9.0 → 0.10.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/README.md +52 -227
- package/lib/anchor-text.js +155 -0
- package/lib/atomic-write.js +54 -0
- package/lib/chrome-api.js +78 -0
- package/lib/chrome-client.js +2255 -0
- package/lib/chrome-session.js +76 -0
- package/lib/cli.js +255 -0
- package/lib/click-target.js +28 -0
- package/lib/comment-anchor.js +23 -0
- package/lib/comment-target.js +164 -0
- package/lib/comparison-view.js +380 -0
- package/lib/contracts/feedback.js +1 -0
- package/lib/contracts/frame.js +7 -0
- package/lib/contracts/history.js +1 -0
- package/lib/contracts/index.js +2 -0
- package/lib/contracts/page.js +23 -0
- package/lib/document-execution.js +115 -0
- package/lib/edit-limits.js +24 -0
- package/lib/editing.js +107 -0
- package/lib/execution-client.js +69 -0
- package/lib/feedback-controller.js +106 -0
- package/lib/frame-channel.js +42 -0
- package/lib/frame-controller.js +313 -0
- package/lib/frame-host.js +136 -0
- package/lib/frame-policy.js +50 -0
- package/lib/history-client.js +112 -0
- package/lib/history-coordinator.js +210 -0
- package/lib/history-policy.js +43 -0
- package/lib/history-server.js +464 -0
- package/lib/html-transform.js +52 -0
- package/lib/icons.js +249 -0
- package/{src → lib}/markdown.js +25 -26
- package/lib/paths.js +83 -0
- package/lib/poll-transport.js +220 -0
- package/lib/positioning.js +97 -0
- package/lib/review-controller.js +93 -0
- package/lib/review-mode.js +66 -0
- package/lib/revision-diff.js +482 -0
- package/lib/revision-schema.js +235 -0
- package/lib/revision-store.js +190 -0
- package/lib/save-controller.js +300 -0
- package/lib/sdk.js +2629 -0
- package/lib/semantic-snapshot.js +268 -0
- package/lib/serialize.js +32 -0
- package/lib/server-entry.js +26 -0
- package/lib/server-lock.js +109 -0
- package/lib/server.js +1501 -0
- package/lib/setup-guidance.js +119 -0
- package/{src → lib}/setup.js +44 -55
- package/lib/state.js +733 -0
- package/lib/view-identity.js +112 -0
- package/package.json +24 -14
- package/src/anchor-text.js +0 -160
- package/src/atomic-write.js +0 -53
- package/src/chrome-client.js +0 -2747
- package/src/chrome-session.js +0 -85
- package/src/cli.js +0 -271
- package/src/click-target.js +0 -26
- package/src/comment-anchor.js +0 -22
- package/src/comment-target.js +0 -164
- package/src/comparison-view.js +0 -337
- package/src/document-execution.js +0 -103
- package/src/edit-limits.js +0 -23
- package/src/editing.js +0 -99
- package/src/execution-client.js +0 -63
- package/src/frame-channel.js +0 -44
- package/src/frame-policy.js +0 -54
- package/src/history-client.js +0 -104
- package/src/history-coordinator.js +0 -186
- package/src/history-policy.js +0 -43
- package/src/history-server.js +0 -463
- package/src/html-transform.js +0 -62
- package/src/icons.js +0 -251
- package/src/paths.js +0 -91
- package/src/poll-transport.js +0 -222
- package/src/positioning.js +0 -107
- package/src/review-mode.js +0 -59
- package/src/revision-diff.js +0 -440
- package/src/revision-schema.js +0 -219
- package/src/revision-store.js +0 -177
- package/src/sdk.js +0 -2607
- package/src/semantic-snapshot.js +0 -230
- package/src/serialize.js +0 -32
- package/src/server-entry.js +0 -26
- package/src/server-lock.js +0 -101
- package/src/server.js +0 -1466
- package/src/setup-guidance.js +0 -128
- package/src/state.js +0 -732
- package/src/view-identity.js +0 -99
- /package/{src → lib}/SKILL.md +0 -0
- /package/{src → lib}/chrome.css +0 -0
- /package/{src → lib}/chrome.html +0 -0
- /package/{src → lib}/document-trust.js +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export const GUIDANCE_BEGIN = "<!-- BEGIN doc-review -->";
|
|
2
|
+
export const GUIDANCE_END = "<!-- END doc-review -->";
|
|
3
|
+
// Historical generated text is deliberately independent of the current guidance.
|
|
4
|
+
const LEGACY_TAIL = `
|
|
5
|
+
\`npx -y @erdemtuna/doc-review <file.html>\`. For a locally running web page, open the real
|
|
6
|
+
route with \`npx -y @erdemtuna/doc-review http://localhost:3000/path\` instead of recreating
|
|
7
|
+
it as a static file. Then block on
|
|
8
|
+
\`npx -y @erdemtuna/doc-review poll <target> --timeout 600\` until they send feedback.
|
|
9
|
+
If it prints \`{"status":"timeout"}\`, no feedback arrived yet — run the same
|
|
10
|
+
poll command again to keep waiting. When a \`{"status":"feedback"}\` batch
|
|
11
|
+
arrives, apply it, then run the exact acknowledgement command in its
|
|
12
|
+
\`next_step\`, which uses \`--ack <batch_id>\`.
|
|
13
|
+
|
|
14
|
+
Keep the poll command in the foreground and do not end the turn while it waits.
|
|
15
|
+
If the shell returns a process or session handle, keep waiting on that handle until
|
|
16
|
+
the command exits. \`npx -y @erdemtuna/doc-review status <target>\` reports instantly
|
|
17
|
+
whether feedback is already waiting, without blocking.
|
|
18
|
+
|
|
19
|
+
The batch groups feedback by page under \`pages\`, so fix every page listed. Items
|
|
20
|
+
under \`edits\` are changes the user already made: \`after\` is their exact wording,
|
|
21
|
+
so carry it across verbatim and never revert it — and if the HTML was generated
|
|
22
|
+
from MDX or Markdown, apply it to the source too. Markdown files open rendered
|
|
23
|
+
and are never written by doc-review: apply their comments and edits to the
|
|
24
|
+
Markdown source, keeping its syntax. There is no reply channel; the user sees
|
|
25
|
+
your work when the page reloads. For a localhost page, direct edits and deletions
|
|
26
|
+
arrive with \`kind: "url"\`; find and update the matching MDX, TSX, template, or
|
|
27
|
+
component source. Never write the rendered HTTP response over project source.`;
|
|
28
|
+
export const LEGACY_GUIDANCE = Object.freeze([
|
|
29
|
+
`## Reviewing files and localhost pages with doc-review
|
|
30
|
+
|
|
31
|
+
After writing an HTML or Markdown file the user will read, open it for them with${LEGACY_TAIL}`,
|
|
32
|
+
`## Reviewing files and localhost pages with doc-review
|
|
33
|
+
|
|
34
|
+
Start only when the user explicitly invokes /doc-review or requests an
|
|
35
|
+
interactive browser review. Writing, updating, discussing, or generically reviewing
|
|
36
|
+
content does not authorize opening a review or polling. Another skill's automatic
|
|
37
|
+
review step is not user permission. Otherwise respond normally.
|
|
38
|
+
|
|
39
|
+
After that explicit request, open the requested HTML or Markdown file with${LEGACY_TAIL}`,
|
|
40
|
+
]);
|
|
41
|
+
const migrationMessage = "AGENTS.md contains custom or unrecognized doc-review guidance — left it unchanged. " +
|
|
42
|
+
"Manually reconcile that guidance, then wrap only the setup-owned section in " +
|
|
43
|
+
`${GUIDANCE_BEGIN} and ${GUIDANCE_END}, or remove it and re-run setup.`;
|
|
44
|
+
function invalidMarkers() {
|
|
45
|
+
throw new Error("AGENTS.md has malformed, duplicate, or ambiguous doc-review ownership markers. " +
|
|
46
|
+
"Keep exactly one standalone BEGIN/END pair in order, then re-run setup. No setup files were changed.");
|
|
47
|
+
}
|
|
48
|
+
function render(body, newline) {
|
|
49
|
+
return `${GUIDANCE_BEGIN}\n${body.trim()}\n${GUIDANCE_END}`.replaceAll("\n", newline);
|
|
50
|
+
}
|
|
51
|
+
/** Plan the complete edit before setup writes any files. Never normalize user text. */
|
|
52
|
+
export function updateGuidance(existing, body) {
|
|
53
|
+
const lines = [...existing.matchAll(/[^\n]*(?:\n|$)/g)]
|
|
54
|
+
.filter((match) => match[0])
|
|
55
|
+
.map((match) => ({
|
|
56
|
+
text: match[0].replace(/\r?\n$/, ""),
|
|
57
|
+
start: match.index,
|
|
58
|
+
end: match.index + match[0].replace(/\r?\n$/, "").length,
|
|
59
|
+
}));
|
|
60
|
+
const markers = lines.filter(({ text }) => /doc-review/i.test(text) &&
|
|
61
|
+
(/\b(?:BEGIN|END)\b/i.test(text) && /<!--|-->|^\s*(?:BEGIN|END)\b/i.test(text)));
|
|
62
|
+
if (markers.length) {
|
|
63
|
+
if (markers.length !== 2 || markers[0].text !== GUIDANCE_BEGIN || markers[1].text !== GUIDANCE_END) {
|
|
64
|
+
invalidMarkers();
|
|
65
|
+
}
|
|
66
|
+
const [begin, end] = markers;
|
|
67
|
+
const newline = existing.slice(begin.end).startsWith("\r\n") ? "\r\n" : "\n";
|
|
68
|
+
return {
|
|
69
|
+
contents: existing.slice(0, begin.start) + render(body, newline) + existing.slice(end.end),
|
|
70
|
+
message: "Updated setup-owned AGENTS.md guidance (Codex)",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const candidates = [];
|
|
74
|
+
for (const legacy of LEGACY_GUIDANCE) {
|
|
75
|
+
for (const command of ["npx -y @erdemtuna/doc-review", "doc-review"]) {
|
|
76
|
+
for (const newline of ["\n", "\r\n"]) {
|
|
77
|
+
const text = legacy.replaceAll("npx -y @erdemtuna/doc-review", command).replaceAll("\n", newline);
|
|
78
|
+
let start = existing.indexOf(text);
|
|
79
|
+
while (start !== -1) {
|
|
80
|
+
const end = start + text.length;
|
|
81
|
+
const before = existing.slice(0, start);
|
|
82
|
+
const after = existing.slice(end);
|
|
83
|
+
// A complete generated section, not a substring of customized prose.
|
|
84
|
+
if ((start === 0 || before.endsWith("\n") || before === "\uFEFF") &&
|
|
85
|
+
/^(?:\r?\n|$)/.test(after) &&
|
|
86
|
+
/^(?:\s*$|(?:\r?\n)+(?=#{1,2} ))/.test(after)) {
|
|
87
|
+
candidates.push({ start, end, newline });
|
|
88
|
+
}
|
|
89
|
+
start = existing.indexOf(text, start + text.length);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (candidates.length > 1) {
|
|
95
|
+
throw new Error("AGENTS.md contains multiple legacy doc-review sections; ownership is ambiguous. " +
|
|
96
|
+
"Reconcile them before re-running setup. No setup files were changed.");
|
|
97
|
+
}
|
|
98
|
+
if (candidates.length === 1) {
|
|
99
|
+
const { start, end, newline } = candidates[0];
|
|
100
|
+
const surrounding = existing.slice(0, start) + existing.slice(end);
|
|
101
|
+
if (!/doc-review/i.test(surrounding)) {
|
|
102
|
+
return {
|
|
103
|
+
contents: existing.slice(0, start) + render(body, newline) + existing.slice(end),
|
|
104
|
+
message: "Migrated legacy AGENTS.md guidance to setup-owned markers (Codex)",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (/doc-review/i.test(existing)) {
|
|
109
|
+
return { contents: existing, message: migrationMessage };
|
|
110
|
+
}
|
|
111
|
+
const newline = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
112
|
+
const separator = !existing || existing.endsWith(newline + newline)
|
|
113
|
+
? ""
|
|
114
|
+
: existing.endsWith("\n") ? newline : newline + newline;
|
|
115
|
+
return {
|
|
116
|
+
contents: existing + separator + render(body, newline) + newline,
|
|
117
|
+
message: `${existing ? "Updated" : "Created"} AGENTS.md (Codex)`,
|
|
118
|
+
};
|
|
119
|
+
}
|
package/{src → lib}/setup.js
RENAMED
|
@@ -4,12 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { updateGuidance } from "./setup-guidance.js";
|
|
7
|
-
|
|
8
7
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
9
8
|
export const PACKAGE_NAME = "@erdemtuna/doc-review";
|
|
10
9
|
export const COMMAND_NAME = "doc-review";
|
|
11
10
|
export const NPX_COMMAND = `npx -y ${PACKAGE_NAME}`;
|
|
12
|
-
|
|
13
11
|
/**
|
|
14
12
|
* Teach agents the command that will actually work here. A global install or
|
|
15
13
|
* `npm link` puts `doc-review` on PATH; otherwise fall back to npx, which only
|
|
@@ -22,32 +20,27 @@ export const NPX_COMMAND = `npx -y ${PACKAGE_NAME}`;
|
|
|
22
20
|
* every later agent invocation dies with "command not found".
|
|
23
21
|
*/
|
|
24
22
|
export function invocation(run = spawnSync) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
24
|
+
const found = run(probe, [COMMAND_NAME], { encoding: "utf8", windowsHide: true });
|
|
25
|
+
const resolved = found.status === 0 ? found.stdout.trim().split(/\r?\n/)[0].trim() : "";
|
|
26
|
+
return resolved && !isNpxCachePath(resolved) ? COMMAND_NAME : NPX_COMMAND;
|
|
29
27
|
}
|
|
30
|
-
|
|
31
28
|
/** True for a binary npm placed in its transient `_npx` cache for one command. */
|
|
32
29
|
export function isNpxCachePath(binPath) {
|
|
33
|
-
|
|
30
|
+
return binPath.split(/[\\/]/).includes("_npx");
|
|
34
31
|
}
|
|
35
|
-
|
|
36
32
|
/**
|
|
37
33
|
* Quote a path for copy-paste into any shell. JSON.stringify would double
|
|
38
34
|
* Windows backslashes; plain double quotes work in bash, zsh, cmd and
|
|
39
35
|
* PowerShell alike, and paths cannot legally contain a double quote on Windows.
|
|
40
36
|
*/
|
|
41
37
|
export function shellQuote(arg) {
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const text = String(arg);
|
|
39
|
+
return /^[\w@%+=:,./-]+$/.test(text) ? text : `"${text.replaceAll('"', '\\"')}"`;
|
|
44
40
|
}
|
|
45
|
-
|
|
46
41
|
/** The skill lives in its own markdown file so nothing needs escaping. */
|
|
47
42
|
export const readSkill = () => fs.readFileSync(path.join(here, "SKILL.md"), "utf8");
|
|
48
|
-
|
|
49
43
|
export const skillFor = (cmd) => readSkill().replaceAll(NPX_COMMAND, cmd);
|
|
50
|
-
|
|
51
44
|
const CODEX_BLOCK = `
|
|
52
45
|
## Reviewing files and localhost pages with doc-review
|
|
53
46
|
|
|
@@ -97,47 +90,43 @@ your work when the page reloads. For a localhost page, direct edits and deletion
|
|
|
97
90
|
arrive with \`kind: "url"\`; find and update the matching MDX, TSX, template, or
|
|
98
91
|
component source. Never write the rendered HTTP response over project source.
|
|
99
92
|
`;
|
|
100
|
-
|
|
101
93
|
export function installSkills(cwd, { global: isGlobal = false, home = os.homedir(), command } = {}) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
94
|
+
const done = [];
|
|
95
|
+
const cmd = command || invocation();
|
|
96
|
+
const agents = path.join(cwd, "AGENTS.md");
|
|
97
|
+
let guidance;
|
|
98
|
+
let existing;
|
|
99
|
+
if (!isGlobal) {
|
|
100
|
+
const bytes = fs.existsSync(agents) ? fs.readFileSync(agents) : Buffer.alloc(0);
|
|
101
|
+
existing = bytes.toString("utf8");
|
|
102
|
+
if (!Buffer.from(existing, "utf8").equals(bytes)) {
|
|
103
|
+
throw new Error("AGENTS.md is not valid UTF-8; convert it before re-running setup. No setup files were changed.");
|
|
104
|
+
}
|
|
105
|
+
guidance = updateGuidance(existing, CODEX_BLOCK.replaceAll(NPX_COMMAND, cmd));
|
|
112
106
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
["
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
done.push(`Heads up: npx only works once ${PACKAGE_NAME} is published. Run \`npm link\` in the`);
|
|
139
|
-
done.push("doc-review folder first if you want to use it locally, then re-run setup.");
|
|
140
|
-
}
|
|
141
|
-
done.push("Any other agent works too — see the JSON contract in the README.");
|
|
142
|
-
return done;
|
|
107
|
+
const skillRoots = isGlobal
|
|
108
|
+
? [
|
|
109
|
+
["Claude Code", path.join(home, ".claude")],
|
|
110
|
+
["Codex", path.join(home, ".codex")],
|
|
111
|
+
["Shared agents", path.join(home, ".agents")],
|
|
112
|
+
]
|
|
113
|
+
: [["Claude Code", path.join(cwd, ".claude")]];
|
|
114
|
+
for (const [agent, base] of skillRoots) {
|
|
115
|
+
const skillFile = path.join(base, "skills", "doc-review", "SKILL.md");
|
|
116
|
+
fs.mkdirSync(path.dirname(skillFile), { recursive: true });
|
|
117
|
+
fs.writeFileSync(skillFile, skillFor(cmd));
|
|
118
|
+
done.push(`${agent} skill ${skillFile}${isGlobal ? " (all projects)" : ""}`);
|
|
119
|
+
}
|
|
120
|
+
if (!isGlobal) {
|
|
121
|
+
if (guidance.contents !== existing)
|
|
122
|
+
fs.writeFileSync(agents, guidance.contents);
|
|
123
|
+
done.push(guidance.message);
|
|
124
|
+
}
|
|
125
|
+
done.push("", `Agents will be told to run: ${cmd}`);
|
|
126
|
+
if (cmd.startsWith("npx")) {
|
|
127
|
+
done.push(`Heads up: npx only works once ${PACKAGE_NAME} is published. Run \`npm link\` in the`);
|
|
128
|
+
done.push("doc-review folder first if you want to use it locally, then re-run setup.");
|
|
129
|
+
}
|
|
130
|
+
done.push("Any other agent works too — see the JSON contract in the README.");
|
|
131
|
+
return done;
|
|
143
132
|
}
|