@ascribed/review 0.2.1-next.11 → 0.2.1-next.13

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 CHANGED
@@ -36,7 +36,7 @@ The colors are the `--ascribe-review-*` custom properties at the top of `marks.c
36
36
 
37
37
  ## The static report
38
38
 
39
- `src/report/` is the script of `ascribe diff --format html`'s report, which draws the list of changed pages and each page with its marks. It isn't an entry point: `pnpm --filter @ascribed/review embed` bundles it with the marks and `@ascribed/elements` into `crates/ascribe-diff/src/html/`, where the binary embeds it, along with one stylesheet made of the element library's, `marks.css`, and `src/report/report.css`. It also writes the script's SHA-256 beside it, which the report's content security policy names as the one script allowed to run. Run it after changing any of them; `test/embedded.test.ts` fails until you do.
39
+ `src/report/` is the script of `ascribe diff --format html`'s report, which draws the list of changed pages and each page with its marks. It isn't an entry point: `pnpm --filter @ascribed/review embed` bundles it with the marks and `@ascribed/elements` into `crates/ascribe-diff/src/html/`, where the binary embeds it, along with one stylesheet made of the element library's, `marks.css`, and `src/report/report.css`. It also writes the script's SHA-256 beside it, which the report's content security policy names as the one script allowed to run. Run it after changing any of them; `test/embedded.test.ts` fails until you do. A page with a `prompt` in the report's data gets **Copy prompt**, which copies it and opens nothing.
40
40
 
41
41
  ## `@ascribed/review/overlay`
42
42
 
@@ -48,12 +48,22 @@ const overlay = createOverlay({ root: article, host });
48
48
 
49
49
  Draws a pull request's threads beside the blocks of `root`, a rendered page with source anchors: in a column beside the page when its container is at least `columnAt` pixels wide (default 600), else as a count on each block that opens its threads. Threads with no block on the page are listed above it, and a bar below it counts unsent comments, with the submit dialog. A reviewer can reply (at once, or with the review), resolve and reopen, comment on any block, and submit or discard the review, all from the keyboard. It draws in shadow roots, so its styles and the page's stay apart; theme it with the `--ascribe-review-*` custom properties `marks.css` declares.
50
50
 
51
- The host (`OverlayHost`) supplies the data and does the work: `load()` for the page's threads (`ReviewSession.threads`), its pending review, and the viewer; `commentTarget`, `comment`, `reply`, `resolve`, `submit`, `discard`, and `allThreads`, as `ReviewSession` has them; and `openSource`, `openThread` (a thread on another page), and optionally `openLink` and `notify`. `onDidChange` tells the overlay to read the threads again. The overlay never talks to GitHub itself, so a host can keep the token out of the page.
51
+ The host (`OverlayHost`) supplies the data and does the work: `load()` for the page's threads (`ReviewSession.threads`), its pending review, and the viewer; `commentTarget`, `comment`, `reply`, `resolve`, `submit`, `discard`, and `allThreads`, as `ReviewSession` has them; and `openSource`, `openThread` (a thread on another page), and optionally `openLink`, `notify`, and `promptAgent`. With `promptAgent`, each thread's card has **Prompt agent** and the list of every thread **Prompt agent: all open**; the overlay passes the host a `PromptRequest` (`{ kind: "thread", threadId }` or `{ kind: "open-threads" }`, and the hosts' own headers ask for `page-changes` and `fragment-reach`) and builds nothing. A rejection's message is shown as why the prompt couldn't be built. `onDidChange` tells the overlay to read the threads again. The overlay never talks to GitHub itself, so a host can keep the token out of the page.
52
52
 
53
53
  `overlay.refresh()` reads the threads again, `layout()` places them again after the page changed size, `goToThread(id)` goes to one, `showAllComments()` opens the list of every thread, and `dispose()` takes everything away.
54
54
 
55
55
  Comment bodies are other people's Markdown: `renderMarkdown(document, text)` builds a safe subset (paragraphs, emphasis, code, links that open apart from the page, lists, block quotes) as DOM nodes. Raw HTML stays text, links other than web and email ones are dropped, and an image is a link to it, so nothing loads without a click.
56
56
 
57
+ ## `@ascribed/review/prompt`
58
+
59
+ ```ts
60
+ import { threadPrompt, openThreadsPrompt } from "@ascribed/review/prompt";
61
+ ```
62
+
63
+ Builds the agent prompts about review threads, in the agents plan's prompt format: `threadPrompt` asks an agent to address one thread, with its place, the block's source lines, and its comments; `openThreadsPrompt` lists every open thread in two lines each. `openThreadsList` isn't a prompt but the answer of an agent's tool (VS Code's `ascribe_review_threads`): every open thread, at most `MAX_LISTED`, with its place, the pages that show it, and all its comments, each cut at `MAX_COMMENT` characters. A comment is other people's text, so it goes in only inside a fence longer than any backtick run in it, under a sentence saying it's data, with its HTML comments taken out first (`stripComments`). A command a prompt names quotes its arguments as a POSIX shell reads them (`shellWord`). A prompt is at most `LIMIT` (5,000) characters, the binary's limit, which a test holds.
64
+
65
+ The builders read nothing. On a host's Node side, `buildThreadsPrompt(context, request)` from `@ascribed/review/github` gathers what they need from a `ReviewSession`, the changed pages, and a `readSource` callback (and `buildThreadsList(context)` does for `openThreadsList`), and `promptProject(projectDir)` finds the project's folder, content root, and `AGENTS.md` as the binary does. The prompts about changes come from the binary: `ascribe diff --format prompt`, or the language server's `ascribe/agentPrompt`.
66
+
57
67
  ## `@ascribed/review/github`
58
68
 
59
69
  ```ts
@@ -1,7 +1,8 @@
1
1
  export { contentPrefixOf, createSession, findPullRequest, openReview, type CommentTarget, type LocalState, type OpenReviewOptions, type PendingReview, type PullRequestInfo, type ReviewEvent, type ReviewSession, type SessionOptions, } from "./session.js";
2
2
  export { answerRequest, baseRevision, OVERLAY_METHODS, pagesShowing, pullRequestBase, type Answer, type ChangedPageRef, type OverlayMethod, type RequestContext, } from "./requests.js";
3
+ export { buildThreadsList, buildThreadsPrompt, promptProject, type ThreadPromptContext, } from "./prompt.js";
3
4
  export { parseRemote, readCheckout, type CheckoutInfo, type RepositoryRef } from "./repository.js";
4
5
  export { ghTransport, graphqlUrl, MIN_GH_VERSION, tokenTransport, type GhTransportOptions, type GitHubTransport, type TokenTransportOptions, } from "./transport.js";
5
6
  export { formatSection, marker, parseSections, type MarkedSection } from "./marker.js";
6
7
  export { ReviewError, type ReviewErrorCode } from "../shared/errors.js";
7
- export type { Author, Side, Thread, ThreadComment } from "../shared/types.js";
8
+ export type { Author, PromptRequest, Side, Thread, ThreadComment } from "../shared/types.js";
@@ -1,6 +1,7 @@
1
1
  // Reading and writing a pull request's review threads on GitHub.
2
2
  export { contentPrefixOf, createSession, findPullRequest, openReview, } from "./session.js";
3
3
  export { answerRequest, baseRevision, OVERLAY_METHODS, pagesShowing, pullRequestBase, } from "./requests.js";
4
+ export { buildThreadsList, buildThreadsPrompt, promptProject, } from "./prompt.js";
4
5
  export { parseRemote, readCheckout } from "./repository.js";
5
6
  export { ghTransport, graphqlUrl, MIN_GH_VERSION, tokenTransport, } from "./transport.js";
6
7
  export { formatSection, marker, parseSections } from "./marker.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/github/index.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,EACf,UAAU,GASX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,eAAe,GAKhB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAyC,MAAM,iBAAiB,CAAC;AACnG,OAAO,EACL,WAAW,EACX,UAAU,EACV,cAAc,EACd,cAAc,GAIf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,WAAW,EAAwB,MAAM,qBAAqB,CAAC","sourcesContent":["// Reading and writing a pull request's review threads on GitHub.\nexport {\n contentPrefixOf,\n createSession,\n findPullRequest,\n openReview,\n type CommentTarget,\n type LocalState,\n type OpenReviewOptions,\n type PendingReview,\n type PullRequestInfo,\n type ReviewEvent,\n type ReviewSession,\n type SessionOptions,\n} from \"./session.js\";\nexport {\n answerRequest,\n baseRevision,\n OVERLAY_METHODS,\n pagesShowing,\n pullRequestBase,\n type Answer,\n type ChangedPageRef,\n type OverlayMethod,\n type RequestContext,\n} from \"./requests.js\";\nexport { parseRemote, readCheckout, type CheckoutInfo, type RepositoryRef } from \"./repository.js\";\nexport {\n ghTransport,\n graphqlUrl,\n MIN_GH_VERSION,\n tokenTransport,\n type GhTransportOptions,\n type GitHubTransport,\n type TokenTransportOptions,\n} from \"./transport.js\";\nexport { formatSection, marker, parseSections, type MarkedSection } from \"./marker.js\";\nexport { ReviewError, type ReviewErrorCode } from \"../shared/errors.js\";\nexport type { Author, Side, Thread, ThreadComment } from \"../shared/types.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/github/index.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,EACf,UAAU,GASX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,eAAe,GAKhB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,aAAa,GAEd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAyC,MAAM,iBAAiB,CAAC;AACnG,OAAO,EACL,WAAW,EACX,UAAU,EACV,cAAc,EACd,cAAc,GAIf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,WAAW,EAAwB,MAAM,qBAAqB,CAAC","sourcesContent":["// Reading and writing a pull request's review threads on GitHub.\nexport {\n contentPrefixOf,\n createSession,\n findPullRequest,\n openReview,\n type CommentTarget,\n type LocalState,\n type OpenReviewOptions,\n type PendingReview,\n type PullRequestInfo,\n type ReviewEvent,\n type ReviewSession,\n type SessionOptions,\n} from \"./session.js\";\nexport {\n answerRequest,\n baseRevision,\n OVERLAY_METHODS,\n pagesShowing,\n pullRequestBase,\n type Answer,\n type ChangedPageRef,\n type OverlayMethod,\n type RequestContext,\n} from \"./requests.js\";\nexport {\n buildThreadsList,\n buildThreadsPrompt,\n promptProject,\n type ThreadPromptContext,\n} from \"./prompt.js\";\nexport { parseRemote, readCheckout, type CheckoutInfo, type RepositoryRef } from \"./repository.js\";\nexport {\n ghTransport,\n graphqlUrl,\n MIN_GH_VERSION,\n tokenTransport,\n type GhTransportOptions,\n type GitHubTransport,\n type TokenTransportOptions,\n} from \"./transport.js\";\nexport { formatSection, marker, parseSections, type MarkedSection } from \"./marker.js\";\nexport { ReviewError, type ReviewErrorCode } from \"../shared/errors.js\";\nexport type { Author, PromptRequest, Side, Thread, ThreadComment } from \"../shared/types.js\";\n"]}
@@ -0,0 +1,40 @@
1
+ import { type PromptProject } from "../prompt/index.js";
2
+ import type { PromptRequest } from "../shared/types.js";
3
+ import { type ChangedPageRef } from "./requests.js";
4
+ import { type ReviewSession } from "./session.js";
5
+ /** What building a prompt about threads needs from its host. */
6
+ export interface ThreadPromptContext {
7
+ session: ReviewSession;
8
+ /** The changed pages, for which pages show a thread's block. */
9
+ changedPages(): Promise<readonly ChangedPageRef[]>;
10
+ /** Where the project is (`promptProject`). */
11
+ project: PromptProject;
12
+ /**
13
+ * A file's text as saved, by content path, and whether an editor has
14
+ * unsaved changes to it; `undefined` when it can't be read.
15
+ */
16
+ readSource(path: string): Promise<{
17
+ text: string;
18
+ unsaved: boolean;
19
+ } | undefined>;
20
+ }
21
+ /**
22
+ * The prompt about one thread, or about every open thread. `undefined` when
23
+ * there's no open thread to prompt about; rejects with a `ReviewError` when
24
+ * the thread isn't on the pull request.
25
+ */
26
+ export declare function buildThreadsPrompt(context: ThreadPromptContext, request: Extract<PromptRequest, {
27
+ kind: "thread" | "open-threads";
28
+ }>): Promise<string | undefined>;
29
+ /**
30
+ * The pull request's open threads, as an agent's tool lists them
31
+ * (`openThreadsList`), with the pages that show each.
32
+ */
33
+ export declare function buildThreadsList(context: Omit<ThreadPromptContext, "readSource">): Promise<string>;
34
+ /**
35
+ * Where the project whose `ascribe.toml` is in `projectDir` is, as the
36
+ * binary's prompts find it: its repository is the nearest folder at or above
37
+ * it with a `.git`, and its `AGENTS.md` is the project folder's, or else the
38
+ * repository's.
39
+ */
40
+ export declare function promptProject(projectDir: string): PromptProject;
@@ -0,0 +1,115 @@
1
+ // Gathering what a prompt about review threads needs, on a host's Node side:
2
+ // the session's threads, the text of a thread's lines, the pages that show
3
+ // it, and where the project is. `../prompt` builds the prompt from them.
4
+ import { existsSync, realpathSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { openThreadsList, openThreadsPrompt, threadPrompt, } from "../prompt/index.js";
7
+ import { ReviewError } from "../shared/errors.js";
8
+ import { pagesShowing } from "./requests.js";
9
+ import { contentPrefixOf } from "./session.js";
10
+ /**
11
+ * The prompt about one thread, or about every open thread. `undefined` when
12
+ * there's no open thread to prompt about; rejects with a `ReviewError` when
13
+ * the thread isn't on the pull request.
14
+ */
15
+ export async function buildThreadsPrompt(context, request) {
16
+ const threads = await context.session.allThreads();
17
+ if (request.kind === "open-threads") {
18
+ return openThreadsPrompt({
19
+ project: context.project,
20
+ pullRequest: context.session.pullRequest.number,
21
+ threads,
22
+ });
23
+ }
24
+ const thread = threads.find((t) => t.id === request.threadId);
25
+ if (thread === undefined) {
26
+ throw new ReviewError("not-found", "That comment isn't on the pull request any more.");
27
+ }
28
+ const onLines = thread.lines !== undefined &&
29
+ thread.detached === undefined &&
30
+ !(thread.kind === "review" && thread.side === "LEFT");
31
+ const [file, pages] = await Promise.all([
32
+ context.readSource(thread.path),
33
+ context.changedPages().catch(() => []),
34
+ ]);
35
+ let source;
36
+ if (onLines && thread.lines && file) {
37
+ const lines = file.text.split(/\r?\n/);
38
+ const { first, last } = thread.lines;
39
+ if (last <= lines.length) {
40
+ source = { first, last, text: lines.slice(first - 1, last).join("\n") };
41
+ }
42
+ }
43
+ // A fragment's block shows on the pages that include it; a page's on itself.
44
+ const showing = pagesShowing(thread.path, pages);
45
+ const shownOn = showing.some((p) => p.path === thread.path) ? [] : showing.map((p) => p.path);
46
+ return threadPrompt({
47
+ project: context.project,
48
+ thread,
49
+ source,
50
+ unsaved: file?.unsaved ?? false,
51
+ shownOn,
52
+ });
53
+ }
54
+ /**
55
+ * The pull request's open threads, as an agent's tool lists them
56
+ * (`openThreadsList`), with the pages that show each.
57
+ */
58
+ export async function buildThreadsList(context) {
59
+ const [threads, pages] = await Promise.all([
60
+ context.session.allThreads(),
61
+ context.changedPages().catch(() => []),
62
+ ]);
63
+ return openThreadsList({
64
+ project: context.project,
65
+ pullRequest: context.session.pullRequest.number,
66
+ threads,
67
+ shownOn: (file) => pagesShowing(file, pages).map((p) => p.path),
68
+ });
69
+ }
70
+ /**
71
+ * Where the project whose `ascribe.toml` is in `projectDir` is, as the
72
+ * binary's prompts find it: its repository is the nearest folder at or above
73
+ * it with a `.git`, and its `AGENTS.md` is the project folder's, or else the
74
+ * repository's.
75
+ */
76
+ export function promptProject(projectDir) {
77
+ const real = (p) => {
78
+ try {
79
+ return realpathSync.native(p);
80
+ }
81
+ catch {
82
+ return path.resolve(p);
83
+ }
84
+ };
85
+ const dir = real(projectDir);
86
+ let root;
87
+ for (let at = dir;; at = path.dirname(at)) {
88
+ if (existsSync(path.join(at, ".git"))) {
89
+ root = at;
90
+ break;
91
+ }
92
+ if (path.dirname(at) === at)
93
+ break;
94
+ }
95
+ const folder = root === undefined ? "" : path.relative(root, dir).split(path.sep).join("/");
96
+ let contentRoot;
97
+ try {
98
+ const base = root ?? dir;
99
+ const contentDir = path.join(base, ...contentPrefixOf(base, dir).split("/"));
100
+ contentRoot = path.relative(dir, contentDir).split(path.sep).join("/");
101
+ }
102
+ catch {
103
+ // No `ascribe.toml` to read: the content root's default.
104
+ contentRoot = "docs";
105
+ }
106
+ const agents = existsSync(path.join(dir, "AGENTS.md"))
107
+ ? folder === ""
108
+ ? "AGENTS.md"
109
+ : `${folder}/AGENTS.md`
110
+ : root !== undefined && existsSync(path.join(root, "AGENTS.md"))
111
+ ? "AGENTS.md"
112
+ : undefined;
113
+ return { folder, contentRoot, agents };
114
+ }
115
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/github/prompt.ts"],"names":[],"mappings":"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","sourcesContent":["// Gathering what a prompt about review threads needs, on a host's Node side:\n// the session's threads, the text of a thread's lines, the pages that show\n// it, and where the project is. `../prompt` builds the prompt from them.\nimport { existsSync, realpathSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n openThreadsList,\n openThreadsPrompt,\n threadPrompt,\n type PromptProject,\n} from \"../prompt/index.js\";\nimport { ReviewError } from \"../shared/errors.js\";\nimport type { PromptRequest } from \"../shared/types.js\";\nimport { pagesShowing, type ChangedPageRef } from \"./requests.js\";\nimport { contentPrefixOf, type ReviewSession } from \"./session.js\";\n\n/** What building a prompt about threads needs from its host. */\nexport interface ThreadPromptContext {\n session: ReviewSession;\n /** The changed pages, for which pages show a thread's block. */\n changedPages(): Promise<readonly ChangedPageRef[]>;\n /** Where the project is (`promptProject`). */\n project: PromptProject;\n /**\n * A file's text as saved, by content path, and whether an editor has\n * unsaved changes to it; `undefined` when it can't be read.\n */\n readSource(path: string): Promise<{ text: string; unsaved: boolean } | undefined>;\n}\n\n/**\n * The prompt about one thread, or about every open thread. `undefined` when\n * there's no open thread to prompt about; rejects with a `ReviewError` when\n * the thread isn't on the pull request.\n */\nexport async function buildThreadsPrompt(\n context: ThreadPromptContext,\n request: Extract<PromptRequest, { kind: \"thread\" | \"open-threads\" }>,\n): Promise<string | undefined> {\n const threads = await context.session.allThreads();\n if (request.kind === \"open-threads\") {\n return openThreadsPrompt({\n project: context.project,\n pullRequest: context.session.pullRequest.number,\n threads,\n });\n }\n const thread = threads.find((t) => t.id === request.threadId);\n if (thread === undefined) {\n throw new ReviewError(\"not-found\", \"That comment isn't on the pull request any more.\");\n }\n const onLines =\n thread.lines !== undefined &&\n thread.detached === undefined &&\n !(thread.kind === \"review\" && thread.side === \"LEFT\");\n const [file, pages] = await Promise.all([\n context.readSource(thread.path),\n context.changedPages().catch(() => []),\n ]);\n let source: { first: number; last: number; text: string } | undefined;\n if (onLines && thread.lines && file) {\n const lines = file.text.split(/\\r?\\n/);\n const { first, last } = thread.lines;\n if (last <= lines.length) {\n source = { first, last, text: lines.slice(first - 1, last).join(\"\\n\") };\n }\n }\n // A fragment's block shows on the pages that include it; a page's on itself.\n const showing = pagesShowing(thread.path, pages);\n const shownOn = showing.some((p) => p.path === thread.path) ? [] : showing.map((p) => p.path);\n return threadPrompt({\n project: context.project,\n thread,\n source,\n unsaved: file?.unsaved ?? false,\n shownOn,\n });\n}\n\n/**\n * The pull request's open threads, as an agent's tool lists them\n * (`openThreadsList`), with the pages that show each.\n */\nexport async function buildThreadsList(\n context: Omit<ThreadPromptContext, \"readSource\">,\n): Promise<string> {\n const [threads, pages] = await Promise.all([\n context.session.allThreads(),\n context.changedPages().catch(() => []),\n ]);\n return openThreadsList({\n project: context.project,\n pullRequest: context.session.pullRequest.number,\n threads,\n shownOn: (file) => pagesShowing(file, pages).map((p) => p.path),\n });\n}\n\n/**\n * Where the project whose `ascribe.toml` is in `projectDir` is, as the\n * binary's prompts find it: its repository is the nearest folder at or above\n * it with a `.git`, and its `AGENTS.md` is the project folder's, or else the\n * repository's.\n */\nexport function promptProject(projectDir: string): PromptProject {\n const real = (p: string): string => {\n try {\n return realpathSync.native(p);\n } catch {\n return path.resolve(p);\n }\n };\n const dir = real(projectDir);\n let root: string | undefined;\n for (let at = dir; ; at = path.dirname(at)) {\n if (existsSync(path.join(at, \".git\"))) {\n root = at;\n break;\n }\n if (path.dirname(at) === at) break;\n }\n const folder = root === undefined ? \"\" : path.relative(root, dir).split(path.sep).join(\"/\");\n let contentRoot: string;\n try {\n const base = root ?? dir;\n const contentDir = path.join(base, ...contentPrefixOf(base, dir).split(\"/\"));\n contentRoot = path.relative(dir, contentDir).split(path.sep).join(\"/\");\n } catch {\n // No `ascribe.toml` to read: the content root's default.\n contentRoot = \"docs\";\n }\n const agents = existsSync(path.join(dir, \"AGENTS.md\"))\n ? folder === \"\"\n ? \"AGENTS.md\"\n : `${folder}/AGENTS.md`\n : root !== undefined && existsSync(path.join(root, \"AGENTS.md\"))\n ? \"AGENTS.md\"\n : undefined;\n return { folder, contentRoot, agents };\n}\n"]}
package/dist/names.d.ts CHANGED
@@ -15,6 +15,11 @@ export declare const ELEMENT_AVAILABILITY = "ascribe-availability";
15
15
  export declare const ELEMENT_AVAILABILITY_TARGET = "ascribe-availability-target";
16
16
  /** `<ascribe-group>`: a project widget's group (packages/elements/CONTRACT.md §6). */
17
17
  export declare const ELEMENT_GROUP = "ascribe-group";
18
+ /**
19
+ * `<ascribe-for-agents>`: the visually hidden pointer to `llms.txt` at the top
20
+ * of a page, for agents (packages/elements/CONTRACT.md §8).
21
+ */
22
+ export declare const ELEMENT_FOR_AGENTS = "ascribe-for-agents";
18
23
  /** `<ascribe-attributes>`: the attribute marker (site-render contract §1). */
19
24
  export declare const ELEMENT_ATTRIBUTES = "ascribe-attributes";
20
25
  /** The name an anchor comment starts with, `<!--ascribe-anchor` (site-render contract §7.3). */
package/dist/names.js CHANGED
@@ -18,6 +18,11 @@ export const ELEMENT_AVAILABILITY = "ascribe-availability";
18
18
  export const ELEMENT_AVAILABILITY_TARGET = "ascribe-availability-target";
19
19
  /** `<ascribe-group>`: a project widget's group (packages/elements/CONTRACT.md §6). */
20
20
  export const ELEMENT_GROUP = "ascribe-group";
21
+ /**
22
+ * `<ascribe-for-agents>`: the visually hidden pointer to `llms.txt` at the top
23
+ * of a page, for agents (packages/elements/CONTRACT.md §8).
24
+ */
25
+ export const ELEMENT_FOR_AGENTS = "ascribe-for-agents";
21
26
  /** `<ascribe-attributes>`: the attribute marker (site-render contract §1). */
22
27
  export const ELEMENT_ATTRIBUTES = "ascribe-attributes";
23
28
  /** The name an anchor comment starts with, `<!--ascribe-anchor` (site-render contract §7.3). */
package/dist/names.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"names.js","sourceRoot":"","sources":["../src/names.ts"],"names":[],"mappings":"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","sourcesContent":["// The names Ascribe puts on a page: elements, attributes, classes, and ids.\n// Generated from crates/ascribe-core/src/names.rs by\n// `ASCRIBE_BLESS=1 cargo test -p ascribe-core --test names`. Don't edit it.\n\n/** `<ascribe-note>`: a note (packages/elements/CONTRACT.md §1). */\nexport const ELEMENT_NOTE = \"ascribe-note\";\n\n/** `<ascribe-steps>`: a procedure (packages/elements/CONTRACT.md §2). */\nexport const ELEMENT_STEPS = \"ascribe-steps\";\n\n/** `<ascribe-tabs>`: a variant group of several arms (packages/elements/CONTRACT.md §3). */\nexport const ELEMENT_TABS = \"ascribe-tabs\";\n\n/** `<ascribe-tab>`: one arm of `<ascribe-tabs>` (packages/elements/CONTRACT.md §3). */\nexport const ELEMENT_TAB = \"ascribe-tab\";\n\n/** `<ascribe-availability>`: a badge or filter (packages/elements/CONTRACT.md §4). */\nexport const ELEMENT_AVAILABILITY = \"ascribe-availability\";\n\n/**\n * `<ascribe-availability-target>`: one target of `<ascribe-availability>`\n * (packages/elements/CONTRACT.md §4).\n */\nexport const ELEMENT_AVAILABILITY_TARGET = \"ascribe-availability-target\";\n\n/** `<ascribe-group>`: a project widget's group (packages/elements/CONTRACT.md §6). */\nexport const ELEMENT_GROUP = \"ascribe-group\";\n\n/** `<ascribe-attributes>`: the attribute marker (site-render contract §1). */\nexport const ELEMENT_ATTRIBUTES = \"ascribe-attributes\";\n\n/** The name an anchor comment starts with, `<!--ascribe-anchor` (site-render contract §7.3). */\nexport const COMMENT_ANCHOR = \"ascribe-anchor\";\n\n/** A block's source anchor (site-render contract §7.1). */\nexport const DATA_SOURCE = \"data-ascribe-source\";\n\n/** The includes a block came through (site-render contract §7.1). */\nexport const DATA_VIA = \"data-ascribe-via\";\n\n/** A glossary term's id, on its link (packages/elements/CONTRACT.md §7). */\nexport const DATA_TERM = \"data-ascribe-term\";\n\n/** The element the report draws into. */\nexport const ID_REPORT = \"ascribe-review\";\n\n/** The `<script type=\"application/json\">` holding the report's data. */\nexport const ID_REPORT_DATA = \"ascribe-review-data\";\n\n/**\n * The page's light or dark scheme, `light` or `dark`, on the root\n * element (docs/content/guides/astro.md).\n */\nexport const DATA_SCHEME = \"data-ascribe-scheme\";\n\n/** On a marked page's root: what it shows, `changes`, `will`, or `was`. */\nexport const DATA_SHOW = \"data-ascribe-show\";\n\n/**\n * On a marked block: its change, `added`, `changed`, `removed`, `moved`,\n * or `moved-from`.\n */\nexport const DATA_CHANGE = \"data-ascribe-change\";\n\n/** On every element review adds to the page, so it can be taken away again. */\nexport const DATA_UI = \"data-ascribe-ui\";\n\n/** On a change's label: the change. */\nexport const DATA_LABEL = \"data-ascribe-label\";\n\n/** On a tab's hint: `added` or `changed`. */\nexport const DATA_HINT = \"data-ascribe-hint\";\n\n/** On a moved block: the id its \"moved from\" link points at. */\nexport const DATA_MOVE = \"data-ascribe-move\";\n\n/** On a changed block whose copy as it was follows it. */\nexport const DATA_HAS_WAS = \"data-ascribe-has-was\";\n\n/** On a copy of a block as it was, shown only with the page as it was. */\nexport const DATA_WAS_ONLY = \"data-ascribe-was-only\";\n\n/** On a changed, removed, or moved block: its source anchor as it was. */\nexport const DATA_WAS_SOURCE = \"data-ascribe-was-source\";\n\n/** On the elements that hold the threads' overlay. */\nexport const DATA_OVERLAY = \"data-ascribe-overlay\";\n\n/** On the style element the Astro dev toolbar adds for the marks. */\nexport const DATA_REVIEW = \"data-ascribe-review\";\n\n/** On the root of a page whose changes are marked. */\nexport const CLASS_MARKS = \"ascribe-marks\";\n\n/** A change's label. */\nexport const CLASS_LABEL = \"ascribe-label\";\n\n/** A label placed before its block rather than over it. */\nexport const CLASS_LABEL_BEFORE = \"ascribe-label-before\";\n\n/** Inserted text. */\nexport const CLASS_INS = \"ascribe-ins\";\n\n/** Deleted text. */\nexport const CLASS_DEL = \"ascribe-del\";\n\n/** A removed block. */\nexport const CLASS_REMOVED = \"ascribe-removed\";\n\n/** A removed block's content. */\nexport const CLASS_REMOVED_BODY = \"ascribe-removed-body\";\n\n/** The button that shows or collapses a removed block. */\nexport const CLASS_TOGGLE = \"ascribe-toggle\";\n\n/** On a removed block that's shown. */\nexport const CLASS_OPEN = \"ascribe-open\";\n\n/** What stands where a moved block was. */\nexport const CLASS_MOVED_FROM = \"ascribe-moved-from\";\n\n/** The note with a moved block's link. */\nexport const CLASS_MOVE_NOTE = \"ascribe-move-note\";\n\n/** A move note after its block, not inside it. */\nexport const CLASS_MOVE_AFTER = \"ascribe-move-after\";\n\n/** The link between a moved block and where it was. */\nexport const CLASS_MOVE_LINK = \"ascribe-move-link\";\n\n/** A moved block's copy as it was, where it was. */\nexport const CLASS_WAS_COPY = \"ascribe-was-copy\";\n\n/** A tab's hint that it holds changes. */\nexport const CLASS_HINT = \"ascribe-hint\";\n\n/** A block flashed after going to it. */\nexport const CLASS_FLASH = \"ascribe-flash\";\n\n/** The tip that shows a block's source on hover and focus. */\nexport const CLASS_WHERE = \"ascribe-where\";\n"]}
1
+ {"version":3,"file":"names.js","sourceRoot":"","sources":["../src/names.ts"],"names":[],"mappings":"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","sourcesContent":["// The names Ascribe puts on a page: elements, attributes, classes, and ids.\n// Generated from crates/ascribe-core/src/names.rs by\n// `ASCRIBE_BLESS=1 cargo test -p ascribe-core --test names`. Don't edit it.\n\n/** `<ascribe-note>`: a note (packages/elements/CONTRACT.md §1). */\nexport const ELEMENT_NOTE = \"ascribe-note\";\n\n/** `<ascribe-steps>`: a procedure (packages/elements/CONTRACT.md §2). */\nexport const ELEMENT_STEPS = \"ascribe-steps\";\n\n/** `<ascribe-tabs>`: a variant group of several arms (packages/elements/CONTRACT.md §3). */\nexport const ELEMENT_TABS = \"ascribe-tabs\";\n\n/** `<ascribe-tab>`: one arm of `<ascribe-tabs>` (packages/elements/CONTRACT.md §3). */\nexport const ELEMENT_TAB = \"ascribe-tab\";\n\n/** `<ascribe-availability>`: a badge or filter (packages/elements/CONTRACT.md §4). */\nexport const ELEMENT_AVAILABILITY = \"ascribe-availability\";\n\n/**\n * `<ascribe-availability-target>`: one target of `<ascribe-availability>`\n * (packages/elements/CONTRACT.md §4).\n */\nexport const ELEMENT_AVAILABILITY_TARGET = \"ascribe-availability-target\";\n\n/** `<ascribe-group>`: a project widget's group (packages/elements/CONTRACT.md §6). */\nexport const ELEMENT_GROUP = \"ascribe-group\";\n\n/**\n * `<ascribe-for-agents>`: the visually hidden pointer to `llms.txt` at the top\n * of a page, for agents (packages/elements/CONTRACT.md §8).\n */\nexport const ELEMENT_FOR_AGENTS = \"ascribe-for-agents\";\n\n/** `<ascribe-attributes>`: the attribute marker (site-render contract §1). */\nexport const ELEMENT_ATTRIBUTES = \"ascribe-attributes\";\n\n/** The name an anchor comment starts with, `<!--ascribe-anchor` (site-render contract §7.3). */\nexport const COMMENT_ANCHOR = \"ascribe-anchor\";\n\n/** A block's source anchor (site-render contract §7.1). */\nexport const DATA_SOURCE = \"data-ascribe-source\";\n\n/** The includes a block came through (site-render contract §7.1). */\nexport const DATA_VIA = \"data-ascribe-via\";\n\n/** A glossary term's id, on its link (packages/elements/CONTRACT.md §7). */\nexport const DATA_TERM = \"data-ascribe-term\";\n\n/** The element the report draws into. */\nexport const ID_REPORT = \"ascribe-review\";\n\n/** The `<script type=\"application/json\">` holding the report's data. */\nexport const ID_REPORT_DATA = \"ascribe-review-data\";\n\n/**\n * The page's light or dark scheme, `light` or `dark`, on the root\n * element (docs/content/guides/astro.md).\n */\nexport const DATA_SCHEME = \"data-ascribe-scheme\";\n\n/** On a marked page's root: what it shows, `changes`, `will`, or `was`. */\nexport const DATA_SHOW = \"data-ascribe-show\";\n\n/**\n * On a marked block: its change, `added`, `changed`, `removed`, `moved`,\n * or `moved-from`.\n */\nexport const DATA_CHANGE = \"data-ascribe-change\";\n\n/** On every element review adds to the page, so it can be taken away again. */\nexport const DATA_UI = \"data-ascribe-ui\";\n\n/** On a change's label: the change. */\nexport const DATA_LABEL = \"data-ascribe-label\";\n\n/** On a tab's hint: `added` or `changed`. */\nexport const DATA_HINT = \"data-ascribe-hint\";\n\n/** On a moved block: the id its \"moved from\" link points at. */\nexport const DATA_MOVE = \"data-ascribe-move\";\n\n/** On a changed block whose copy as it was follows it. */\nexport const DATA_HAS_WAS = \"data-ascribe-has-was\";\n\n/** On a copy of a block as it was, shown only with the page as it was. */\nexport const DATA_WAS_ONLY = \"data-ascribe-was-only\";\n\n/** On a changed, removed, or moved block: its source anchor as it was. */\nexport const DATA_WAS_SOURCE = \"data-ascribe-was-source\";\n\n/** On the elements that hold the threads' overlay. */\nexport const DATA_OVERLAY = \"data-ascribe-overlay\";\n\n/** On the style element the Astro dev toolbar adds for the marks. */\nexport const DATA_REVIEW = \"data-ascribe-review\";\n\n/** On the root of a page whose changes are marked. */\nexport const CLASS_MARKS = \"ascribe-marks\";\n\n/** A change's label. */\nexport const CLASS_LABEL = \"ascribe-label\";\n\n/** A label placed before its block rather than over it. */\nexport const CLASS_LABEL_BEFORE = \"ascribe-label-before\";\n\n/** Inserted text. */\nexport const CLASS_INS = \"ascribe-ins\";\n\n/** Deleted text. */\nexport const CLASS_DEL = \"ascribe-del\";\n\n/** A removed block. */\nexport const CLASS_REMOVED = \"ascribe-removed\";\n\n/** A removed block's content. */\nexport const CLASS_REMOVED_BODY = \"ascribe-removed-body\";\n\n/** The button that shows or collapses a removed block. */\nexport const CLASS_TOGGLE = \"ascribe-toggle\";\n\n/** On a removed block that's shown. */\nexport const CLASS_OPEN = \"ascribe-open\";\n\n/** What stands where a moved block was. */\nexport const CLASS_MOVED_FROM = \"ascribe-moved-from\";\n\n/** The note with a moved block's link. */\nexport const CLASS_MOVE_NOTE = \"ascribe-move-note\";\n\n/** A move note after its block, not inside it. */\nexport const CLASS_MOVE_AFTER = \"ascribe-move-after\";\n\n/** The link between a moved block and where it was. */\nexport const CLASS_MOVE_LINK = \"ascribe-move-link\";\n\n/** A moved block's copy as it was, where it was. */\nexport const CLASS_WAS_COPY = \"ascribe-was-copy\";\n\n/** A tab's hint that it holds changes. */\nexport const CLASS_HINT = \"ascribe-hint\";\n\n/** A block flashed after going to it. */\nexport const CLASS_FLASH = \"ascribe-flash\";\n\n/** The tip that shows a block's source on hover and focus. */\nexport const CLASS_WHERE = \"ascribe-where\";\n"]}
@@ -1,4 +1,4 @@
1
1
  export { createOverlay, type Overlay, type OverlayOptions } from "./overlay.js";
2
2
  export { renderMarkdown, safeUrl } from "./markdown.js";
3
- export type { Anchor, CommentTarget, FormattedPiece, HostError, LocatedThread, OverlayData, OverlayHost, PendingReview, PlacedThreads, ReviewEvent, ThreadSummary, } from "./types.js";
3
+ export type { Anchor, CommentTarget, FormattedPiece, HostError, LocatedThread, OverlayData, OverlayHost, PendingReview, PlacedThreads, PromptRequest, ReviewEvent, ThreadSummary, } from "./types.js";
4
4
  export type { Author, Thread, ThreadComment } from "../shared/types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/overlay/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yEAAyE;AACzE,4DAA4D;AAC5D,+CAA+C;AAC/C,OAAO,EAAE,aAAa,EAAqC,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC","sourcesContent":["// @ascribed/review/overlay: the review overlay, in the browser. A pull\n// request's threads beside the blocks of a rendered page, with replying,\n// resolving, commenting, and submitting, all through a host\n// (`OverlayHost`) that keeps GitHub to itself.\nexport { createOverlay, type Overlay, type OverlayOptions } from \"./overlay.js\";\nexport { renderMarkdown, safeUrl } from \"./markdown.js\";\nexport type {\n Anchor,\n CommentTarget,\n FormattedPiece,\n HostError,\n LocatedThread,\n OverlayData,\n OverlayHost,\n PendingReview,\n PlacedThreads,\n ReviewEvent,\n ThreadSummary,\n} from \"./types.js\";\nexport type { Author, Thread, ThreadComment } from \"../shared/types.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/overlay/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,yEAAyE;AACzE,4DAA4D;AAC5D,+CAA+C;AAC/C,OAAO,EAAE,aAAa,EAAqC,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC","sourcesContent":["// @ascribed/review/overlay: the review overlay, in the browser. A pull\n// request's threads beside the blocks of a rendered page, with replying,\n// resolving, commenting, and submitting, all through a host\n// (`OverlayHost`) that keeps GitHub to itself.\nexport { createOverlay, type Overlay, type OverlayOptions } from \"./overlay.js\";\nexport { renderMarkdown, safeUrl } from \"./markdown.js\";\nexport type {\n Anchor,\n CommentTarget,\n FormattedPiece,\n HostError,\n LocatedThread,\n OverlayData,\n OverlayHost,\n PendingReview,\n PlacedThreads,\n PromptRequest,\n ReviewEvent,\n ThreadSummary,\n} from \"./types.js\";\nexport type { Author, Thread, ThreadComment } from \"../shared/types.js\";\n"]}
@@ -718,6 +718,11 @@ class ReviewOverlay {
718
718
  if (entry && !entry.removed) {
719
719
  acts.append(this.button("link", "Open source", () => this.host.openSource(threadSource(thread, entry))));
720
720
  }
721
+ if (this.host.promptAgent) {
722
+ const prompt = this.button("link", "Prompt agent", () => void this.promptAgent({ kind: "thread", threadId: thread.id }), `prompt:${thread.id}`);
723
+ prompt.title = "Prompt agent: address this comment";
724
+ acts.append(prompt);
725
+ }
721
726
  // A detached thread has no block to go to: GitHub shows it in full.
722
727
  const url = thread.comments.at(-1)?.url ?? thread.comments[0]?.url;
723
728
  if (thread.detached !== undefined && url !== undefined && /^https?:\/\//i.test(url)) {
@@ -917,7 +922,14 @@ class ReviewOverlay {
917
922
  drawAll(dialog, data) {
918
923
  dialog.setAttribute("aria-label", "All comments");
919
924
  const head = this.el("div", "row");
920
- head.append(this.el("h2", "", `All comments on #${data.pullRequest.number}`), this.el("span", "spacer"), this.button("ghost", "Close", () => this.closeDialog(), "dialog-close"));
925
+ head.append(this.el("h2", "", `All comments on #${data.pullRequest.number}`), this.el("span", "spacer"));
926
+ const open = this.all?.some((s) => !s.thread.resolved && !s.thread.comments.every((c) => c.pending));
927
+ if (this.host.promptAgent && open) {
928
+ const prompt = this.button("ghost", "Prompt agent: all open", () => void this.promptAgent({ kind: "open-threads" }), "prompt-all");
929
+ prompt.title = "A prompt for your agent to address every open comment";
930
+ head.append(prompt);
931
+ }
932
+ head.append(this.button("ghost", "Close", () => this.closeDialog(), "dialog-close"));
921
933
  dialog.append(head);
922
934
  const all = this.all;
923
935
  if (all === undefined) {
@@ -1364,6 +1376,19 @@ class ReviewOverlay {
1364
1376
  this.focusOn(key);
1365
1377
  }
1366
1378
  }
1379
+ /** Asks the host for a prompt; says so when it couldn't build one. */
1380
+ async promptAgent(request) {
1381
+ try {
1382
+ await this.host.promptAgent?.(request);
1383
+ }
1384
+ catch (error) {
1385
+ const message = `The prompt couldn't be built: ${messageOf(error)}`;
1386
+ if (this.host.notify)
1387
+ this.host.notify(message);
1388
+ else
1389
+ this.announce(message);
1390
+ }
1391
+ }
1367
1392
  async resolve(thread) {
1368
1393
  if (this.busy.has(thread.id))
1369
1394
  return;