@dev.fast/whiteboard 0.0.0-stage → 0.2.1-preview.20261005.98
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 +21 -0
- package/README.md +146 -2
- package/THIRD_PARTY_NOTICES.md +93 -0
- package/dist/account-alias-DOHN1RSH.js +973 -0
- package/dist/agent-cli-ChOq0ZuO.js +117 -0
- package/dist/agent-cli-DzWfZr6G.js +2 -0
- package/dist/agent-client-CPQo7iTI.js +305 -0
- package/dist/authoring-tools-Cr6Kpjsx.js +234 -0
- package/dist/build-info.json +1 -0
- package/dist/cli-hVgHcdsv.js +47 -0
- package/dist/cli-runner-D8y2_luU.js +5095 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +128 -0
- package/dist/client-DFoh50W3.js +950 -0
- package/dist/desktop-discovery-DBO-PM2V.js +164 -0
- package/dist/error-message-OtiDonty.js +6 -0
- package/dist/fs-utils-BMPLt0cr.js +22 -0
- package/dist/fuzzy-match-BoAmcyak.js +30 -0
- package/dist/headless-host-BJxf4dUU.js +94 -0
- package/dist/input-error-OLgB_h31.js +11 -0
- package/dist/local-data-CxLk75rx.js +4491 -0
- package/dist/mcp-QeF8vkdf.js +140 -0
- package/dist/package-paths-B6-zxvIO.js +47 -0
- package/dist/process-error-telemetry-v9e5h6D7.js +3952 -0
- package/dist/profile-4ry2f3AM.js +76 -0
- package/dist/profile-Bh5FIrmf.js +2 -0
- package/dist/request-origin-D_4QIdoN.js +811 -0
- package/dist/review-agent-traces-DNJFYzc8.js +3866 -0
- package/dist/review-home-paths-6zZH-1c9.js +2560 -0
- package/dist/review-telemetry-DEFnQsQ2.js +1195 -0
- package/dist/runtime-CKiWhGrf.js +52 -0
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.js +2 -0
- package/dist/s3-SzLpeCG6.js +345 -0
- package/dist/s3-config-BgxkSoOy.js +2 -0
- package/dist/s3-config-CAxhO9u_.js +676 -0
- package/dist/server/desktop-host.d.ts +4 -0
- package/dist/server/desktop-host.js +1702 -0
- package/dist/server-discovery-Dbzn3w6Z.js +66 -0
- package/dist/sharing/index.d.ts +1977 -0
- package/dist/sharing/index.js +2 -0
- package/dist/src-CGP5ytbV.js +283 -0
- package/dist/src-CmdiBL20.js +3676 -0
- package/dist/src-DnwdaQ2r.js +865 -0
- package/dist/src-X9phtB2j.js +68 -0
- package/dist/stored-document-migration-DHeMpLMb.js +2671 -0
- package/dist/tool-failure-C8zB73HV.js +98 -0
- package/dist/tutorial-trace-DrBvkaeL.js +43 -0
- package/instructions/authoring.md +36 -0
- package/instructions/file-lenses.md +13 -0
- package/instructions/scratchpad.md +23 -0
- package/instructions/trace-archaeology.md +113 -0
- package/onboarding.md +8 -0
- package/package.json +106 -3
- package/src/agent-selection.ts +100 -0
- package/src/agent-session-ref.ts +80 -0
- package/src/ask/agents.ts +351 -0
- package/src/ask/checkout-files.ts +47 -0
- package/src/ask/file-refs.ts +111 -0
- package/src/ask/pi-mcp.ts +154 -0
- package/src/ask/protocol.ts +244 -0
- package/src/ask/thread-state.ts +350 -0
- package/src/ask/thread.ts +1400 -0
- package/src/ask/threads.ts +219 -0
- package/src/ask/watch.ts +79 -0
- package/src/cli-install.ts +1041 -0
- package/src/cli-runner.ts +1461 -0
- package/src/cli-runtime-info.ts +35 -0
- package/src/cli.ts +222 -0
- package/src/connect-prompts.ts +248 -0
- package/src/cursor-deeplink.ts +11 -0
- package/src/desktop-discovery.ts +355 -0
- package/src/diff-selection-migration.ts +63 -0
- package/src/embedded-posthog-key.ts +6 -0
- package/src/error-telemetry.ts +247 -0
- package/src/evidence.ts +14 -0
- package/src/exception-telemetry.ts +126 -0
- package/src/fixtures/blocks/call_stack_diff.json +27 -0
- package/src/fixtures/blocks/callout.json +11 -0
- package/src/fixtures/blocks/code.json +9 -0
- package/src/fixtures/blocks/code_peek.json +8 -0
- package/src/fixtures/blocks/database_lens.json +51 -0
- package/src/fixtures/blocks/divider.json +1 -0
- package/src/fixtures/blocks/fixtures.ts +33 -0
- package/src/fixtures/blocks/flow_diagram.json +36 -0
- package/src/fixtures/blocks/ids.ts +8 -0
- package/src/fixtures/blocks/image.json +9 -0
- package/src/fixtures/blocks/markdown.json +7 -0
- package/src/fixtures/blocks/section.json +11 -0
- package/src/fixtures/blocks/sequence.json +31 -0
- package/src/fixtures/blocks/software_map.json +7 -0
- package/src/fixtures/blocks/trace_quote.json +9 -0
- package/src/fixtures/blocks/tutorial.json +51 -0
- package/src/fs-utils.ts +32 -0
- package/src/fuzzy-match.ts +94 -0
- package/src/install.ts +29 -0
- package/src/legacy-skills.ts +133 -0
- package/src/lens-selection.ts +230 -0
- package/src/markdown-latex-math.ts +230 -0
- package/src/markdown.ts +81 -0
- package/src/package-paths.ts +53 -0
- package/src/posthog-capture-client.ts +610 -0
- package/src/review-api/README.md +295 -0
- package/src/review-api/activity.ts +340 -0
- package/src/review-api/agent-cli.ts +219 -0
- package/src/review-api/agent-client.ts +188 -0
- package/src/review-api/anchor-quotes.ts +88 -0
- package/src/review-api/ask-history.ts +242 -0
- package/src/review-api/authoring-tools.ts +211 -0
- package/src/review-api/blocks/call_stack_diff.ts +75 -0
- package/src/review-api/blocks/callout.ts +24 -0
- package/src/review-api/blocks/code.ts +9 -0
- package/src/review-api/blocks/code_peek.ts +13 -0
- package/src/review-api/blocks/database_lens.ts +150 -0
- package/src/review-api/blocks/definition.ts +40 -0
- package/src/review-api/blocks/divider.ts +6 -0
- package/src/review-api/blocks/flow_diagram.ts +111 -0
- package/src/review-api/blocks/image.ts +10 -0
- package/src/review-api/blocks/index.ts +90 -0
- package/src/review-api/blocks/markdown.ts +17 -0
- package/src/review-api/blocks/section.ts +24 -0
- package/src/review-api/blocks/sequence.ts +56 -0
- package/src/review-api/blocks/software_map.ts +9 -0
- package/src/review-api/blocks/trace_quote.ts +10 -0
- package/src/review-api/blocks/tutorial.ts +39 -0
- package/src/review-api/checkout-fs.ts +14 -0
- package/src/review-api/client.ts +1 -0
- package/src/review-api/comparison-coverage.ts +304 -0
- package/src/review-api/component-reference.ts +30 -0
- package/src/review-api/diff-lenses.ts +175 -0
- package/src/review-api/document-headings.ts +51 -0
- package/src/review-api/document-text.ts +200 -0
- package/src/review-api/document.ts +906 -0
- package/src/review-api/file-lenses.ts +100 -0
- package/src/review-api/http.ts +1877 -0
- package/src/review-api/image-decode.ts +29 -0
- package/src/review-api/input-error.ts +9 -0
- package/src/review-api/instructions.ts +89 -0
- package/src/review-api/lens-alignment.ts +52 -0
- package/src/review-api/local-data.ts +1837 -0
- package/src/review-api/map-input.ts +154 -0
- package/src/review-api/mcp-client-agent.ts +32 -0
- package/src/review-api/mcp.ts +240 -0
- package/src/review-api/origin.ts +43 -0
- package/src/review-api/profile.ts +146 -0
- package/src/review-api/public-tools.ts +102 -0
- package/src/review-api/pull-request.ts +389 -0
- package/src/review-api/read-schemas.ts +85 -0
- package/src/review-api/recovery.ts +2 -0
- package/src/review-api/request-origin.ts +33 -0
- package/src/review-api/review-progress.ts +382 -0
- package/src/review-api/status-tool.ts +10 -0
- package/src/review-api/store-schema.ts +22 -0
- package/src/review-api/store.ts +1647 -0
- package/src/review-api/tool-failure.ts +64 -0
- package/src/review-api/trace-schema.ts +13 -0
- package/src/review-api/traces.ts +119 -0
- package/src/review-api/unsupported-files.integration.ts +91 -0
- package/src/review-api/workspaces.ts +692 -0
- package/src/review-api/worktree-source.ts +170 -0
- package/src/review-api/worktree-structural.integration.ts +320 -0
- package/src/review-app-launcher.ts +432 -0
- package/src/review-app-picker.ts +168 -0
- package/src/review-app.ts +134 -0
- package/src/review-bundled-tools.ts +249 -0
- package/src/review-checkout-paths.ts +37 -0
- package/src/review-diff-files.ts +89 -0
- package/src/review-head-checkout.ts +300 -0
- package/src/review-home-paths.ts +73 -0
- package/src/review-info.ts +59 -0
- package/src/review-instances.ts +113 -0
- package/src/review-logger.ts +183 -0
- package/src/review-preferences.ts +88 -0
- package/src/review-prepare.ts +291 -0
- package/src/review-stack.ts +112 -0
- package/src/review-telemetry.ts +1066 -0
- package/src/runtime.ts +74 -0
- package/src/server/account-alias.ts +30 -0
- package/src/server/bounded-stream.ts +34 -0
- package/src/server/bug-report.ts +301 -0
- package/src/server/client-error-budget.ts +45 -0
- package/src/server/crash-report.ts +273 -0
- package/src/server/desktop-host-shutdown.ts +64 -0
- package/src/server/desktop-host.ts +190 -0
- package/src/server/desktop-server.ts +703 -0
- package/src/server/diffr-config.ts +509 -0
- package/src/server/diffr-languages.ts +97 -0
- package/src/server/global-verb-relay.ts +190 -0
- package/src/server/headless-host.ts +149 -0
- package/src/server/hono-http.ts +162 -0
- package/src/server/http-json.ts +24 -0
- package/src/server/json-review-reporting.ts +174 -0
- package/src/server/process-error-telemetry.ts +174 -0
- package/src/server/review-api-parsers.ts +90 -0
- package/src/server/review-lifecycle-telemetry.ts +73 -0
- package/src/server/review-open-watchdog.ts +46 -0
- package/src/server/review-server-core.ts +290 -0
- package/src/server/structural-comparisons.ts +153 -0
- package/src/server/structural-diff.ts +186 -0
- package/src/server/tutorial-service.ts +241 -0
- package/src/server/ui-telemetry.ts +198 -0
- package/src/server-discovery.ts +95 -0
- package/src/session-markers.ts +132 -0
- package/src/sharing/auth.ts +41 -0
- package/src/sharing/cli.ts +83 -0
- package/src/sharing/client.ts +334 -0
- package/src/sharing/export.ts +195 -0
- package/src/sharing/host.ts +316 -0
- package/src/sharing/import.ts +787 -0
- package/src/sharing/index.ts +16 -0
- package/src/sharing/repository.ts +145 -0
- package/src/sharing/routes.ts +34 -0
- package/src/slug.ts +23 -0
- package/src/software-map-diff-counts.ts +517 -0
- package/src/software-map-model.ts +1147 -0
- package/src/software-map-topology-diff.ts +260 -0
- package/src/source.ts +92 -0
- package/src/startup-trace.ts +232 -0
- package/src/stored-document-migration.ts +151 -0
- package/src/telemetry-clean-text.ts +257 -0
- package/src/telemetry-config.ts +298 -0
- package/src/telemetry-debug-sink.ts +38 -0
- package/src/telemetry.ts +13 -0
- package/src/trace-cli.ts +156 -0
- package/src/trace-storage-cli.ts +509 -0
- package/src/tutorial-conversation.ts +18 -0
- package/src/ui-telemetry-events.ts +765 -0
- package/src/unified-diff.ts +71 -0
- package/src/viewed-coverage.ts +259 -0
- package/src/windows-cli.ts +125 -0
- package/tutorial/document.json +275 -0
- package/tutorial/runtime-manifest.json +12 -0
- package/tutorial/sample-service/package.json +9 -0
- package/tutorial/sample-service/src/api/checkout-api.ts +17 -0
- package/tutorial/sample-service/src/app.ts +26 -0
- package/tutorial/sample-service/src/database/schema.ts +10 -0
- package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +15 -0
- package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +24 -0
- package/tutorial/sample-service/src/inventory/inventory-service.ts +11 -0
- package/tutorial/sample-service/src/orders/order-service.ts +34 -0
- package/tutorial/sample-service/src/orders/order.ts +21 -0
- package/tutorial/sample-service/src/orders/orders-repository.ts +24 -0
- package/tutorial/sample-service/src/payments/payment-gateway.ts +12 -0
- package/tutorial/sample-service/src/shipping/shipping-gateway.ts +15 -0
- package/tutorial/sample-service/tsconfig.json +12 -0
- package/tutorial/software-map.json +139 -0
- package/tutorial/trace.json +20 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
activityBeginSchema,
|
|
5
|
+
activityEndSchema,
|
|
6
|
+
activityUpdateSchema,
|
|
7
|
+
} from "./activity.js";
|
|
8
|
+
import { pathTargetSchema, publishedEditSchema } from "./document.js";
|
|
9
|
+
import { instructionsQuerySchema } from "./instructions.js";
|
|
10
|
+
import { uploadSchema } from "./local-data.js";
|
|
11
|
+
import { inspectQuerySchema } from "./read-schemas.js";
|
|
12
|
+
import { REVIEW_STATUS_TOOL } from "./status-tool.js";
|
|
13
|
+
import { commandSchema } from "./store.js";
|
|
14
|
+
|
|
15
|
+
/** The host publishes its input schemas, except where agents write less than
|
|
16
|
+
* the host accepts (publishedEditSchema, publishedLensEditSchema) or name a
|
|
17
|
+
* checkout by path (pathTargetSchema); adapters validate nothing. */
|
|
18
|
+
export function authoringTools(
|
|
19
|
+
scratchpadAvailable = false,
|
|
20
|
+
traceEnabled = false,
|
|
21
|
+
) {
|
|
22
|
+
const id = z.string().min(1);
|
|
23
|
+
const review = { reviewId: id };
|
|
24
|
+
|
|
25
|
+
// Anthropic rejects a top-level union, so publish one object; the host validates the union.
|
|
26
|
+
const [image, trace] = uploadSchema.options;
|
|
27
|
+
|
|
28
|
+
const uploadInput = z
|
|
29
|
+
.strictObject({
|
|
30
|
+
...image.shape,
|
|
31
|
+
...trace.shape,
|
|
32
|
+
kind: z.enum(["image", "trace"]),
|
|
33
|
+
})
|
|
34
|
+
.partial()
|
|
35
|
+
.required({ id: true, repositoryId: true, kind: true });
|
|
36
|
+
|
|
37
|
+
const descriptions = {
|
|
38
|
+
create:
|
|
39
|
+
'Create a review of saved working files, immutable commits or a GitHub PR. Revisions are resolved on acceptance. A worktree target reviews the saved files in its checkout, uncommitted ones included and untracked ones left out (git add -N a new file to include it), against base: the branch to compare against, by default the default branch of the repository. The diff starts at the merge base of base and HEAD, which follows rebases. Omitted commits base means source at head with no diff; supply the parent to review introduced changes. For a GitHub PR, pullRequestUrl alone is enough: target and title become optional, and the host fetches the PR into a registered checkout of its repository and pins the current PR head and GitHub diff base, titled from the PR. When a review for that PR exists, it is returned instead, reporting whether its head moved and whether an agent is working on it; update it in place, move its target with review_set_target, or create a separate review with reuseExisting. kind:"scratchpad" names the one scratchpad, which the host creates itself. The result carries review, the review as review_list shows it: its target with resolved commits, origin (its PR), repositoryName and repositoryPath, so no follow-up read is needed before diffing. When Desktop is available the review opens there and the result reports opened, softwareMapEnabled and environmentIssues, as review_open does; set open:false to author in the background without taking over Desktop.',
|
|
40
|
+
set_target:
|
|
41
|
+
"Move the review to a new target, such as the commits after a rebase or amend, preserving the document and component IDs. Revisions are resolved on acceptance. Returns warnings for retained source ranges in files the new commits changed, to verify, and resources that no longer match; fix them with review_edit. Earlier versions keep their source. Omitted pullRequestUrl keeps the PR within the same repository; changing repositories clears it. Supply a URL to replace it or null to detach.",
|
|
42
|
+
edit: [
|
|
43
|
+
"Edit one document component. content is one component, named by its type; each type's fields are in the Components section of session_get_instructions (authoring or scratchpad).",
|
|
44
|
+
"Anchors are head/path#L10-L20 or base/path#L7, or diff/path#L84-R90 for a range across sides, with repository-relative paths and 1-based inclusive lines. pins {repositoryId, head, base?} say which commits anchors quote: a step, frame, attachment or operation without pins uses its block's, and a block without them the review's.",
|
|
45
|
+
"insert adds content: parentId nests it, afterId places it, and omitted placement appends (on the scratchpad, it lands at the top). update patches a component's own fields; null removes an optional one. A section's children and a diagram's units change through their own IDs; a call stack's frames change by replacing the call stack. move and remove take a targetId; removing a flow_node removes its edges.",
|
|
46
|
+
"The host assigns short durable IDs. The result names the edited component and, for an insert or replace, its first-level children, and quotes the first and last line of each anchor the edit added or changed (the first 20; unquotedAnchors counts the rest): check they are the lines you meant. Accepted edits save at once.",
|
|
47
|
+
"While a reader may be watching, write small and often: one paragraph per edit. Insert a new diagram whole. Change one already drawn a unit at a time (a flow_node, flow_edge or step, with parentId naming the diagram), and link each added flow_node to a node already drawn.",
|
|
48
|
+
].join("\n"),
|
|
49
|
+
lens_edit:
|
|
50
|
+
"Edit one Diff-view lens. Lenses partition the review's change for the Diff view; they sit beside the document (never in it) and version with it. The host assigns durable lens IDs; updates replace only the fields supplied. Write one lens per call while a reader may be watching; each draws in on the Diffs page. Pass your activityId so your courier draws each lens. The result identifies the lens and reports uncategorized: changed lines no lens selects yet, grouped by file. Keep adding lenses until it is empty or what remains is deliberate. review_lens_get reads the current lenses and gaps.",
|
|
51
|
+
rename: "Change the review title.",
|
|
52
|
+
restore:
|
|
53
|
+
"Restore title, source pins, PR identity and content from a saved version.",
|
|
54
|
+
attention:
|
|
55
|
+
"Mark a review viewed, dismissed or restored without changing its content.",
|
|
56
|
+
delete: "Permanently delete this review and its history.",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const tool = (
|
|
60
|
+
name: string,
|
|
61
|
+
description: string,
|
|
62
|
+
schema: z.ZodType,
|
|
63
|
+
method: "GET" | "POST",
|
|
64
|
+
path: string,
|
|
65
|
+
commandType?: string,
|
|
66
|
+
) => ({
|
|
67
|
+
name: `review_${name}`,
|
|
68
|
+
description,
|
|
69
|
+
inputSchema: {
|
|
70
|
+
...z.toJSONSchema(schema, { io: "input" }),
|
|
71
|
+
type: "object" as const,
|
|
72
|
+
},
|
|
73
|
+
method,
|
|
74
|
+
path,
|
|
75
|
+
commandType,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return [
|
|
79
|
+
REVIEW_STATUS_TOOL,
|
|
80
|
+
tool(
|
|
81
|
+
"capabilities",
|
|
82
|
+
"Discover whether Desktop is available and optional software-map generation is enabled. Read before authoring.",
|
|
83
|
+
z.strictObject({}),
|
|
84
|
+
"GET",
|
|
85
|
+
"/capabilities",
|
|
86
|
+
),
|
|
87
|
+
tool(
|
|
88
|
+
"get_instructions",
|
|
89
|
+
'Read Whiteboard\'s guidance before creating or editing a review. The default topic gives the authoring workflow; "file-lenses" covers Diff-view file lenses.' +
|
|
90
|
+
(traceEnabled
|
|
91
|
+
? ' Call review_get_instructions({topic:"trace-archaeology"}) for why code exists, what an agent was thinking, or whether an agent solved this before.'
|
|
92
|
+
: "") +
|
|
93
|
+
(scratchpadAvailable
|
|
94
|
+
? ' When the user asks in conversation to be shown how code works or wants a diagram, without asking for a review, draw it on the scratchpad rather than answering only in chat: call review_get_instructions({topic:"scratchpad"}) first. A request for a review or to use Whiteboard means authoring a review with the default topic.'
|
|
95
|
+
: ""),
|
|
96
|
+
instructionsQuerySchema.partial(),
|
|
97
|
+
"GET",
|
|
98
|
+
"/instructions",
|
|
99
|
+
),
|
|
100
|
+
tool(
|
|
101
|
+
"activity_begin",
|
|
102
|
+
"Show that you are working on a review: the reader sees your focus and your courier drawing your edits, and the review reads as finished only once every agent has ended. Returns an activityId: pass it to session_edit and session_lens_edit so your courier draws them, and to review_activity_update and review_activity_end. Nothing is locked; other agents can work on the review too. The activity expires after 3 minutes without an edit or update.",
|
|
103
|
+
activityBeginSchema.extend(review),
|
|
104
|
+
"POST",
|
|
105
|
+
"/:reviewId/activity/begin",
|
|
106
|
+
),
|
|
107
|
+
tool(
|
|
108
|
+
"activity_update",
|
|
109
|
+
"Change your focus, or keep your activity alive during long reads or pauses; an edit carrying your activityId already keeps it alive. Omitted focus preserves it and null clears it. Fails once the activity has expired: begin a new one.",
|
|
110
|
+
activityUpdateSchema.extend(review),
|
|
111
|
+
"POST",
|
|
112
|
+
"/:reviewId/activity/update",
|
|
113
|
+
),
|
|
114
|
+
tool(
|
|
115
|
+
"activity_end",
|
|
116
|
+
"End your activity when your part is finished: readers treat a review with content and no live activity as ready. Ending it creates no document version. Ending twice, or an expired activity, changes nothing.",
|
|
117
|
+
activityEndSchema.extend(review),
|
|
118
|
+
"POST",
|
|
119
|
+
"/:reviewId/activity/end",
|
|
120
|
+
),
|
|
121
|
+
...commandSchema.shape.operation.options.map((operation) => {
|
|
122
|
+
const type = operation.shape.type.value;
|
|
123
|
+
|
|
124
|
+
// Agents name a checkout by its path; /commands registers it.
|
|
125
|
+
const {
|
|
126
|
+
type: _type,
|
|
127
|
+
repositoryId: _repositoryId,
|
|
128
|
+
...fields
|
|
129
|
+
}: Record<string, z.ZodType> = operation.shape;
|
|
130
|
+
|
|
131
|
+
return tool(
|
|
132
|
+
type,
|
|
133
|
+
descriptions[type],
|
|
134
|
+
z.strictObject({
|
|
135
|
+
...fields,
|
|
136
|
+
...(type === "create" && {
|
|
137
|
+
target: pathTargetSchema.optional(),
|
|
138
|
+
repositoryPath: id
|
|
139
|
+
.optional()
|
|
140
|
+
.describe(
|
|
141
|
+
"Only with pullRequestUrl and no target: the local checkout to fetch the PR into. Default: the existing review's, else the first registered checkout with a remote for the PR's repository.",
|
|
142
|
+
),
|
|
143
|
+
open: z.boolean().optional(),
|
|
144
|
+
}),
|
|
145
|
+
...(type === "set_target" && { target: pathTargetSchema }),
|
|
146
|
+
...(type === "edit" && { edit: publishedEditSchema }),
|
|
147
|
+
}),
|
|
148
|
+
"POST",
|
|
149
|
+
"/commands",
|
|
150
|
+
type,
|
|
151
|
+
);
|
|
152
|
+
}),
|
|
153
|
+
tool("list", "List saved reviews.", z.strictObject({}), "GET", ""),
|
|
154
|
+
tool(
|
|
155
|
+
"get",
|
|
156
|
+
"Read a readable, nested text outline with editable IDs. targetId reads one component in full; full:true reads all content. Use format:json for raw node data or snapshots instead of text.",
|
|
157
|
+
z.strictObject({ ...review, ...inspectQuerySchema.shape }),
|
|
158
|
+
"GET",
|
|
159
|
+
"/:reviewId/inspect",
|
|
160
|
+
),
|
|
161
|
+
tool(
|
|
162
|
+
"lens_get",
|
|
163
|
+
"Read the review's Diff-view lenses as authored (ids, titles, targets), each lens's resolved fileCount (and unavailable reason, if any), and uncategorized: the changed lines no lens selects yet, by file.",
|
|
164
|
+
z.strictObject(review),
|
|
165
|
+
"GET",
|
|
166
|
+
"/:reviewId/lenses",
|
|
167
|
+
),
|
|
168
|
+
tool(
|
|
169
|
+
"history",
|
|
170
|
+
"List saved document versions.",
|
|
171
|
+
z.strictObject(review),
|
|
172
|
+
"GET",
|
|
173
|
+
"/:reviewId/history",
|
|
174
|
+
),
|
|
175
|
+
tool(
|
|
176
|
+
"open",
|
|
177
|
+
"Show an existing review immediately and prepare current pinned checkouts in the background. Returns softwareMapEnabled and any already-recorded environmentIssues. Missing optional setup is not an issue; use review_environment to recheck.",
|
|
178
|
+
z.strictObject(review),
|
|
179
|
+
"POST",
|
|
180
|
+
"/:reviewId/open",
|
|
181
|
+
),
|
|
182
|
+
tool(
|
|
183
|
+
"environment",
|
|
184
|
+
"Acquire and recheck this review's current base/head language checkouts (not historical or selected commits). Returns acquisition issues, not full LSP health. Missing optional setup and failed setup with a usable checkout stay silent. Set retry:true to rerun failed preparation after an actual language-feature failure; preparation runs in the background.",
|
|
185
|
+
z.strictObject({ ...review, retry: z.boolean().optional() }),
|
|
186
|
+
"POST",
|
|
187
|
+
"/:reviewId/environment",
|
|
188
|
+
),
|
|
189
|
+
tool(
|
|
190
|
+
"workspace_cleanup",
|
|
191
|
+
"Inspect failed cleanup of retired review-owned checkouts. Supply workspaceId to retry removal of that checkout. This does not remove active review checkouts.",
|
|
192
|
+
z.strictObject({ workspaceId: id.optional() }),
|
|
193
|
+
"POST",
|
|
194
|
+
"/workspace-cleanup",
|
|
195
|
+
),
|
|
196
|
+
tool(
|
|
197
|
+
"register_repository",
|
|
198
|
+
"Register a local Git or jj repository and return its id, for pins on scratchpad blocks. session_create and session_set_target take the checkout's path instead.",
|
|
199
|
+
z.strictObject({ path: id }),
|
|
200
|
+
"POST",
|
|
201
|
+
"/repositories",
|
|
202
|
+
),
|
|
203
|
+
tool(
|
|
204
|
+
"upload",
|
|
205
|
+
'Retain an image or trace for use in a review. kind:"image" takes base64; kind:"trace" takes trace. Reusing an upload ID requires identical content; rejected uploads are not saved.',
|
|
206
|
+
uploadInput,
|
|
207
|
+
"POST",
|
|
208
|
+
"/resources",
|
|
209
|
+
),
|
|
210
|
+
];
|
|
211
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { anchorSchema } from "@review/lens-selection.js";
|
|
2
|
+
import { ReviewInputError } from "@review/review-api/input-error.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type BlockDefinition,
|
|
7
|
+
defineBlock,
|
|
8
|
+
elementPins,
|
|
9
|
+
identity,
|
|
10
|
+
label,
|
|
11
|
+
} from "./definition.js";
|
|
12
|
+
|
|
13
|
+
export const frameSchema = z.strictObject({
|
|
14
|
+
...identity,
|
|
15
|
+
// Optional component-local name for the same frame on both sides (even if moved).
|
|
16
|
+
key: label.optional(),
|
|
17
|
+
parentKey: label.nullable().optional(),
|
|
18
|
+
callSite: anchorSchema.optional(),
|
|
19
|
+
source: anchorSchema,
|
|
20
|
+
contextSources: z
|
|
21
|
+
.array(anchorSchema)
|
|
22
|
+
.max(1000)
|
|
23
|
+
.optional()
|
|
24
|
+
.describe(
|
|
25
|
+
"Supporting code owned by this frame, such as field initializers. These ranges share the frame’s diff section and coverage; they do not create call edges.",
|
|
26
|
+
),
|
|
27
|
+
label: label.optional(),
|
|
28
|
+
pins: elementPins,
|
|
29
|
+
via: z
|
|
30
|
+
.strictObject({
|
|
31
|
+
kind: z.enum(["call", "queue", "callback", "rpc"]),
|
|
32
|
+
reason: label,
|
|
33
|
+
})
|
|
34
|
+
.optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type Frame = z.infer<typeof frameSchema>;
|
|
38
|
+
|
|
39
|
+
const schema = defineBlock("call_stack_diff", {
|
|
40
|
+
title: label,
|
|
41
|
+
base: z.array(frameSchema),
|
|
42
|
+
head: z.array(frameSchema),
|
|
43
|
+
pins: elementPins,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export type CallStackDiffBlock = z.infer<typeof schema>;
|
|
47
|
+
|
|
48
|
+
export const call_stack_diff = {
|
|
49
|
+
type: "call_stack_diff",
|
|
50
|
+
schema,
|
|
51
|
+
check(block: CallStackDiffBlock) {
|
|
52
|
+
for (const side of ["base", "head"] as const) {
|
|
53
|
+
const keys = block[side].flatMap((frame) =>
|
|
54
|
+
frame.key ? [frame.key] : [],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (new Set(keys).size !== keys.length)
|
|
58
|
+
throw new ReviewInputError(`Frame keys must be unique within ${side}.`);
|
|
59
|
+
|
|
60
|
+
for (const [index, frame] of block[side].entries()) {
|
|
61
|
+
if (
|
|
62
|
+
frame.parentKey &&
|
|
63
|
+
!block[side]
|
|
64
|
+
.slice(0, index)
|
|
65
|
+
.some((parent) => parent.key === frame.parentKey)
|
|
66
|
+
)
|
|
67
|
+
throw new ReviewInputError(
|
|
68
|
+
"A frame parentKey must name an earlier frame on the same side.",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Columns may compare two paths in one snapshot. Each source keeps its own pin.
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
} satisfies BlockDefinition<CallStackDiffBlock>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { type BlockDefinition, defineBlock, label } from "./definition.js";
|
|
4
|
+
import { type Block, blockSchema } from "./index.js";
|
|
5
|
+
|
|
6
|
+
export interface CalloutBlock {
|
|
7
|
+
id?: string;
|
|
8
|
+
type: "callout";
|
|
9
|
+
title?: string;
|
|
10
|
+
tone: "info" | "warning" | "danger" | "success";
|
|
11
|
+
children: Block[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// blockSchema is read inside z.lazy, after every module in the cycle has evaluated.
|
|
15
|
+
export const calloutSchema = defineBlock("callout", {
|
|
16
|
+
title: label.optional(),
|
|
17
|
+
tone: z.enum(["info", "warning", "danger", "success"]).default("info"),
|
|
18
|
+
children: z.array(z.lazy((): z.ZodType<Block> => blockSchema)),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const callout: BlockDefinition<CalloutBlock> = {
|
|
22
|
+
type: "callout",
|
|
23
|
+
schema: calloutSchema,
|
|
24
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineBlock, text } from "./definition.js";
|
|
2
|
+
|
|
3
|
+
/** Shared with sequence steps. */
|
|
4
|
+
export const codeFields = { language: text.default("text"), text };
|
|
5
|
+
|
|
6
|
+
export const code = {
|
|
7
|
+
type: "code",
|
|
8
|
+
schema: defineBlock("code", { ...codeFields, caption: text.optional() }),
|
|
9
|
+
} as const;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { anchorSchema } from "@review/lens-selection.js";
|
|
2
|
+
|
|
3
|
+
import { defineBlock, elementPins, text } from "./definition.js";
|
|
4
|
+
|
|
5
|
+
export const code_peek = {
|
|
6
|
+
type: "code_peek",
|
|
7
|
+
schema: defineBlock("code_peek", {
|
|
8
|
+
source: anchorSchema,
|
|
9
|
+
pins: elementPins,
|
|
10
|
+
// Not rendered yet.
|
|
11
|
+
caption: text.optional(),
|
|
12
|
+
}),
|
|
13
|
+
} as const;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { type JsonValue, jsonValueSchema } from "@dev.fast/review-protocol";
|
|
2
|
+
import { anchorSchema } from "@review/lens-selection.js";
|
|
3
|
+
import { ReviewInputError } from "@review/review-api/input-error.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type BlockDefinition,
|
|
8
|
+
defineBlock,
|
|
9
|
+
elementPins,
|
|
10
|
+
identity,
|
|
11
|
+
label,
|
|
12
|
+
requireKey,
|
|
13
|
+
text,
|
|
14
|
+
} from "./definition.js";
|
|
15
|
+
|
|
16
|
+
export interface DatabaseField {
|
|
17
|
+
label: string;
|
|
18
|
+
dataType: string;
|
|
19
|
+
nullable?: boolean;
|
|
20
|
+
primaryKey?: boolean;
|
|
21
|
+
references?: { store: string; collection: string; field: string };
|
|
22
|
+
/** An illustrative value shown beside the field. */
|
|
23
|
+
example?: JsonValue;
|
|
24
|
+
/** Nested fields of a document-store object field. */
|
|
25
|
+
fields?: Record<string, DatabaseField>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const fieldSchema: z.ZodType<DatabaseField> = z.lazy(() =>
|
|
29
|
+
z.strictObject({
|
|
30
|
+
label,
|
|
31
|
+
dataType: label,
|
|
32
|
+
nullable: z.boolean().optional(),
|
|
33
|
+
primaryKey: z.boolean().optional(),
|
|
34
|
+
references: z
|
|
35
|
+
.strictObject({ store: label, collection: label, field: label })
|
|
36
|
+
.optional(),
|
|
37
|
+
example: jsonValueSchema.optional(),
|
|
38
|
+
fields: z.record(text, fieldSchema).optional(),
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export const databaseActorSchema = z.union([
|
|
43
|
+
label,
|
|
44
|
+
z.strictObject({ label, softwareMapPath: label.optional() }),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export const storeSchema = z.strictObject({
|
|
48
|
+
label,
|
|
49
|
+
storage: z.enum(["relational", "document"]),
|
|
50
|
+
dataStoreKind: z
|
|
51
|
+
.enum(["database", "objectStore", "bucket", "artifactStore", "fileStore"])
|
|
52
|
+
.optional(),
|
|
53
|
+
softwareMapPath: label.optional(),
|
|
54
|
+
collections: z.record(
|
|
55
|
+
text,
|
|
56
|
+
z.strictObject({
|
|
57
|
+
label,
|
|
58
|
+
key: label.optional(),
|
|
59
|
+
fields: z.record(text, fieldSchema),
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const operationSchema = z.strictObject({
|
|
65
|
+
...identity,
|
|
66
|
+
kind: z.enum(["read", "write"]),
|
|
67
|
+
store: label,
|
|
68
|
+
collection: label,
|
|
69
|
+
field: label.optional(),
|
|
70
|
+
actor: label,
|
|
71
|
+
label,
|
|
72
|
+
detail: label.optional(),
|
|
73
|
+
source: anchorSchema,
|
|
74
|
+
pins: elementPins,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// The legacy MDX audit rejected these empty forms after a lens with
|
|
78
|
+
// unresolved operations published clean and rendered a blank canvas.
|
|
79
|
+
export const databaseLensSchema = defineBlock("database_lens", {
|
|
80
|
+
title: label,
|
|
81
|
+
actors: z.record(text, databaseActorSchema),
|
|
82
|
+
stores: z
|
|
83
|
+
.record(text, storeSchema)
|
|
84
|
+
.refine(
|
|
85
|
+
(stores) => Object.keys(stores).length > 0,
|
|
86
|
+
"A database lens needs at least one store.",
|
|
87
|
+
),
|
|
88
|
+
useCases: z
|
|
89
|
+
.array(
|
|
90
|
+
z.strictObject({
|
|
91
|
+
...identity,
|
|
92
|
+
label,
|
|
93
|
+
summary: text.optional(),
|
|
94
|
+
operations: z
|
|
95
|
+
.array(operationSchema)
|
|
96
|
+
.min(1, "A use case needs at least one operation."),
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
.min(1, "A database lens needs at least one use case."),
|
|
100
|
+
pins: elementPins,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export type DatabaseLensBlock = z.infer<typeof databaseLensSchema>;
|
|
104
|
+
|
|
105
|
+
export const database_lens = {
|
|
106
|
+
type: "database_lens",
|
|
107
|
+
schema: databaseLensSchema,
|
|
108
|
+
check(block: DatabaseLensBlock) {
|
|
109
|
+
const field = (store: string, collection: string, name?: string) => {
|
|
110
|
+
requireKey(block.stores, store);
|
|
111
|
+
const collections = block.stores[store]!.collections;
|
|
112
|
+
requireKey(collections, collection);
|
|
113
|
+
|
|
114
|
+
if (name === undefined) return;
|
|
115
|
+
|
|
116
|
+
// Nested document fields are addressed by dotted path.
|
|
117
|
+
let fields: Record<string, DatabaseField> | undefined =
|
|
118
|
+
collections[collection]!.fields;
|
|
119
|
+
|
|
120
|
+
for (const part of name.split(".")) {
|
|
121
|
+
if (!fields)
|
|
122
|
+
throw new ReviewInputError(`Unknown component name: ${name}`);
|
|
123
|
+
requireKey(fields, part);
|
|
124
|
+
fields = fields[part]!.fields;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
for (const useCase of block.useCases)
|
|
129
|
+
for (const op of useCase.operations) {
|
|
130
|
+
requireKey(block.actors, op.actor);
|
|
131
|
+
field(op.store, op.collection, op.field);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const store of Object.values(block.stores))
|
|
135
|
+
for (const collection of Object.values(store.collections))
|
|
136
|
+
for (const value of Object.values(collection.fields))
|
|
137
|
+
if (value.references)
|
|
138
|
+
field(
|
|
139
|
+
value.references.store,
|
|
140
|
+
value.references.collection,
|
|
141
|
+
value.references.field,
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
} satisfies BlockDefinition<DatabaseLensBlock>;
|
|
145
|
+
|
|
146
|
+
export type DatabaseActor = z.infer<typeof databaseActorSchema>;
|
|
147
|
+
|
|
148
|
+
export type DatabaseStore = z.infer<typeof storeSchema>;
|
|
149
|
+
|
|
150
|
+
export type DatabaseOperation = z.infer<typeof operationSchema>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ReviewInputError } from "@review/review-api/input-error.js";
|
|
2
|
+
import { sourcePinsSchema } from "@review/source.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
export const text = z.string();
|
|
6
|
+
|
|
7
|
+
export const label = text.trim().min(1);
|
|
8
|
+
|
|
9
|
+
export const identity = { id: text.optional() };
|
|
10
|
+
|
|
11
|
+
/** The repository and commits an element's anchors quote. A step, frame,
|
|
12
|
+
* attachment or operation without them reads its block's; a block without
|
|
13
|
+
* them reads its document's. */
|
|
14
|
+
export const elementPins = sourcePinsSchema
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Pins for the anchors here; needed on the scratchpad.");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* One block kind: the strict schema the store parses with, and the rules a
|
|
20
|
+
* field schema cannot express. `check` throws ReviewInputError.
|
|
21
|
+
*/
|
|
22
|
+
export interface BlockDefinition<Content extends { type: string }> {
|
|
23
|
+
type: Content["type"];
|
|
24
|
+
schema: z.ZodType<Content>;
|
|
25
|
+
check?(block: Content): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The strict schema for a block's own fields; id and type are added here. */
|
|
29
|
+
export function defineBlock<
|
|
30
|
+
Type extends string,
|
|
31
|
+
Props extends Record<string, z.ZodType>,
|
|
32
|
+
>(type: Type, props: Props) {
|
|
33
|
+
return z.strictObject({ ...identity, type: z.literal(type), ...props });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Shared by every check that resolves a component-local name. */
|
|
37
|
+
export function requireKey<T>(record: Record<string, T>, name: string): void {
|
|
38
|
+
if (!Object.hasOwn(record, name))
|
|
39
|
+
throw new ReviewInputError(`Unknown component name: ${name}`);
|
|
40
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DiffSelection,
|
|
3
|
+
anchorSchema,
|
|
4
|
+
anchorSelection,
|
|
5
|
+
} from "@review/lens-selection.js";
|
|
6
|
+
import { ReviewInputError } from "@review/review-api/input-error.js";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
type BlockDefinition,
|
|
11
|
+
defineBlock,
|
|
12
|
+
elementPins,
|
|
13
|
+
identity,
|
|
14
|
+
label,
|
|
15
|
+
} from "./definition.js";
|
|
16
|
+
|
|
17
|
+
/** Nodes and edges are the diagram's children: each is its own edit target,
|
|
18
|
+
* so an agent can draw a diagram one unit at a time. Edges still name nodes
|
|
19
|
+
* by their diagram-local key. */
|
|
20
|
+
export const flowNodeSchema = z.strictObject({
|
|
21
|
+
...identity,
|
|
22
|
+
type: z.literal("flow_node").default("flow_node"),
|
|
23
|
+
key: label,
|
|
24
|
+
label,
|
|
25
|
+
description: z.string().optional(),
|
|
26
|
+
kind: z.enum(["process", "decision", "terminal"]).optional(),
|
|
27
|
+
attachments: z
|
|
28
|
+
.array(
|
|
29
|
+
z.strictObject({
|
|
30
|
+
label,
|
|
31
|
+
sources: z.array(anchorSchema).min(1),
|
|
32
|
+
pins: elementPins,
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
.default([])
|
|
36
|
+
.describe(
|
|
37
|
+
"Code evidence for this node. Omit for nodes no code backs, such as start or exit states.",
|
|
38
|
+
),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const flowEdgeSchema = z.strictObject({
|
|
42
|
+
...identity,
|
|
43
|
+
type: z.literal("flow_edge").default("flow_edge"),
|
|
44
|
+
from: label,
|
|
45
|
+
to: label,
|
|
46
|
+
label: z.string().optional(),
|
|
47
|
+
style: z.enum(["solid", "dashed"]).optional(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** The edge a new node arrives with: `from` a node already drawn to this
|
|
51
|
+
* one, or from this one `to` a node already drawn. Insert-time only; the
|
|
52
|
+
* server stores it as an ordinary edge. */
|
|
53
|
+
export const flowLinkSchema = z.strictObject({
|
|
54
|
+
from: label.optional(),
|
|
55
|
+
to: label.optional(),
|
|
56
|
+
label: z.string().optional(),
|
|
57
|
+
style: z.enum(["solid", "dashed"]).optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const flowNodeInsertSchema = flowNodeSchema.extend({
|
|
61
|
+
link: flowLinkSchema.optional(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const flowDiagramSchema = defineBlock("flow_diagram", {
|
|
65
|
+
title: label,
|
|
66
|
+
description: z.string().optional(),
|
|
67
|
+
direction: z.enum(["right", "down"]).optional(),
|
|
68
|
+
nodes: z.array(flowNodeSchema).min(1).max(100),
|
|
69
|
+
edges: z.array(flowEdgeSchema).max(300),
|
|
70
|
+
pins: elementPins,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export type FlowDiagramBlock = z.infer<typeof flowDiagramSchema>;
|
|
74
|
+
|
|
75
|
+
export type FlowDiagramNode = z.infer<typeof flowNodeSchema>;
|
|
76
|
+
|
|
77
|
+
/** A node's evidence, each anchor read at its attachment's pins, else the
|
|
78
|
+
* diagram's. */
|
|
79
|
+
export function nodeSources(
|
|
80
|
+
node: FlowDiagramNode,
|
|
81
|
+
diagramPins: FlowDiagramBlock["pins"],
|
|
82
|
+
): { label: string; source: DiffSelection }[] {
|
|
83
|
+
return node.attachments.flatMap((attachment) =>
|
|
84
|
+
attachment.sources.map((anchor) => ({
|
|
85
|
+
label: attachment.label,
|
|
86
|
+
source: anchorSelection(anchor, attachment.pins ?? diagramPins),
|
|
87
|
+
})),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type FlowDiagramEdge = z.infer<typeof flowEdgeSchema>;
|
|
92
|
+
|
|
93
|
+
export const flow_diagram = {
|
|
94
|
+
type: "flow_diagram",
|
|
95
|
+
schema: flowDiagramSchema,
|
|
96
|
+
check(block: FlowDiagramBlock) {
|
|
97
|
+
const keys = new Set(block.nodes.map((node) => node.key));
|
|
98
|
+
|
|
99
|
+
if (keys.size !== block.nodes.length)
|
|
100
|
+
throw new ReviewInputError(
|
|
101
|
+
"Flow node keys must be unique within the diagram.",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
for (const edge of block.edges) {
|
|
105
|
+
if (!keys.has(edge.from) || !keys.has(edge.to))
|
|
106
|
+
throw new ReviewInputError(
|
|
107
|
+
`Unknown flow endpoint: ${edge.from} → ${edge.to}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
} satisfies BlockDefinition<FlowDiagramBlock>;
|