pi-webdesk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +111 -0
  2. package/dist/apps/daemon/src/appearance-preferences.js +218 -0
  3. package/dist/apps/daemon/src/auth.js +88 -0
  4. package/dist/apps/daemon/src/bin.js +123 -0
  5. package/dist/apps/daemon/src/cli.js +48 -0
  6. package/dist/apps/daemon/src/event-hub.js +155 -0
  7. package/dist/apps/daemon/src/index.js +102 -0
  8. package/dist/apps/daemon/src/launcher-control.js +114 -0
  9. package/dist/apps/daemon/src/launcher.js +73 -0
  10. package/dist/apps/daemon/src/pi-auth.js +290 -0
  11. package/dist/apps/daemon/src/pi-resources.js +182 -0
  12. package/dist/apps/daemon/src/pi-runtime-factory.js +19 -0
  13. package/dist/apps/daemon/src/pi-sessions.js +265 -0
  14. package/dist/apps/daemon/src/runtime-process.js +241 -0
  15. package/dist/apps/daemon/src/secret.js +71 -0
  16. package/dist/apps/daemon/src/server.js +1662 -0
  17. package/dist/apps/daemon/src/session-projection.js +117 -0
  18. package/dist/apps/daemon/src/state-lock.js +31 -0
  19. package/dist/apps/daemon/src/static-web.js +53 -0
  20. package/dist/apps/daemon/src/task-archive.js +152 -0
  21. package/dist/apps/daemon/src/task-commit.js +503 -0
  22. package/dist/apps/daemon/src/task-merge.js +912 -0
  23. package/dist/apps/daemon/src/task-review.js +204 -0
  24. package/dist/apps/daemon/src/task-runtime.js +1124 -0
  25. package/dist/apps/daemon/src/task-validation.js +352 -0
  26. package/dist/apps/daemon/src/workspace-store.js +140 -0
  27. package/dist/apps/daemon/src/workspace.js +795 -0
  28. package/dist/extensions/webdesk.js +34 -0
  29. package/dist/packages/git/src/commit.js +675 -0
  30. package/dist/packages/git/src/errors.js +55 -0
  31. package/dist/packages/git/src/fingerprint.js +286 -0
  32. package/dist/packages/git/src/index.js +123 -0
  33. package/dist/packages/git/src/merge.js +1008 -0
  34. package/dist/packages/git/src/paths.js +58 -0
  35. package/dist/packages/git/src/repository.js +77 -0
  36. package/dist/packages/git/src/review.js +396 -0
  37. package/dist/packages/git/src/runner.js +110 -0
  38. package/dist/packages/git/src/validation.js +263 -0
  39. package/dist/packages/git/src/worktree.js +233 -0
  40. package/dist/packages/pi-bridge/extensions/pita-policy.js +117 -0
  41. package/dist/packages/pi-bridge/src/auth.js +80 -0
  42. package/dist/packages/pi-bridge/src/errors.js +19 -0
  43. package/dist/packages/pi-bridge/src/handshake.js +43 -0
  44. package/dist/packages/pi-bridge/src/index.js +76 -0
  45. package/dist/packages/pi-bridge/src/jsonl.js +105 -0
  46. package/dist/packages/pi-bridge/src/policy-approval.js +62 -0
  47. package/dist/packages/pi-bridge/src/resolve.js +59 -0
  48. package/dist/packages/pi-bridge/src/resources-child.mjs +23 -0
  49. package/dist/packages/pi-bridge/src/resources.js +481 -0
  50. package/dist/packages/pi-bridge/src/rpc/client.js +480 -0
  51. package/dist/packages/pi-bridge/src/rpc/runtime.js +496 -0
  52. package/dist/packages/pi-bridge/src/rpc/supervisor.mjs +129 -0
  53. package/dist/packages/pi-bridge/src/rpc/tool-events.js +78 -0
  54. package/dist/packages/pi-bridge/src/rpc/wire.js +263 -0
  55. package/dist/packages/pi-bridge/src/runtime.js +0 -0
  56. package/dist/packages/pi-bridge/src/sessions-child.mjs +38 -0
  57. package/dist/packages/pi-bridge/src/sessions.js +314 -0
  58. package/dist/packages/pi-bridge/src/tool-activity.js +56 -0
  59. package/dist/packages/protocol/src/index.js +1863 -0
  60. package/dist/web/assets/index-BOw_fhvO.css +2 -0
  61. package/dist/web/assets/index-oXs7yAAo.js +119 -0
  62. package/dist/web/index.html +14 -0
  63. package/package.json +69 -0
  64. package/scripts/prepare.mjs +7 -0
@@ -0,0 +1,55 @@
1
+ // packages/git/src/errors.ts
2
+ var GitServiceError = class extends Error {
3
+ code;
4
+ details;
5
+ constructor(code, message, options) {
6
+ super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
7
+ this.name = "GitServiceError";
8
+ this.code = code;
9
+ this.details = options?.details ?? {};
10
+ }
11
+ };
12
+ function isGitServiceError(value, code) {
13
+ if (!(value instanceof GitServiceError)) return false;
14
+ return code === void 0 || value.code === code;
15
+ }
16
+ var GitWorktreeIncompleteError = class extends GitServiceError {
17
+ facts;
18
+ constructor(message, facts, options) {
19
+ super("GIT_WORKTREE_CREATE_INCOMPLETE", message, {
20
+ cause: options?.cause,
21
+ details: { ...facts }
22
+ });
23
+ this.name = "GitWorktreeIncompleteError";
24
+ this.facts = facts;
25
+ }
26
+ };
27
+ var GitMergeConflictError = class extends GitServiceError {
28
+ /** Raw conflicted paths, bounded in count; presentation must escape them. */
29
+ conflictPaths;
30
+ /** Conflicted paths beyond the retained bound (reported, never silent). */
31
+ omittedConflictPaths;
32
+ constructor(message, conflictPaths, omittedConflictPaths, options) {
33
+ super("GIT_MERGE_CONFLICTED", message, {
34
+ ...options?.cause === void 0 ? {} : { cause: options.cause },
35
+ details: {
36
+ conflictedPaths: conflictPaths.length + omittedConflictPaths
37
+ }
38
+ });
39
+ this.name = "GitMergeConflictError";
40
+ this.conflictPaths = conflictPaths;
41
+ this.omittedConflictPaths = omittedConflictPaths;
42
+ }
43
+ };
44
+ function boundedDetailText(text, maxLength = 400) {
45
+ const trimmed = text.trim();
46
+ if (trimmed.length <= maxLength) return trimmed;
47
+ return `${trimmed.slice(0, maxLength)}\u2026 [truncated]`;
48
+ }
49
+ export {
50
+ GitMergeConflictError,
51
+ GitServiceError,
52
+ GitWorktreeIncompleteError,
53
+ boundedDetailText,
54
+ isGitServiceError
55
+ };
@@ -0,0 +1,286 @@
1
+ // packages/git/src/fingerprint.ts
2
+ import { createHash } from "node:crypto";
3
+ import { constants } from "node:fs";
4
+ import { lstat, open, readlink } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { GitServiceError, isGitServiceError } from "./errors.js";
7
+ import { inspectRepository } from "./repository.js";
8
+ import {
9
+ READ_ONLY_GIT_FLAGS,
10
+ parseStatusOutput,
11
+ sameRepositoryIdentity
12
+ } from "./review.js";
13
+ import {
14
+ defaultGitRunner,
15
+ runGitExpectingSuccess
16
+ } from "./runner.js";
17
+ var DEFAULT_STATE_MAX_ENTRIES = 2e3;
18
+ var DEFAULT_STATE_MAX_DURATION_MS = 1e4;
19
+ var DEFAULT_STATE_MAX_CONTENT_BYTES = 64 * 1024 * 1024;
20
+ function positiveBound(value, label) {
21
+ if (!Number.isSafeInteger(value) || value < 1) {
22
+ throw new GitServiceError(
23
+ "GIT_STATE_TOO_LARGE",
24
+ `${label} must be a positive safe integer`
25
+ );
26
+ }
27
+ return value;
28
+ }
29
+ function withCaptureDeadline(runner, deadlineMs, now) {
30
+ return async (args, options = {}) => {
31
+ const remainingMs = Math.floor(deadlineMs - now());
32
+ if (remainingMs < 1) {
33
+ throw new GitServiceError(
34
+ "GIT_STATE_DEADLINE_EXCEEDED",
35
+ "The worktree-state capture exceeded its overall time budget"
36
+ );
37
+ }
38
+ const callerLimitMs = options.timeoutMs ?? Number.POSITIVE_INFINITY;
39
+ try {
40
+ return await runner(args, {
41
+ ...options,
42
+ timeoutMs: Math.min(remainingMs, callerLimitMs)
43
+ });
44
+ } catch (error) {
45
+ if (remainingMs <= callerLimitMs && isGitServiceError(error, "GIT_COMMAND_TERMINATED")) {
46
+ throw new GitServiceError(
47
+ "GIT_STATE_DEADLINE_EXCEEDED",
48
+ "The worktree-state capture exceeded its overall time budget",
49
+ { cause: error }
50
+ );
51
+ }
52
+ throw error;
53
+ }
54
+ };
55
+ }
56
+ function needsContentHash(entry) {
57
+ if (entry.record === "untracked" || entry.record === "unmerged") return true;
58
+ return entry.y !== "." && entry.y !== "D";
59
+ }
60
+ async function hashWorktreeContent(root, paths, maxContentBytes, deadlineMs, now) {
61
+ const hashes = /* @__PURE__ */ new Map();
62
+ let consumedBytes = 0;
63
+ function ensureBudget() {
64
+ if (now() >= deadlineMs) {
65
+ throw new GitServiceError(
66
+ "GIT_STATE_DEADLINE_EXCEEDED",
67
+ "The worktree-state capture exceeded its overall time budget"
68
+ );
69
+ }
70
+ }
71
+ function consume(bytes) {
72
+ consumedBytes += bytes;
73
+ if (consumedBytes > maxContentBytes) {
74
+ throw new GitServiceError(
75
+ "GIT_STATE_TOO_LARGE",
76
+ `Changed worktree content exceeds the ${maxContentBytes}-byte fingerprint bound`,
77
+ { details: { maxContentBytes } }
78
+ );
79
+ }
80
+ }
81
+ function sameFileIdentity(before, after) {
82
+ return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeNs === after.mtimeNs && before.ctimeNs === after.ctimeNs && before.mode === after.mode;
83
+ }
84
+ for (const entryPath of paths) {
85
+ ensureBudget();
86
+ const absolutePath = path.join(root, ...entryPath.split("/"));
87
+ let before;
88
+ try {
89
+ before = await lstat(absolutePath, { bigint: true });
90
+ } catch (error) {
91
+ throw new GitServiceError(
92
+ "GIT_STATE_CHANGED_DURING_CAPTURE",
93
+ "A changed worktree path disappeared while its state was being captured",
94
+ { cause: error }
95
+ );
96
+ }
97
+ if (before.isSymbolicLink()) {
98
+ let target;
99
+ let after;
100
+ try {
101
+ target = await readlink(absolutePath, { encoding: "buffer" });
102
+ after = await lstat(absolutePath, { bigint: true });
103
+ } catch (error) {
104
+ throw new GitServiceError(
105
+ "GIT_STATE_CHANGED_DURING_CAPTURE",
106
+ "A symlink changed while its state was being captured",
107
+ { cause: error }
108
+ );
109
+ }
110
+ if (!sameFileIdentity(before, after)) {
111
+ throw new GitServiceError(
112
+ "GIT_STATE_CHANGED_DURING_CAPTURE",
113
+ "A symlink changed while its state was being captured"
114
+ );
115
+ }
116
+ consume(target.byteLength);
117
+ hashes.set(
118
+ entryPath,
119
+ `symlink-sha256:${createHash("sha256").update(target).digest("hex")}`
120
+ );
121
+ continue;
122
+ }
123
+ if (!before.isFile()) {
124
+ throw new GitServiceError(
125
+ "GIT_STATE_UNFINGERPRINTABLE",
126
+ "A changed worktree path is a directory or special file that Webdesk cannot fingerprint safely"
127
+ );
128
+ }
129
+ if (before.size > BigInt(maxContentBytes - consumedBytes)) {
130
+ throw new GitServiceError(
131
+ "GIT_STATE_TOO_LARGE",
132
+ `Changed worktree content exceeds the ${maxContentBytes}-byte fingerprint bound`,
133
+ { details: { maxContentBytes } }
134
+ );
135
+ }
136
+ consume(Number(before.size));
137
+ let handle = null;
138
+ try {
139
+ handle = await open(absolutePath, constants.O_RDONLY | constants.O_NOFOLLOW);
140
+ const opened = await handle.stat({ bigint: true });
141
+ if (!opened.isFile() || !sameFileIdentity(before, opened)) {
142
+ throw new GitServiceError(
143
+ "GIT_STATE_CHANGED_DURING_CAPTURE",
144
+ "A worktree file changed before its content could be captured"
145
+ );
146
+ }
147
+ const digest = createHash("sha256");
148
+ const buffer = Buffer.allocUnsafe(64 * 1024);
149
+ let bytesReadTotal = 0;
150
+ while (true) {
151
+ ensureBudget();
152
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
153
+ if (bytesRead === 0) break;
154
+ bytesReadTotal += bytesRead;
155
+ digest.update(buffer.subarray(0, bytesRead));
156
+ }
157
+ const after = await handle.stat({ bigint: true });
158
+ if (BigInt(bytesReadTotal) !== before.size || !sameFileIdentity(before, after)) {
159
+ throw new GitServiceError(
160
+ "GIT_STATE_CHANGED_DURING_CAPTURE",
161
+ "A worktree file changed while its content was being captured"
162
+ );
163
+ }
164
+ hashes.set(entryPath, `file-sha256:${digest.digest("hex")}`);
165
+ } catch (error) {
166
+ if (isGitServiceError(error)) throw error;
167
+ throw new GitServiceError(
168
+ "GIT_STATE_CHANGED_DURING_CAPTURE",
169
+ "A worktree file changed or could not be opened safely while its state was being captured",
170
+ { cause: error }
171
+ );
172
+ } finally {
173
+ await handle?.close().catch(() => void 0);
174
+ }
175
+ }
176
+ return hashes;
177
+ }
178
+ async function captureWorktreeState(options) {
179
+ const maxEntries = positiveBound(
180
+ options.maxEntries ?? DEFAULT_STATE_MAX_ENTRIES,
181
+ "State entry limit"
182
+ );
183
+ const maxDurationMs = positiveBound(
184
+ options.maxDurationMs ?? DEFAULT_STATE_MAX_DURATION_MS,
185
+ "State duration limit"
186
+ );
187
+ const maxContentBytes = positiveBound(
188
+ options.maxContentBytes ?? DEFAULT_STATE_MAX_CONTENT_BYTES,
189
+ "State content-byte limit"
190
+ );
191
+ const now = options.now ?? Date.now;
192
+ const deadlineMs = now() + maxDurationMs;
193
+ const runner = withCaptureDeadline(
194
+ options.runner ?? defaultGitRunner,
195
+ deadlineMs,
196
+ now
197
+ );
198
+ const worktree = await inspectRepository(options.worktreePath, { runner });
199
+ const status = await runGitExpectingSuccess(
200
+ runner,
201
+ [
202
+ ...READ_ONLY_GIT_FLAGS,
203
+ "status",
204
+ "--porcelain=v2",
205
+ "-z",
206
+ "--untracked-files=all"
207
+ ],
208
+ { cwd: worktree.root }
209
+ );
210
+ const entries = parseStatusOutput(status.stdout);
211
+ if (entries.length > maxEntries) {
212
+ throw new GitServiceError(
213
+ "GIT_STATE_TOO_LARGE",
214
+ `The worktree has ${entries.length} changed or untracked entries, more than the ${maxEntries}-entry fingerprint bound`,
215
+ { details: { entries: entries.length, maxEntries } }
216
+ );
217
+ }
218
+ entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
219
+ if (entries.some((entry) => entry.sub.startsWith("S"))) {
220
+ throw new GitServiceError(
221
+ "GIT_STATE_UNFINGERPRINTABLE",
222
+ "Dirty or moved submodules cannot be fingerprinted safely yet"
223
+ );
224
+ }
225
+ const contentPaths = entries.filter(needsContentHash).map((entry) => entry.path);
226
+ const contentHashes = await hashWorktreeContent(
227
+ worktree.root,
228
+ contentPaths,
229
+ maxContentBytes,
230
+ deadlineMs,
231
+ now
232
+ );
233
+ const digest = createHash("sha256");
234
+ digest.update("pita-worktree-state-v1");
235
+ digest.update("\0");
236
+ digest.update(worktree.root);
237
+ digest.update("\0");
238
+ digest.update(worktree.commonDir);
239
+ digest.update("\0");
240
+ digest.update(worktree.branch ?? "detached");
241
+ digest.update("\0");
242
+ digest.update(worktree.headCommit);
243
+ digest.update("\0\0");
244
+ digest.update(status.stdout);
245
+ digest.update("\0\0");
246
+ for (const entryPath of contentPaths) {
247
+ const contentHash = contentHashes.get(entryPath);
248
+ if (contentHash === void 0) {
249
+ throw new GitServiceError(
250
+ "GIT_STATE_CHANGED_DURING_CAPTURE",
251
+ "A changed worktree path was not captured consistently"
252
+ );
253
+ }
254
+ digest.update(entryPath);
255
+ digest.update("\0");
256
+ digest.update(contentHash);
257
+ digest.update("\0");
258
+ }
259
+ const stateFingerprint = digest.digest("hex");
260
+ const verified = await inspectRepository(worktree.root, { runner });
261
+ if (!sameRepositoryIdentity(worktree, verified)) {
262
+ throw new GitServiceError(
263
+ "GIT_REPOSITORY_CHANGED_DURING_CAPTURE",
264
+ "The worktree branch or HEAD changed while its state was being captured; retry to observe the new state",
265
+ {
266
+ details: {
267
+ beforeHead: worktree.headCommit,
268
+ afterHead: verified.headCommit,
269
+ beforeBranch: worktree.branch,
270
+ afterBranch: verified.branch
271
+ }
272
+ }
273
+ );
274
+ }
275
+ return {
276
+ worktree,
277
+ stateFingerprint,
278
+ statusEntryCount: entries.length
279
+ };
280
+ }
281
+ export {
282
+ DEFAULT_STATE_MAX_CONTENT_BYTES,
283
+ DEFAULT_STATE_MAX_DURATION_MS,
284
+ DEFAULT_STATE_MAX_ENTRIES,
285
+ captureWorktreeState
286
+ };
@@ -0,0 +1,123 @@
1
+ // packages/git/src/index.ts
2
+ import {
3
+ GitMergeConflictError,
4
+ GitServiceError,
5
+ GitWorktreeIncompleteError,
6
+ isGitServiceError
7
+ } from "./errors.js";
8
+ import {
9
+ createDefaultGitRunner
10
+ } from "./runner.js";
11
+ import {
12
+ inspectRepository
13
+ } from "./repository.js";
14
+ import {
15
+ DEFAULT_REVIEW_MAX_DIFF_CHARS_PER_FILE,
16
+ DEFAULT_REVIEW_MAX_DURATION_MS,
17
+ DEFAULT_REVIEW_MAX_FILES,
18
+ DEFAULT_REVIEW_MAX_TOTAL_DIFF_CHARS,
19
+ reviewWorktree
20
+ } from "./review.js";
21
+ import {
22
+ DEFAULT_STATE_MAX_DURATION_MS,
23
+ DEFAULT_STATE_MAX_CONTENT_BYTES,
24
+ DEFAULT_STATE_MAX_ENTRIES,
25
+ captureWorktreeState
26
+ } from "./fingerprint.js";
27
+ import {
28
+ DEFAULT_VALIDATION_MAX_OUTPUT_CHARS,
29
+ DEFAULT_VALIDATION_TIMEOUT_MS,
30
+ MAX_VALIDATION_COMMAND_CHARS,
31
+ MAX_VALIDATION_TIMEOUT_MS,
32
+ ValidationRunnerError,
33
+ isValidationRunnerError,
34
+ startValidationRun
35
+ } from "./validation.js";
36
+ import {
37
+ createTaskWorktree,
38
+ preflightTaskWorktree
39
+ } from "./worktree.js";
40
+ import {
41
+ DEFAULT_COMMIT_MAX_DIFF_CHARS_PER_FILE,
42
+ DEFAULT_COMMIT_MAX_DURATION_MS,
43
+ DEFAULT_COMMIT_MAX_ENTRIES,
44
+ DEFAULT_COMMIT_MAX_FILES,
45
+ DEFAULT_COMMIT_MAX_TOTAL_DIFF_CHARS,
46
+ DEFAULT_COMMIT_SIGNING_TIMEOUT_MS,
47
+ MAX_COMMIT_MESSAGE_CHARS,
48
+ commitPreparedTree,
49
+ prepareCommitTree
50
+ } from "./commit.js";
51
+ import {
52
+ DEFAULT_MERGE_MAX_COMMIT_SUBJECTS,
53
+ DEFAULT_MERGE_MAX_DIFF_CHARS_PER_FILE,
54
+ DEFAULT_MERGE_MAX_DURATION_MS,
55
+ DEFAULT_MERGE_MAX_ENTRIES,
56
+ DEFAULT_MERGE_MAX_FILES,
57
+ DEFAULT_MERGE_MAX_GUARD_PATH_CHARS,
58
+ DEFAULT_MERGE_MAX_TOTAL_DIFF_CHARS,
59
+ DEFAULT_MERGE_GUARD_TIMEOUT_MS,
60
+ DEFAULT_MERGE_SIGNING_TIMEOUT_MS,
61
+ MAX_MERGE_CONFLICT_PATHS,
62
+ MAX_MERGE_MESSAGE_CHARS,
63
+ MIN_MERGE_GIT_VERSION,
64
+ assertMergeTargetPathsUnoccupied,
65
+ ensureMergeCapableGit,
66
+ isMergeCommitAncestor,
67
+ prepareMergeTree,
68
+ publishMerge,
69
+ syncTargetCheckout
70
+ } from "./merge.js";
71
+ export {
72
+ DEFAULT_COMMIT_MAX_DIFF_CHARS_PER_FILE,
73
+ DEFAULT_COMMIT_MAX_DURATION_MS,
74
+ DEFAULT_COMMIT_MAX_ENTRIES,
75
+ DEFAULT_COMMIT_MAX_FILES,
76
+ DEFAULT_COMMIT_MAX_TOTAL_DIFF_CHARS,
77
+ DEFAULT_COMMIT_SIGNING_TIMEOUT_MS,
78
+ DEFAULT_MERGE_GUARD_TIMEOUT_MS,
79
+ DEFAULT_MERGE_MAX_COMMIT_SUBJECTS,
80
+ DEFAULT_MERGE_MAX_DIFF_CHARS_PER_FILE,
81
+ DEFAULT_MERGE_MAX_DURATION_MS,
82
+ DEFAULT_MERGE_MAX_ENTRIES,
83
+ DEFAULT_MERGE_MAX_FILES,
84
+ DEFAULT_MERGE_MAX_GUARD_PATH_CHARS,
85
+ DEFAULT_MERGE_MAX_TOTAL_DIFF_CHARS,
86
+ DEFAULT_MERGE_SIGNING_TIMEOUT_MS,
87
+ DEFAULT_REVIEW_MAX_DIFF_CHARS_PER_FILE,
88
+ DEFAULT_REVIEW_MAX_DURATION_MS,
89
+ DEFAULT_REVIEW_MAX_FILES,
90
+ DEFAULT_REVIEW_MAX_TOTAL_DIFF_CHARS,
91
+ DEFAULT_STATE_MAX_CONTENT_BYTES,
92
+ DEFAULT_STATE_MAX_DURATION_MS,
93
+ DEFAULT_STATE_MAX_ENTRIES,
94
+ DEFAULT_VALIDATION_MAX_OUTPUT_CHARS,
95
+ DEFAULT_VALIDATION_TIMEOUT_MS,
96
+ GitMergeConflictError,
97
+ GitServiceError,
98
+ GitWorktreeIncompleteError,
99
+ MAX_COMMIT_MESSAGE_CHARS,
100
+ MAX_MERGE_CONFLICT_PATHS,
101
+ MAX_MERGE_MESSAGE_CHARS,
102
+ MAX_VALIDATION_COMMAND_CHARS,
103
+ MAX_VALIDATION_TIMEOUT_MS,
104
+ MIN_MERGE_GIT_VERSION,
105
+ ValidationRunnerError,
106
+ assertMergeTargetPathsUnoccupied,
107
+ captureWorktreeState,
108
+ commitPreparedTree,
109
+ createDefaultGitRunner,
110
+ createTaskWorktree,
111
+ ensureMergeCapableGit,
112
+ inspectRepository,
113
+ isGitServiceError,
114
+ isMergeCommitAncestor,
115
+ isValidationRunnerError,
116
+ preflightTaskWorktree,
117
+ prepareCommitTree,
118
+ prepareMergeTree,
119
+ publishMerge,
120
+ reviewWorktree,
121
+ startValidationRun,
122
+ syncTargetCheckout
123
+ };