@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,170 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
|
|
6
|
+
import { type LocalVcs, gitCommonDir } from "@dev.fast/local-vcs";
|
|
7
|
+
import { isMissingFileError } from "@review/fs-utils.js";
|
|
8
|
+
|
|
9
|
+
import { checkoutFs } from "./checkout-fs.js";
|
|
10
|
+
import { ReviewInputError } from "./document.js";
|
|
11
|
+
|
|
12
|
+
const { lstat, readFile, readlink, realpath } = checkoutFs;
|
|
13
|
+
|
|
14
|
+
const exec = promisify(execFile);
|
|
15
|
+
|
|
16
|
+
export const EMPTY_SOURCE = "empty-worktree-baseline";
|
|
17
|
+
|
|
18
|
+
/** Never follow a working-copy link outside the selected checkout. */
|
|
19
|
+
export async function localSourcePath(
|
|
20
|
+
root: string,
|
|
21
|
+
file: string,
|
|
22
|
+
): Promise<string> {
|
|
23
|
+
const canonicalRoot = await realpath(root);
|
|
24
|
+
const candidate = await realpath(resolve(canonicalRoot, file));
|
|
25
|
+
const child = relative(canonicalRoot, candidate);
|
|
26
|
+
|
|
27
|
+
if (
|
|
28
|
+
!child ||
|
|
29
|
+
isAbsolute(child) ||
|
|
30
|
+
child === ".." ||
|
|
31
|
+
child.startsWith(`..${sep}`)
|
|
32
|
+
)
|
|
33
|
+
throw new ReviewInputError("Source symlink leaves the selected worktree.");
|
|
34
|
+
const stat = await lstat(candidate);
|
|
35
|
+
|
|
36
|
+
if (!stat.isFile())
|
|
37
|
+
throw new ReviewInputError("Source is not a regular file.");
|
|
38
|
+
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** List the live checkout's tracked files. jj tracks new files itself; Git's untracked files are left out. */
|
|
43
|
+
export async function workingFiles(vcs: LocalVcs): Promise<string[]> {
|
|
44
|
+
const gitDirectory =
|
|
45
|
+
vcs.kind === "jj" ? await gitCommonDir(vcs.rootPath) : undefined;
|
|
46
|
+
|
|
47
|
+
if (vcs.kind === "jj" && !gitDirectory)
|
|
48
|
+
throw new ReviewInputError(
|
|
49
|
+
"Working source requires a Git-backed jj repository.",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const { stdout } = await exec(
|
|
53
|
+
"git",
|
|
54
|
+
[
|
|
55
|
+
"-C",
|
|
56
|
+
vcs.rootPath,
|
|
57
|
+
...(gitDirectory
|
|
58
|
+
? ["--git-dir", gitDirectory, "--work-tree", vcs.rootPath]
|
|
59
|
+
: []),
|
|
60
|
+
"ls-files",
|
|
61
|
+
"-z",
|
|
62
|
+
...(vcs.kind === "git"
|
|
63
|
+
? ["--cached"]
|
|
64
|
+
: ["--others", "--exclude-standard", "--exclude=.jj/"]),
|
|
65
|
+
],
|
|
66
|
+
{ maxBuffer: 32 * 1024 * 1024 },
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const paths = [
|
|
70
|
+
...new Set([
|
|
71
|
+
...stdout.split("\0").filter(Boolean),
|
|
72
|
+
...(vcs.kind === "jj" ? await vcs.listTrackedFiles() : []),
|
|
73
|
+
]),
|
|
74
|
+
].sort();
|
|
75
|
+
|
|
76
|
+
const present = await Promise.all(
|
|
77
|
+
paths.map(async (file) => {
|
|
78
|
+
try {
|
|
79
|
+
await lstat(resolve(vcs.rootPath, file));
|
|
80
|
+
|
|
81
|
+
return file;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (isMissingFileError(error)) return undefined;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return present.filter((file): file is string => file !== undefined);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Count Git's untracked, non-ignored files, which working tree reviews leave
|
|
94
|
+
* out. Undefined for jj, which tracks new files itself, or when the listing
|
|
95
|
+
* is too large to read.
|
|
96
|
+
*/
|
|
97
|
+
export async function untrackedFileCount(
|
|
98
|
+
vcs: LocalVcs,
|
|
99
|
+
): Promise<number | undefined> {
|
|
100
|
+
if (vcs.kind !== "git") return undefined;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const { stdout } = await exec(
|
|
104
|
+
"git",
|
|
105
|
+
[
|
|
106
|
+
"-C",
|
|
107
|
+
vcs.rootPath,
|
|
108
|
+
"ls-files",
|
|
109
|
+
"-z",
|
|
110
|
+
"--others",
|
|
111
|
+
"--exclude-standard",
|
|
112
|
+
"--exclude=.jj/",
|
|
113
|
+
],
|
|
114
|
+
{ maxBuffer: 32 * 1024 * 1024 },
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return stdout.split("\0").filter(Boolean).length;
|
|
118
|
+
} catch {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A refresh token, not stored source: all reads still use the checkout. */
|
|
124
|
+
export async function inspectWorktree(repositoryId: string, vcs: LocalVcs) {
|
|
125
|
+
const commit =
|
|
126
|
+
(await vcs.resolveRevision(vcs.kind === "jj" ? "@" : "HEAD"))?.commit ??
|
|
127
|
+
EMPTY_SOURCE;
|
|
128
|
+
|
|
129
|
+
const hash = createHash("sha256").update(`${repositoryId}\0${commit}`);
|
|
130
|
+
|
|
131
|
+
for (const file of await workingFiles(vcs)) {
|
|
132
|
+
try {
|
|
133
|
+
const info = await lstat(resolve(vcs.rootPath, file), { bigint: true });
|
|
134
|
+
hash.update(
|
|
135
|
+
`\0${file}\0${info.ino}:${info.size}:${info.mode}:${info.mtimeNs}:${info.ctimeNs}`,
|
|
136
|
+
);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (!isMissingFileError(error)) throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { revision: hash.digest("hex"), commit };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function readWorkingFile(
|
|
146
|
+
root: string,
|
|
147
|
+
file: string,
|
|
148
|
+
): Promise<string | null> {
|
|
149
|
+
try {
|
|
150
|
+
const candidate = resolve(root, file);
|
|
151
|
+
|
|
152
|
+
// Match Git's representation of tracked links, never read their target bytes.
|
|
153
|
+
const parent = relative(
|
|
154
|
+
await realpath(root),
|
|
155
|
+
await realpath(dirname(candidate)),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (isAbsolute(parent) || parent === ".." || parent.startsWith(`..${sep}`))
|
|
159
|
+
throw new ReviewInputError(
|
|
160
|
+
"Source symlink leaves the selected worktree.",
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if ((await lstat(candidate)).isSymbolicLink()) return readlink(candidate);
|
|
164
|
+
|
|
165
|
+
return await readFile(await localSourcePath(root, file), "utf8");
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (isMissingFileError(error)) return null;
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { formatAnchor, selectionKey } from "@review/lens-selection";
|
|
7
|
+
import { expect, it } from "vitest";
|
|
8
|
+
|
|
9
|
+
import { createReviewApi } from "./http";
|
|
10
|
+
import { openLocalReviewStore } from "./local-data";
|
|
11
|
+
|
|
12
|
+
it.each([
|
|
13
|
+
{ linked: false, explicitBase: false },
|
|
14
|
+
{ linked: true, explicitBase: false },
|
|
15
|
+
{ linked: true, explicitBase: true },
|
|
16
|
+
])(
|
|
17
|
+
"resolves saved edits and refreshes cached peeks: %j",
|
|
18
|
+
async ({ linked, explicitBase }) => {
|
|
19
|
+
const root = mkdtempSync(path.join(tmpdir(), "worktree-structural-"));
|
|
20
|
+
let repository = path.join(root, "repository");
|
|
21
|
+
mkdirSync(repository);
|
|
22
|
+
|
|
23
|
+
const git = (...args: string[]) =>
|
|
24
|
+
execFileSync("git", ["-C", repository, ...args], {
|
|
25
|
+
encoding: "utf8",
|
|
26
|
+
}).trim();
|
|
27
|
+
|
|
28
|
+
git("init", "-q", "-b", "main");
|
|
29
|
+
writeFileSync(
|
|
30
|
+
path.join(repository, "value.ts"),
|
|
31
|
+
"export const value = 1;\n",
|
|
32
|
+
);
|
|
33
|
+
git("add", "value.ts");
|
|
34
|
+
git(
|
|
35
|
+
"-c",
|
|
36
|
+
"user.name=Test",
|
|
37
|
+
"-c",
|
|
38
|
+
"user.email=test@example.invalid",
|
|
39
|
+
"commit",
|
|
40
|
+
"-qm",
|
|
41
|
+
"Base",
|
|
42
|
+
);
|
|
43
|
+
const base = git("rev-parse", "HEAD");
|
|
44
|
+
|
|
45
|
+
if (explicitBase) {
|
|
46
|
+
writeFileSync(
|
|
47
|
+
path.join(repository, "value.ts"),
|
|
48
|
+
"export const value = 10;\n",
|
|
49
|
+
);
|
|
50
|
+
git(
|
|
51
|
+
"-c",
|
|
52
|
+
"user.name=Test",
|
|
53
|
+
"-c",
|
|
54
|
+
"user.email=test@example.invalid",
|
|
55
|
+
"commit",
|
|
56
|
+
"-am",
|
|
57
|
+
"Head",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (linked) {
|
|
62
|
+
const checkout = path.join(root, "linked checkout");
|
|
63
|
+
git("worktree", "add", "-qb", "live", checkout);
|
|
64
|
+
repository = checkout;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const local = openLocalReviewStore(path.join(root, "reviews.db"));
|
|
68
|
+
|
|
69
|
+
const selection = {
|
|
70
|
+
file: "value.ts",
|
|
71
|
+
start: { side: "head" as const, line: 1 },
|
|
72
|
+
end: { side: "head" as const, line: 1 },
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const source = formatAnchor(selection);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const { id } = await local.data.register(repository);
|
|
79
|
+
writeFileSync(
|
|
80
|
+
path.join(repository, "value.ts"),
|
|
81
|
+
"export const value = 2;\n",
|
|
82
|
+
);
|
|
83
|
+
git("add", "value.ts");
|
|
84
|
+
|
|
85
|
+
const { reviewId } = await local.store.execute({
|
|
86
|
+
operation: {
|
|
87
|
+
type: "create",
|
|
88
|
+
title: "Live",
|
|
89
|
+
target: {
|
|
90
|
+
kind: "worktree",
|
|
91
|
+
repositoryId: id,
|
|
92
|
+
base: explicitBase ? base : undefined,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await local.store.execute({
|
|
98
|
+
operation: {
|
|
99
|
+
type: "edit",
|
|
100
|
+
reviewId,
|
|
101
|
+
edit: { type: "insert", content: { type: "code_peek", source } },
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
const app = createReviewApi(local.store, local.data);
|
|
105
|
+
const index = git("diff", "--cached");
|
|
106
|
+
const worktrees = git("worktree", "list", "--porcelain");
|
|
107
|
+
let revision: string | undefined;
|
|
108
|
+
|
|
109
|
+
for (const value of [2, 333]) {
|
|
110
|
+
writeFileSync(
|
|
111
|
+
path.join(repository, "value.ts"),
|
|
112
|
+
`export const value = ${value};\n`,
|
|
113
|
+
);
|
|
114
|
+
await local.store.refreshWorktrees();
|
|
115
|
+
const snapshot = local.store.read(reviewId);
|
|
116
|
+
expect(snapshot.pins!.base).toBe(base);
|
|
117
|
+
expect(snapshot.pins!.head).toBe(
|
|
118
|
+
explicitBase ? git("rev-parse", "HEAD") : base,
|
|
119
|
+
);
|
|
120
|
+
expect(snapshot.pins!.worktreeRevision).not.toBe(revision);
|
|
121
|
+
revision = snapshot.pins!.worktreeRevision;
|
|
122
|
+
|
|
123
|
+
const response = await app.request(
|
|
124
|
+
`/${reviewId}/structural-diff?file=value.ts`,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(response.status).toBe(200);
|
|
128
|
+
|
|
129
|
+
const events = (await response.text())
|
|
130
|
+
.trim()
|
|
131
|
+
.split("\n")
|
|
132
|
+
.map((line) => JSON.parse(line));
|
|
133
|
+
|
|
134
|
+
expect(events.find((event) => event.type === "file")).toMatchObject({
|
|
135
|
+
diff: {
|
|
136
|
+
lhs: { text: "export const value = 1;\n" },
|
|
137
|
+
rhs: { text: `export const value = ${value};\n` },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const progress = await (
|
|
142
|
+
await app.request(`/${reviewId}/progress?mode=structural`)
|
|
143
|
+
).json();
|
|
144
|
+
|
|
145
|
+
expect(progress.unavailableSelections).toEqual({});
|
|
146
|
+
expect(
|
|
147
|
+
progress.resolvedSelections[selectionKey(selection)],
|
|
148
|
+
).toContainEqual(
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
file: "value.ts",
|
|
151
|
+
side: "head",
|
|
152
|
+
fromLine: 1,
|
|
153
|
+
toLine: 1,
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
expect(progress.files).toHaveLength(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
expect(git("diff", "--cached")).toBe(index);
|
|
160
|
+
expect(git("worktree", "list", "--porcelain")).toBe(worktrees);
|
|
161
|
+
} finally {
|
|
162
|
+
await local.data.close();
|
|
163
|
+
await local.store.close();
|
|
164
|
+
rmSync(root, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
30_000,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
it("streams added, deleted, renamed and binary working files and respects path filtering", async () => {
|
|
171
|
+
const root = mkdtempSync(path.join(tmpdir(), "worktree-files-"));
|
|
172
|
+
const repository = path.join(root, "repository");
|
|
173
|
+
mkdirSync(repository);
|
|
174
|
+
|
|
175
|
+
const git = (...args: string[]) =>
|
|
176
|
+
execFileSync("git", ["-C", repository, ...args], {
|
|
177
|
+
encoding: "utf8",
|
|
178
|
+
}).trim();
|
|
179
|
+
|
|
180
|
+
git("init", "-q", "-b", "main");
|
|
181
|
+
writeFileSync(
|
|
182
|
+
path.join(repository, "gone.ts"),
|
|
183
|
+
"export const gone = true;\n",
|
|
184
|
+
);
|
|
185
|
+
writeFileSync(
|
|
186
|
+
path.join(repository, "old name.ts"),
|
|
187
|
+
"export const renamed = true;\n",
|
|
188
|
+
);
|
|
189
|
+
writeFileSync(path.join(repository, "binary.bin"), Buffer.from([0, 1, 2, 3]));
|
|
190
|
+
git("add", ".");
|
|
191
|
+
git(
|
|
192
|
+
"-c",
|
|
193
|
+
"user.name=Test",
|
|
194
|
+
"-c",
|
|
195
|
+
"user.email=test@example.invalid",
|
|
196
|
+
"commit",
|
|
197
|
+
"-qm",
|
|
198
|
+
"Base",
|
|
199
|
+
);
|
|
200
|
+
git("mv", "old name.ts", "new name.ts");
|
|
201
|
+
git("rm", "gone.ts");
|
|
202
|
+
writeFileSync(
|
|
203
|
+
path.join(repository, "added file.ts"),
|
|
204
|
+
"export const added = 1;\n",
|
|
205
|
+
);
|
|
206
|
+
git("add", "added file.ts");
|
|
207
|
+
writeFileSync(path.join(repository, "binary.bin"), Buffer.from([0, 4, 5, 6]));
|
|
208
|
+
const index = git("diff", "--cached");
|
|
209
|
+
const local = openLocalReviewStore(path.join(root, "reviews.db"));
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const { id } = await local.data.register(repository);
|
|
213
|
+
|
|
214
|
+
const { reviewId } = await local.store.execute({
|
|
215
|
+
operation: {
|
|
216
|
+
type: "create",
|
|
217
|
+
title: "Changed files",
|
|
218
|
+
target: { kind: "worktree", repositoryId: id },
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const app = createReviewApi(local.store, local.data);
|
|
223
|
+
|
|
224
|
+
const read = async (query = "") => {
|
|
225
|
+
const response = await app.request(
|
|
226
|
+
`/${reviewId}/structural-diff${query}`,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
expect(response.status).toBe(200);
|
|
230
|
+
|
|
231
|
+
return (await response.text())
|
|
232
|
+
.trim()
|
|
233
|
+
.split("\n")
|
|
234
|
+
.map((line) => JSON.parse(line));
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const events = await read();
|
|
238
|
+
expect(events.at(-1)).toMatchObject({ type: "complete", failed: 0 });
|
|
239
|
+
expect(events[0].files).toEqual(
|
|
240
|
+
expect.arrayContaining([
|
|
241
|
+
expect.objectContaining({
|
|
242
|
+
status: "added",
|
|
243
|
+
file: expect.objectContaining({
|
|
244
|
+
rhs: expect.objectContaining({ path: "added file.ts" }),
|
|
245
|
+
}),
|
|
246
|
+
}),
|
|
247
|
+
expect.objectContaining({
|
|
248
|
+
status: "deleted",
|
|
249
|
+
file: expect.objectContaining({
|
|
250
|
+
lhs: expect.objectContaining({ path: "gone.ts" }),
|
|
251
|
+
}),
|
|
252
|
+
}),
|
|
253
|
+
expect.objectContaining({
|
|
254
|
+
status: "renamed",
|
|
255
|
+
file: expect.objectContaining({
|
|
256
|
+
lhs: expect.objectContaining({ path: "old name.ts" }),
|
|
257
|
+
rhs: expect.objectContaining({ path: "new name.ts" }),
|
|
258
|
+
}),
|
|
259
|
+
}),
|
|
260
|
+
]),
|
|
261
|
+
);
|
|
262
|
+
expect(
|
|
263
|
+
events.find(
|
|
264
|
+
(event) =>
|
|
265
|
+
event.type === "file" && event.file.rhs?.path === "binary.bin",
|
|
266
|
+
).diff.type,
|
|
267
|
+
).toBe("binary");
|
|
268
|
+
const filtered = await read("?file=added%20file.ts");
|
|
269
|
+
expect(filtered.filter((event) => event.type === "file")).toHaveLength(1);
|
|
270
|
+
expect(filtered.find((event) => event.type === "file")).toMatchObject({
|
|
271
|
+
diff: { rhs: { text: "export const added = 1;\n" } },
|
|
272
|
+
});
|
|
273
|
+
expect(git("diff", "--cached")).toBe(index);
|
|
274
|
+
} finally {
|
|
275
|
+
await local.data.close();
|
|
276
|
+
await local.store.close();
|
|
277
|
+
rmSync(root, { recursive: true, force: true });
|
|
278
|
+
}
|
|
279
|
+
}, 30_000);
|
|
280
|
+
|
|
281
|
+
it("compares a staged file in an unborn repository with empty source", async () => {
|
|
282
|
+
const root = mkdtempSync(path.join(tmpdir(), "worktree-unborn-"));
|
|
283
|
+
const repository = path.join(root, "repository");
|
|
284
|
+
mkdirSync(repository);
|
|
285
|
+
execFileSync("git", ["-C", repository, "init", "-q", "-b", "main"]);
|
|
286
|
+
writeFileSync(path.join(repository, "first.ts"), "export const first = 1;\n");
|
|
287
|
+
execFileSync("git", ["-C", repository, "add", "first.ts"]);
|
|
288
|
+
const local = openLocalReviewStore(path.join(root, "reviews.db"));
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const { id } = await local.data.register(repository);
|
|
292
|
+
|
|
293
|
+
const { reviewId } = await local.store.execute({
|
|
294
|
+
operation: {
|
|
295
|
+
type: "create",
|
|
296
|
+
title: "Unborn",
|
|
297
|
+
target: { kind: "worktree", repositoryId: id },
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const response = await createReviewApi(local.store, local.data).request(
|
|
302
|
+
`/${reviewId}/structural-diff`,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const events = (await response.text())
|
|
306
|
+
.trim()
|
|
307
|
+
.split("\n")
|
|
308
|
+
.map((line) => JSON.parse(line));
|
|
309
|
+
|
|
310
|
+
expect(events.at(-1)).toMatchObject({ type: "complete", failed: 0 });
|
|
311
|
+
expect(events.find((event) => event.type === "file")).toMatchObject({
|
|
312
|
+
file: { rhs: { path: "first.ts" } },
|
|
313
|
+
diff: { rhs: { text: "export const first = 1;\n" } },
|
|
314
|
+
});
|
|
315
|
+
} finally {
|
|
316
|
+
await local.data.close();
|
|
317
|
+
await local.store.close();
|
|
318
|
+
rmSync(root, { recursive: true, force: true });
|
|
319
|
+
}
|
|
320
|
+
}, 30_000);
|