@posthog/agent 1.30.0 → 2.0.1
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 +1 -1
- package/README.md +221 -219
- package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +21 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +547 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -0
- package/dist/adapters/claude/permissions/permission-options.d.ts +13 -0
- package/dist/adapters/claude/permissions/permission-options.js +117 -0
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -0
- package/dist/adapters/claude/questions/utils.d.ts +132 -0
- package/dist/adapters/claude/questions/utils.js +63 -0
- package/dist/adapters/claude/questions/utils.js.map +1 -0
- package/dist/adapters/claude/tools.d.ts +18 -0
- package/dist/adapters/claude/tools.js +95 -0
- package/dist/adapters/claude/tools.js.map +1 -0
- package/dist/agent-DBQY1BfC.d.ts +123 -0
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +3656 -0
- package/dist/agent.js.map +1 -0
- package/dist/claude-cli/cli.js +3695 -2746
- package/dist/claude-cli/vendor/ripgrep/COPYING +3 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/ripgrep.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/ripgrep.node +0 -0
- package/dist/gateway-models.d.ts +24 -0
- package/dist/gateway-models.js +93 -0
- package/dist/gateway-models.js.map +1 -0
- package/dist/index.d.ts +172 -1203
- package/dist/index.js +3704 -6826
- package/dist/index.js.map +1 -1
- package/dist/logger-DDBiMOOD.d.ts +24 -0
- package/dist/posthog-api.d.ts +40 -0
- package/dist/posthog-api.js +175 -0
- package/dist/posthog-api.js.map +1 -0
- package/dist/server/agent-server.d.ts +41 -0
- package/dist/server/agent-server.js +4451 -0
- package/dist/server/agent-server.js.map +1 -0
- package/dist/server/bin.d.ts +1 -0
- package/dist/server/bin.js +4507 -0
- package/dist/server/bin.js.map +1 -0
- package/dist/types.d.ts +129 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -14
- package/src/acp-extensions.ts +93 -61
- package/src/adapters/acp-connection.ts +494 -0
- package/src/adapters/base-acp-agent.ts +150 -0
- package/src/adapters/claude/claude-agent.ts +596 -0
- package/src/adapters/claude/conversion/acp-to-sdk.ts +102 -0
- package/src/adapters/claude/conversion/sdk-to-acp.ts +571 -0
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +618 -0
- package/src/adapters/claude/hooks.ts +64 -0
- package/src/adapters/claude/mcp/tool-metadata.ts +102 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +433 -0
- package/src/adapters/claude/permissions/permission-options.ts +103 -0
- package/src/adapters/claude/plan/utils.ts +56 -0
- package/src/adapters/claude/questions/utils.ts +92 -0
- package/src/adapters/claude/session/commands.ts +38 -0
- package/src/adapters/claude/session/mcp-config.ts +37 -0
- package/src/adapters/claude/session/models.ts +12 -0
- package/src/adapters/claude/session/options.ts +236 -0
- package/src/adapters/claude/tool-meta.ts +143 -0
- package/src/adapters/claude/tools.ts +53 -611
- package/src/adapters/claude/types.ts +61 -0
- package/src/adapters/codex/spawn.ts +130 -0
- package/src/agent.ts +97 -734
- package/src/execution-mode.ts +43 -0
- package/src/gateway-models.ts +135 -0
- package/src/index.ts +79 -0
- package/src/otel-log-writer.test.ts +105 -0
- package/src/otel-log-writer.ts +94 -0
- package/src/posthog-api.ts +75 -235
- package/src/resume.ts +115 -0
- package/src/sagas/apply-snapshot-saga.test.ts +690 -0
- package/src/sagas/apply-snapshot-saga.ts +88 -0
- package/src/sagas/capture-tree-saga.test.ts +892 -0
- package/src/sagas/capture-tree-saga.ts +141 -0
- package/src/sagas/resume-saga.test.ts +558 -0
- package/src/sagas/resume-saga.ts +332 -0
- package/src/sagas/test-fixtures.ts +250 -0
- package/src/server/agent-server.test.ts +220 -0
- package/src/server/agent-server.ts +748 -0
- package/src/server/bin.ts +88 -0
- package/src/server/jwt.ts +65 -0
- package/src/server/schemas.ts +47 -0
- package/src/server/types.ts +13 -0
- package/src/server/utils/retry.test.ts +122 -0
- package/src/server/utils/retry.ts +61 -0
- package/src/server/utils/sse-parser.test.ts +93 -0
- package/src/server/utils/sse-parser.ts +46 -0
- package/src/session-log-writer.test.ts +140 -0
- package/src/session-log-writer.ts +137 -0
- package/src/test/assertions.ts +114 -0
- package/src/test/controllers/sse-controller.ts +107 -0
- package/src/test/fixtures/api.ts +111 -0
- package/src/test/fixtures/config.ts +33 -0
- package/src/test/fixtures/notifications.ts +92 -0
- package/src/test/mocks/claude-sdk.ts +251 -0
- package/src/test/mocks/msw-handlers.ts +48 -0
- package/src/test/setup.ts +114 -0
- package/src/test/wait.ts +41 -0
- package/src/tree-tracker.ts +173 -0
- package/src/types.ts +51 -154
- package/src/utils/acp-content.ts +58 -0
- package/src/utils/async-mutex.test.ts +104 -0
- package/src/utils/async-mutex.ts +31 -0
- package/src/utils/common.ts +15 -0
- package/src/utils/gateway.ts +9 -6
- package/src/utils/logger.ts +0 -30
- package/src/utils/streams.ts +220 -0
- package/CLAUDE.md +0 -331
- package/dist/templates/plan-template.md +0 -41
- package/src/adapters/claude/claude.ts +0 -1543
- package/src/adapters/claude/mcp-server.ts +0 -810
- package/src/adapters/claude/utils.ts +0 -267
- package/src/agents/execution.ts +0 -37
- package/src/agents/planning.ts +0 -60
- package/src/agents/research.ts +0 -160
- package/src/file-manager.ts +0 -306
- package/src/git-manager.ts +0 -577
- package/src/prompt-builder.ts +0 -499
- package/src/schemas.ts +0 -241
- package/src/session-store.ts +0 -259
- package/src/task-manager.ts +0 -163
- package/src/template-manager.ts +0 -236
- package/src/templates/plan-template.md +0 -41
- package/src/todo-manager.ts +0 -180
- package/src/tools/registry.ts +0 -129
- package/src/tools/types.ts +0 -127
- package/src/utils/tapped-stream.ts +0 -60
- package/src/workflow/config.ts +0 -53
- package/src/workflow/steps/build.ts +0 -135
- package/src/workflow/steps/finalize.ts +0 -241
- package/src/workflow/steps/plan.ts +0 -167
- package/src/workflow/steps/research.ts +0 -223
- package/src/workflow/types.ts +0 -62
- package/src/workflow/utils.ts +0 -53
- package/src/worktree-manager.ts +0 -928
package/src/worktree-manager.ts
DELETED
|
@@ -1,928 +0,0 @@
|
|
|
1
|
-
import { exec, execFile } from "node:child_process";
|
|
2
|
-
import * as crypto from "node:crypto";
|
|
3
|
-
import * as fs from "node:fs/promises";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
import type { WorktreeInfo } from "./types.js";
|
|
7
|
-
import { Logger } from "./utils/logger.js";
|
|
8
|
-
|
|
9
|
-
const execAsync = promisify(exec);
|
|
10
|
-
const execFileAsync = promisify(execFile);
|
|
11
|
-
|
|
12
|
-
export interface WorktreeConfig {
|
|
13
|
-
mainRepoPath: string;
|
|
14
|
-
worktreeBasePath?: string;
|
|
15
|
-
logger?: Logger;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const ADJECTIVES = [
|
|
19
|
-
"swift",
|
|
20
|
-
"bright",
|
|
21
|
-
"calm",
|
|
22
|
-
"bold",
|
|
23
|
-
"gentle",
|
|
24
|
-
"quick",
|
|
25
|
-
"soft",
|
|
26
|
-
"warm",
|
|
27
|
-
"cool",
|
|
28
|
-
"wise",
|
|
29
|
-
"keen",
|
|
30
|
-
"brave",
|
|
31
|
-
"clear",
|
|
32
|
-
"crisp",
|
|
33
|
-
"deep",
|
|
34
|
-
"fair",
|
|
35
|
-
"fine",
|
|
36
|
-
"free",
|
|
37
|
-
"glad",
|
|
38
|
-
"good",
|
|
39
|
-
"grand",
|
|
40
|
-
"great",
|
|
41
|
-
"happy",
|
|
42
|
-
"kind",
|
|
43
|
-
"light",
|
|
44
|
-
"lively",
|
|
45
|
-
"neat",
|
|
46
|
-
"nice",
|
|
47
|
-
"plain",
|
|
48
|
-
"proud",
|
|
49
|
-
"pure",
|
|
50
|
-
"rare",
|
|
51
|
-
"rich",
|
|
52
|
-
"safe",
|
|
53
|
-
"sharp",
|
|
54
|
-
"shy",
|
|
55
|
-
"simple",
|
|
56
|
-
"slim",
|
|
57
|
-
"smart",
|
|
58
|
-
"smooth",
|
|
59
|
-
"solid",
|
|
60
|
-
"sound",
|
|
61
|
-
"spare",
|
|
62
|
-
"stable",
|
|
63
|
-
"steady",
|
|
64
|
-
"still",
|
|
65
|
-
"strong",
|
|
66
|
-
"sure",
|
|
67
|
-
"sweet",
|
|
68
|
-
"tall",
|
|
69
|
-
"agile",
|
|
70
|
-
"ancient",
|
|
71
|
-
"autumn",
|
|
72
|
-
"azure",
|
|
73
|
-
"cosmic",
|
|
74
|
-
"daring",
|
|
75
|
-
"dawn",
|
|
76
|
-
"dusty",
|
|
77
|
-
"eager",
|
|
78
|
-
"early",
|
|
79
|
-
"endless",
|
|
80
|
-
"fading",
|
|
81
|
-
"fallen",
|
|
82
|
-
"famous",
|
|
83
|
-
"feral",
|
|
84
|
-
"fierce",
|
|
85
|
-
"fleet",
|
|
86
|
-
"foggy",
|
|
87
|
-
"forest",
|
|
88
|
-
"frozen",
|
|
89
|
-
"gleeful",
|
|
90
|
-
"golden",
|
|
91
|
-
"hazy",
|
|
92
|
-
"hidden",
|
|
93
|
-
"hollow",
|
|
94
|
-
"humble",
|
|
95
|
-
"hushed",
|
|
96
|
-
"icy",
|
|
97
|
-
"inner",
|
|
98
|
-
"late",
|
|
99
|
-
"lazy",
|
|
100
|
-
"little",
|
|
101
|
-
"lone",
|
|
102
|
-
"long",
|
|
103
|
-
"lost",
|
|
104
|
-
"lucky",
|
|
105
|
-
"lunar",
|
|
106
|
-
"magic",
|
|
107
|
-
"mellow",
|
|
108
|
-
"mighty",
|
|
109
|
-
"misty",
|
|
110
|
-
"modest",
|
|
111
|
-
"mossy",
|
|
112
|
-
"mystic",
|
|
113
|
-
"nimble",
|
|
114
|
-
"noble",
|
|
115
|
-
"ocean",
|
|
116
|
-
"outer",
|
|
117
|
-
"pale",
|
|
118
|
-
"paper",
|
|
119
|
-
"patient",
|
|
120
|
-
"peaceful",
|
|
121
|
-
"phantom",
|
|
122
|
-
"polite",
|
|
123
|
-
"primal",
|
|
124
|
-
"quiet",
|
|
125
|
-
"rapid",
|
|
126
|
-
"restless",
|
|
127
|
-
"rising",
|
|
128
|
-
"roaming",
|
|
129
|
-
"rocky",
|
|
130
|
-
"rustic",
|
|
131
|
-
"sacred",
|
|
132
|
-
"sandy",
|
|
133
|
-
"secret",
|
|
134
|
-
"serene",
|
|
135
|
-
"shadow",
|
|
136
|
-
"shining",
|
|
137
|
-
"silent",
|
|
138
|
-
"silky",
|
|
139
|
-
"silver",
|
|
140
|
-
"sleek",
|
|
141
|
-
"snowy",
|
|
142
|
-
"solar",
|
|
143
|
-
"solemn",
|
|
144
|
-
"spring",
|
|
145
|
-
"starry",
|
|
146
|
-
"stormy",
|
|
147
|
-
"summer",
|
|
148
|
-
"sunny",
|
|
149
|
-
"tender",
|
|
150
|
-
"thorny",
|
|
151
|
-
"tiny",
|
|
152
|
-
"tranquil",
|
|
153
|
-
"twilight",
|
|
154
|
-
"upward",
|
|
155
|
-
"velvet",
|
|
156
|
-
"vivid",
|
|
157
|
-
"wandering",
|
|
158
|
-
"wary",
|
|
159
|
-
"wild",
|
|
160
|
-
"windy",
|
|
161
|
-
"winter",
|
|
162
|
-
"wispy",
|
|
163
|
-
"young",
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
const COLORS = [
|
|
167
|
-
"blue",
|
|
168
|
-
"red",
|
|
169
|
-
"green",
|
|
170
|
-
"amber",
|
|
171
|
-
"coral",
|
|
172
|
-
"jade",
|
|
173
|
-
"pearl",
|
|
174
|
-
"ruby",
|
|
175
|
-
"sage",
|
|
176
|
-
"teal",
|
|
177
|
-
"gold",
|
|
178
|
-
"silver",
|
|
179
|
-
"bronze",
|
|
180
|
-
"copper",
|
|
181
|
-
"ivory",
|
|
182
|
-
"onyx",
|
|
183
|
-
"opal",
|
|
184
|
-
"rose",
|
|
185
|
-
"slate",
|
|
186
|
-
"violet",
|
|
187
|
-
"aqua",
|
|
188
|
-
"azure",
|
|
189
|
-
"beige",
|
|
190
|
-
"black",
|
|
191
|
-
"brass",
|
|
192
|
-
"brick",
|
|
193
|
-
"brown",
|
|
194
|
-
"cedar",
|
|
195
|
-
"charcoal",
|
|
196
|
-
"cherry",
|
|
197
|
-
"chestnut",
|
|
198
|
-
"chrome",
|
|
199
|
-
"cider",
|
|
200
|
-
"cinnamon",
|
|
201
|
-
"citrus",
|
|
202
|
-
"clay",
|
|
203
|
-
"cloud",
|
|
204
|
-
"cobalt",
|
|
205
|
-
"cocoa",
|
|
206
|
-
"cream",
|
|
207
|
-
"crimson",
|
|
208
|
-
"crystal",
|
|
209
|
-
"cyan",
|
|
210
|
-
"denim",
|
|
211
|
-
"dusk",
|
|
212
|
-
"ebony",
|
|
213
|
-
"ember",
|
|
214
|
-
"emerald",
|
|
215
|
-
"fern",
|
|
216
|
-
"flame",
|
|
217
|
-
"flint",
|
|
218
|
-
"forest",
|
|
219
|
-
"frost",
|
|
220
|
-
"garnet",
|
|
221
|
-
"ginger",
|
|
222
|
-
"glacier",
|
|
223
|
-
"granite",
|
|
224
|
-
"grape",
|
|
225
|
-
"gray",
|
|
226
|
-
"hazel",
|
|
227
|
-
"honey",
|
|
228
|
-
"indigo",
|
|
229
|
-
"iron",
|
|
230
|
-
"lapis",
|
|
231
|
-
"lava",
|
|
232
|
-
"lavender",
|
|
233
|
-
"lemon",
|
|
234
|
-
"lilac",
|
|
235
|
-
"lime",
|
|
236
|
-
"magenta",
|
|
237
|
-
"mahogany",
|
|
238
|
-
"maple",
|
|
239
|
-
"marble",
|
|
240
|
-
"maroon",
|
|
241
|
-
"mauve",
|
|
242
|
-
"midnight",
|
|
243
|
-
"mint",
|
|
244
|
-
"mocha",
|
|
245
|
-
"moss",
|
|
246
|
-
"mustard",
|
|
247
|
-
"navy",
|
|
248
|
-
"nickel",
|
|
249
|
-
"obsidian",
|
|
250
|
-
"ochre",
|
|
251
|
-
"olive",
|
|
252
|
-
"orange",
|
|
253
|
-
"orchid",
|
|
254
|
-
"peach",
|
|
255
|
-
"pine",
|
|
256
|
-
"pink",
|
|
257
|
-
"plum",
|
|
258
|
-
"porcelain",
|
|
259
|
-
"purple",
|
|
260
|
-
"quartz",
|
|
261
|
-
"rust",
|
|
262
|
-
"saffron",
|
|
263
|
-
"salmon",
|
|
264
|
-
"sand",
|
|
265
|
-
"sapphire",
|
|
266
|
-
"scarlet",
|
|
267
|
-
"sepia",
|
|
268
|
-
"shadow",
|
|
269
|
-
"sienna",
|
|
270
|
-
"smoke",
|
|
271
|
-
"snow",
|
|
272
|
-
"steel",
|
|
273
|
-
"stone",
|
|
274
|
-
"storm",
|
|
275
|
-
"sunset",
|
|
276
|
-
"tan",
|
|
277
|
-
"tangerine",
|
|
278
|
-
"taupe",
|
|
279
|
-
"terra",
|
|
280
|
-
"timber",
|
|
281
|
-
"topaz",
|
|
282
|
-
"turquoise",
|
|
283
|
-
"umber",
|
|
284
|
-
"vanilla",
|
|
285
|
-
"walnut",
|
|
286
|
-
"wheat",
|
|
287
|
-
"white",
|
|
288
|
-
"wine",
|
|
289
|
-
"yellow",
|
|
290
|
-
];
|
|
291
|
-
|
|
292
|
-
const ANIMALS = [
|
|
293
|
-
"fox",
|
|
294
|
-
"owl",
|
|
295
|
-
"bear",
|
|
296
|
-
"wolf",
|
|
297
|
-
"hawk",
|
|
298
|
-
"deer",
|
|
299
|
-
"lynx",
|
|
300
|
-
"otter",
|
|
301
|
-
"raven",
|
|
302
|
-
"falcon",
|
|
303
|
-
"badger",
|
|
304
|
-
"beaver",
|
|
305
|
-
"bison",
|
|
306
|
-
"bobcat",
|
|
307
|
-
"crane",
|
|
308
|
-
"eagle",
|
|
309
|
-
"ferret",
|
|
310
|
-
"finch",
|
|
311
|
-
"gopher",
|
|
312
|
-
"heron",
|
|
313
|
-
"jaguar",
|
|
314
|
-
"koala",
|
|
315
|
-
"lemur",
|
|
316
|
-
"marten",
|
|
317
|
-
"mink",
|
|
318
|
-
"moose",
|
|
319
|
-
"newt",
|
|
320
|
-
"ocelot",
|
|
321
|
-
"osprey",
|
|
322
|
-
"panda",
|
|
323
|
-
"parrot",
|
|
324
|
-
"pelican",
|
|
325
|
-
"puma",
|
|
326
|
-
"quail",
|
|
327
|
-
"rabbit",
|
|
328
|
-
"raccoon",
|
|
329
|
-
"salmon",
|
|
330
|
-
"seal",
|
|
331
|
-
"shark",
|
|
332
|
-
"shrew",
|
|
333
|
-
"sloth",
|
|
334
|
-
"snake",
|
|
335
|
-
"spider",
|
|
336
|
-
"squid",
|
|
337
|
-
"stork",
|
|
338
|
-
"swan",
|
|
339
|
-
"tiger",
|
|
340
|
-
"toucan",
|
|
341
|
-
"turtle",
|
|
342
|
-
"whale",
|
|
343
|
-
"albatross",
|
|
344
|
-
"ant",
|
|
345
|
-
"antelope",
|
|
346
|
-
"armadillo",
|
|
347
|
-
"baboon",
|
|
348
|
-
"bat",
|
|
349
|
-
"bee",
|
|
350
|
-
"beetle",
|
|
351
|
-
"buffalo",
|
|
352
|
-
"butterfly",
|
|
353
|
-
"camel",
|
|
354
|
-
"cardinal",
|
|
355
|
-
"caribou",
|
|
356
|
-
"catfish",
|
|
357
|
-
"cheetah",
|
|
358
|
-
"chipmunk",
|
|
359
|
-
"cicada",
|
|
360
|
-
"clam",
|
|
361
|
-
"cobra",
|
|
362
|
-
"condor",
|
|
363
|
-
"corgi",
|
|
364
|
-
"cougar",
|
|
365
|
-
"coyote",
|
|
366
|
-
"crab",
|
|
367
|
-
"cricket",
|
|
368
|
-
"crow",
|
|
369
|
-
"dolphin",
|
|
370
|
-
"donkey",
|
|
371
|
-
"dove",
|
|
372
|
-
"dragonfly",
|
|
373
|
-
"duck",
|
|
374
|
-
"eel",
|
|
375
|
-
"egret",
|
|
376
|
-
"elephant",
|
|
377
|
-
"elk",
|
|
378
|
-
"emu",
|
|
379
|
-
"firefly",
|
|
380
|
-
"flamingo",
|
|
381
|
-
"frog",
|
|
382
|
-
"gazelle",
|
|
383
|
-
"gecko",
|
|
384
|
-
"gibbon",
|
|
385
|
-
"giraffe",
|
|
386
|
-
"goat",
|
|
387
|
-
"goose",
|
|
388
|
-
"gorilla",
|
|
389
|
-
"grasshopper",
|
|
390
|
-
"grouse",
|
|
391
|
-
"gull",
|
|
392
|
-
"hamster",
|
|
393
|
-
"hare",
|
|
394
|
-
"hedgehog",
|
|
395
|
-
"hippo",
|
|
396
|
-
"hornet",
|
|
397
|
-
"horse",
|
|
398
|
-
"hound",
|
|
399
|
-
"hummingbird",
|
|
400
|
-
"hyena",
|
|
401
|
-
"ibis",
|
|
402
|
-
"iguana",
|
|
403
|
-
"impala",
|
|
404
|
-
"jackal",
|
|
405
|
-
"jay",
|
|
406
|
-
"jellyfish",
|
|
407
|
-
"kangaroo",
|
|
408
|
-
"kestrel",
|
|
409
|
-
"kingfisher",
|
|
410
|
-
"kite",
|
|
411
|
-
"kiwi",
|
|
412
|
-
"lark",
|
|
413
|
-
"leopard",
|
|
414
|
-
"lion",
|
|
415
|
-
"lizard",
|
|
416
|
-
"llama",
|
|
417
|
-
"lobster",
|
|
418
|
-
"loon",
|
|
419
|
-
"macaw",
|
|
420
|
-
"magpie",
|
|
421
|
-
"mallard",
|
|
422
|
-
"mammoth",
|
|
423
|
-
"manatee",
|
|
424
|
-
"mantis",
|
|
425
|
-
"marlin",
|
|
426
|
-
"marmot",
|
|
427
|
-
"meerkat",
|
|
428
|
-
"mockingbird",
|
|
429
|
-
"mole",
|
|
430
|
-
"mongoose",
|
|
431
|
-
"monkey",
|
|
432
|
-
"moth",
|
|
433
|
-
"mouse",
|
|
434
|
-
"mule",
|
|
435
|
-
"narwhal",
|
|
436
|
-
"nightingale",
|
|
437
|
-
"octopus",
|
|
438
|
-
"opossum",
|
|
439
|
-
"orangutan",
|
|
440
|
-
"oriole",
|
|
441
|
-
"ostrich",
|
|
442
|
-
"oyster",
|
|
443
|
-
"panther",
|
|
444
|
-
"peacock",
|
|
445
|
-
"penguin",
|
|
446
|
-
"pheasant",
|
|
447
|
-
"pig",
|
|
448
|
-
"pigeon",
|
|
449
|
-
"pike",
|
|
450
|
-
"piranha",
|
|
451
|
-
"platypus",
|
|
452
|
-
"pony",
|
|
453
|
-
"porcupine",
|
|
454
|
-
"porpoise",
|
|
455
|
-
"python",
|
|
456
|
-
"raven",
|
|
457
|
-
"ray",
|
|
458
|
-
"reindeer",
|
|
459
|
-
"rhino",
|
|
460
|
-
"robin",
|
|
461
|
-
"rooster",
|
|
462
|
-
"salamander",
|
|
463
|
-
"sandpiper",
|
|
464
|
-
"sardine",
|
|
465
|
-
"scorpion",
|
|
466
|
-
"seagull",
|
|
467
|
-
"seahorse",
|
|
468
|
-
"skunk",
|
|
469
|
-
"snail",
|
|
470
|
-
"sparrow",
|
|
471
|
-
"squirrel",
|
|
472
|
-
"starfish",
|
|
473
|
-
"starling",
|
|
474
|
-
"stingray",
|
|
475
|
-
"swallow",
|
|
476
|
-
"tapir",
|
|
477
|
-
"termite",
|
|
478
|
-
"tern",
|
|
479
|
-
"toad",
|
|
480
|
-
"trout",
|
|
481
|
-
"tuna",
|
|
482
|
-
"viper",
|
|
483
|
-
"vulture",
|
|
484
|
-
"walrus",
|
|
485
|
-
"wasp",
|
|
486
|
-
"weasel",
|
|
487
|
-
"wombat",
|
|
488
|
-
"woodpecker",
|
|
489
|
-
"wren",
|
|
490
|
-
"yak",
|
|
491
|
-
"zebra",
|
|
492
|
-
];
|
|
493
|
-
|
|
494
|
-
const WORKTREE_FOLDER_NAME = ".array";
|
|
495
|
-
|
|
496
|
-
export class WorktreeManager {
|
|
497
|
-
private mainRepoPath: string;
|
|
498
|
-
private worktreeBasePath: string | null;
|
|
499
|
-
private repoName: string;
|
|
500
|
-
private logger: Logger;
|
|
501
|
-
|
|
502
|
-
constructor(config: WorktreeConfig) {
|
|
503
|
-
this.mainRepoPath = config.mainRepoPath;
|
|
504
|
-
this.worktreeBasePath = config.worktreeBasePath || null;
|
|
505
|
-
this.repoName = path.basename(config.mainRepoPath);
|
|
506
|
-
this.logger =
|
|
507
|
-
config.logger ||
|
|
508
|
-
new Logger({ debug: false, prefix: "[WorktreeManager]" });
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
private usesExternalPath(): boolean {
|
|
512
|
-
return this.worktreeBasePath !== null;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
private async runGitCommand(command: string): Promise<string> {
|
|
516
|
-
try {
|
|
517
|
-
const { stdout } = await execAsync(`git ${command}`, {
|
|
518
|
-
cwd: this.mainRepoPath,
|
|
519
|
-
});
|
|
520
|
-
return stdout.trim();
|
|
521
|
-
} catch (error) {
|
|
522
|
-
throw new Error(`Git command failed: ${command}\n${error}`);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
private randomElement<T>(array: T[]): T {
|
|
527
|
-
return array[crypto.randomInt(array.length)];
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
generateWorktreeName(): string {
|
|
531
|
-
const adjective = this.randomElement(ADJECTIVES);
|
|
532
|
-
const color = this.randomElement(COLORS);
|
|
533
|
-
const animal = this.randomElement(ANIMALS);
|
|
534
|
-
return `${adjective}-${color}-${animal}`;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
private getWorktreeFolderPath(): string {
|
|
538
|
-
if (this.worktreeBasePath) {
|
|
539
|
-
return path.join(this.worktreeBasePath, this.repoName);
|
|
540
|
-
}
|
|
541
|
-
return path.join(this.mainRepoPath, WORKTREE_FOLDER_NAME);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
private getWorktreePath(name: string): string {
|
|
545
|
-
return path.join(this.getWorktreeFolderPath(), name);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
async worktreeExists(name: string): Promise<boolean> {
|
|
549
|
-
const worktreePath = this.getWorktreePath(name);
|
|
550
|
-
try {
|
|
551
|
-
await fs.access(worktreePath);
|
|
552
|
-
return true;
|
|
553
|
-
} catch {
|
|
554
|
-
return false;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
async ensureArrayDirIgnored(): Promise<void> {
|
|
559
|
-
// Use .git/info/exclude instead of .gitignore to avoid modifying tracked files
|
|
560
|
-
const excludePath = path.join(this.mainRepoPath, ".git", "info", "exclude");
|
|
561
|
-
const ignorePattern = `/${WORKTREE_FOLDER_NAME}/`;
|
|
562
|
-
|
|
563
|
-
let content = "";
|
|
564
|
-
try {
|
|
565
|
-
content = await fs.readFile(excludePath, "utf-8");
|
|
566
|
-
} catch {
|
|
567
|
-
// File doesn't exist or .git/info doesn't exist
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Check if pattern is already present
|
|
571
|
-
if (
|
|
572
|
-
content.includes(`/${WORKTREE_FOLDER_NAME}/`) ||
|
|
573
|
-
content.includes(`/${WORKTREE_FOLDER_NAME}`)
|
|
574
|
-
) {
|
|
575
|
-
this.logger.debug("Exclude file already contains .array folder pattern");
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Ensure .git/info directory exists
|
|
580
|
-
const infoDir = path.join(this.mainRepoPath, ".git", "info");
|
|
581
|
-
await fs.mkdir(infoDir, { recursive: true });
|
|
582
|
-
|
|
583
|
-
// Append the pattern
|
|
584
|
-
const newContent = `${content.trimEnd()}\n\n# Array worktrees\n${ignorePattern}\n`;
|
|
585
|
-
await fs.writeFile(excludePath, newContent);
|
|
586
|
-
this.logger.info("Added .array folder to .git/info/exclude");
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
private async generateUniqueWorktreeName(): Promise<string> {
|
|
590
|
-
let name = this.generateWorktreeName();
|
|
591
|
-
let attempts = 0;
|
|
592
|
-
const maxAttempts = 100;
|
|
593
|
-
|
|
594
|
-
while ((await this.worktreeExists(name)) && attempts < maxAttempts) {
|
|
595
|
-
name = this.generateWorktreeName();
|
|
596
|
-
attempts++;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (attempts >= maxAttempts) {
|
|
600
|
-
// Fallback: append timestamp
|
|
601
|
-
name = `${this.generateWorktreeName()}-${Date.now()}`;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
return name;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
private async getDefaultBranch(): Promise<string> {
|
|
608
|
-
try {
|
|
609
|
-
const remoteBranch = await this.runGitCommand(
|
|
610
|
-
"symbolic-ref refs/remotes/origin/HEAD",
|
|
611
|
-
);
|
|
612
|
-
return remoteBranch.replace("refs/remotes/origin/", "");
|
|
613
|
-
} catch {
|
|
614
|
-
// Fallback: check if main exists, otherwise use master
|
|
615
|
-
try {
|
|
616
|
-
await this.runGitCommand("rev-parse --verify main");
|
|
617
|
-
return "main";
|
|
618
|
-
} catch {
|
|
619
|
-
try {
|
|
620
|
-
await this.runGitCommand("rev-parse --verify master");
|
|
621
|
-
return "master";
|
|
622
|
-
} catch {
|
|
623
|
-
throw new Error(
|
|
624
|
-
"Cannot determine default branch. No main or master branch found.",
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
async createWorktree(options?: {
|
|
632
|
-
baseBranch?: string;
|
|
633
|
-
}): Promise<WorktreeInfo> {
|
|
634
|
-
// Only modify .git/info/exclude when using in-repo storage
|
|
635
|
-
if (!this.usesExternalPath()) {
|
|
636
|
-
await this.ensureArrayDirIgnored();
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Ensure the worktree folder exists when using external path
|
|
640
|
-
if (this.usesExternalPath()) {
|
|
641
|
-
const folderPath = this.getWorktreeFolderPath();
|
|
642
|
-
await fs.mkdir(folderPath, { recursive: true });
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Generate unique worktree name
|
|
646
|
-
const worktreeName = await this.generateUniqueWorktreeName();
|
|
647
|
-
const worktreePath = this.getWorktreePath(worktreeName);
|
|
648
|
-
const branchName = `array/${worktreeName}`;
|
|
649
|
-
const baseBranch = options?.baseBranch ?? (await this.getDefaultBranch());
|
|
650
|
-
|
|
651
|
-
this.logger.info("Creating worktree", {
|
|
652
|
-
worktreeName,
|
|
653
|
-
worktreePath,
|
|
654
|
-
branchName,
|
|
655
|
-
baseBranch,
|
|
656
|
-
external: this.usesExternalPath(),
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
// Create the worktree with a new branch
|
|
660
|
-
if (this.usesExternalPath()) {
|
|
661
|
-
// Use absolute path for external worktrees
|
|
662
|
-
await this.runGitCommand(
|
|
663
|
-
`worktree add -b "${branchName}" "${worktreePath}" "${baseBranch}"`,
|
|
664
|
-
);
|
|
665
|
-
} else {
|
|
666
|
-
// Use relative path from repo root for in-repo worktrees
|
|
667
|
-
const relativePath = `${WORKTREE_FOLDER_NAME}/${worktreeName}`;
|
|
668
|
-
await this.runGitCommand(
|
|
669
|
-
`worktree add -b "${branchName}" "./${relativePath}" "${baseBranch}"`,
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const createdAt = new Date().toISOString();
|
|
674
|
-
|
|
675
|
-
this.logger.info("Worktree created successfully", {
|
|
676
|
-
worktreeName,
|
|
677
|
-
worktreePath,
|
|
678
|
-
branchName,
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
return {
|
|
682
|
-
worktreePath,
|
|
683
|
-
worktreeName,
|
|
684
|
-
branchName,
|
|
685
|
-
baseBranch,
|
|
686
|
-
createdAt,
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
async deleteWorktree(worktreePath: string): Promise<void> {
|
|
691
|
-
const resolvedWorktreePath = path.resolve(worktreePath);
|
|
692
|
-
const resolvedMainRepoPath = path.resolve(this.mainRepoPath);
|
|
693
|
-
|
|
694
|
-
// Safety check 1: Never delete the main repo path
|
|
695
|
-
if (resolvedWorktreePath === resolvedMainRepoPath) {
|
|
696
|
-
const error = new Error(
|
|
697
|
-
"Cannot delete worktree: path matches main repo path",
|
|
698
|
-
);
|
|
699
|
-
this.logger.error("Safety check failed", { worktreePath, error });
|
|
700
|
-
throw error;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Safety check 2: Never delete a parent of the main repo path
|
|
704
|
-
if (
|
|
705
|
-
resolvedMainRepoPath.startsWith(resolvedWorktreePath) &&
|
|
706
|
-
resolvedMainRepoPath !== resolvedWorktreePath
|
|
707
|
-
) {
|
|
708
|
-
const error = new Error(
|
|
709
|
-
"Cannot delete worktree: path is a parent of main repo path",
|
|
710
|
-
);
|
|
711
|
-
this.logger.error("Safety check failed", { worktreePath, error });
|
|
712
|
-
throw error;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// Safety check 3: Check for .git directory (indicates main repo)
|
|
716
|
-
try {
|
|
717
|
-
const gitPath = path.join(resolvedWorktreePath, ".git");
|
|
718
|
-
const stat = await fs.stat(gitPath);
|
|
719
|
-
if (stat.isDirectory()) {
|
|
720
|
-
const error = new Error(
|
|
721
|
-
"Cannot delete worktree: path appears to be a main repository (contains .git directory)",
|
|
722
|
-
);
|
|
723
|
-
this.logger.error("Safety check failed", { worktreePath, error });
|
|
724
|
-
throw error;
|
|
725
|
-
}
|
|
726
|
-
} catch (error) {
|
|
727
|
-
// If .git doesn't exist or we can't read it, proceed (unless it was the directory check above)
|
|
728
|
-
if (
|
|
729
|
-
error instanceof Error &&
|
|
730
|
-
error.message.includes("Cannot delete worktree")
|
|
731
|
-
) {
|
|
732
|
-
throw error;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
this.logger.info("Deleting worktree", { worktreePath });
|
|
737
|
-
|
|
738
|
-
try {
|
|
739
|
-
// First, try to remove the worktree via git using execFileAsync for safety
|
|
740
|
-
await execFileAsync(
|
|
741
|
-
"git",
|
|
742
|
-
["worktree", "remove", worktreePath, "--force"],
|
|
743
|
-
{
|
|
744
|
-
cwd: this.mainRepoPath,
|
|
745
|
-
},
|
|
746
|
-
);
|
|
747
|
-
this.logger.info("Worktree deleted successfully", { worktreePath });
|
|
748
|
-
} catch (error) {
|
|
749
|
-
this.logger.warn(
|
|
750
|
-
"Git worktree remove failed, attempting manual cleanup",
|
|
751
|
-
{
|
|
752
|
-
worktreePath,
|
|
753
|
-
error,
|
|
754
|
-
},
|
|
755
|
-
);
|
|
756
|
-
|
|
757
|
-
// Manual cleanup if git command fails
|
|
758
|
-
try {
|
|
759
|
-
await fs.rm(worktreePath, { recursive: true, force: true });
|
|
760
|
-
// Also prune the worktree list
|
|
761
|
-
await this.runGitCommand("worktree prune");
|
|
762
|
-
this.logger.info("Worktree cleaned up manually", { worktreePath });
|
|
763
|
-
} catch (cleanupError) {
|
|
764
|
-
this.logger.error("Failed to cleanup worktree", {
|
|
765
|
-
worktreePath,
|
|
766
|
-
cleanupError,
|
|
767
|
-
});
|
|
768
|
-
throw cleanupError;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
async getWorktreeInfo(worktreePath: string): Promise<WorktreeInfo | null> {
|
|
774
|
-
try {
|
|
775
|
-
// Parse the worktree list to find info about this worktree
|
|
776
|
-
const output = await this.runGitCommand("worktree list --porcelain");
|
|
777
|
-
const worktrees = this.parseWorktreeList(output);
|
|
778
|
-
|
|
779
|
-
const worktree = worktrees.find((w) => w.worktreePath === worktreePath);
|
|
780
|
-
return worktree || null;
|
|
781
|
-
} catch (error) {
|
|
782
|
-
this.logger.debug("Failed to get worktree info", { worktreePath, error });
|
|
783
|
-
return null;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
async listWorktrees(): Promise<WorktreeInfo[]> {
|
|
788
|
-
try {
|
|
789
|
-
const output = await this.runGitCommand("worktree list --porcelain");
|
|
790
|
-
return this.parseWorktreeList(output);
|
|
791
|
-
} catch (error) {
|
|
792
|
-
this.logger.debug("Failed to list worktrees", { error });
|
|
793
|
-
return [];
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
private parseWorktreeList(output: string): WorktreeInfo[] {
|
|
798
|
-
const worktrees: WorktreeInfo[] = [];
|
|
799
|
-
const entries = output.split("\n\n").filter((e) => e.trim());
|
|
800
|
-
const worktreeFolderPath = this.getWorktreeFolderPath();
|
|
801
|
-
|
|
802
|
-
for (const entry of entries) {
|
|
803
|
-
const lines = entry.split("\n");
|
|
804
|
-
let worktreePath = "";
|
|
805
|
-
let branchName = "";
|
|
806
|
-
|
|
807
|
-
for (const line of lines) {
|
|
808
|
-
if (line.startsWith("worktree ")) {
|
|
809
|
-
worktreePath = line.replace("worktree ", "");
|
|
810
|
-
} else if (line.startsWith("branch refs/heads/")) {
|
|
811
|
-
branchName = line.replace("branch refs/heads/", "");
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Include worktrees that:
|
|
816
|
-
// 1. Are in our worktree folder (external or in-repo)
|
|
817
|
-
// 2. Have a posthog/ branch prefix (our naming convention)
|
|
818
|
-
const isInWorktreeFolder = worktreePath?.startsWith(worktreeFolderPath);
|
|
819
|
-
const isArrayBranch =
|
|
820
|
-
branchName?.startsWith("array/") || branchName?.startsWith("posthog/");
|
|
821
|
-
|
|
822
|
-
if (worktreePath && branchName && (isInWorktreeFolder || isArrayBranch)) {
|
|
823
|
-
const worktreeName = path.basename(worktreePath);
|
|
824
|
-
worktrees.push({
|
|
825
|
-
worktreePath,
|
|
826
|
-
worktreeName,
|
|
827
|
-
branchName,
|
|
828
|
-
baseBranch: "",
|
|
829
|
-
createdAt: "",
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
return worktrees;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
async isWorktree(repoPath: string): Promise<boolean> {
|
|
838
|
-
try {
|
|
839
|
-
const { stdout } = await execAsync(
|
|
840
|
-
"git rev-parse --is-inside-work-tree",
|
|
841
|
-
{ cwd: repoPath },
|
|
842
|
-
);
|
|
843
|
-
if (stdout.trim() !== "true") {
|
|
844
|
-
return false;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// Check if there's a .git file (worktrees have a .git file, not a .git directory)
|
|
848
|
-
const gitPath = path.join(repoPath, ".git");
|
|
849
|
-
const stat = await fs.stat(gitPath);
|
|
850
|
-
return stat.isFile(); // Worktrees have .git as a file, main repos have .git as a directory
|
|
851
|
-
} catch {
|
|
852
|
-
return false;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
async getMainRepoPathFromWorktree(
|
|
857
|
-
worktreePath: string,
|
|
858
|
-
): Promise<string | null> {
|
|
859
|
-
try {
|
|
860
|
-
const gitFilePath = path.join(worktreePath, ".git");
|
|
861
|
-
const content = await fs.readFile(gitFilePath, "utf-8");
|
|
862
|
-
|
|
863
|
-
// The .git file in a worktree contains: gitdir: /path/to/main/.git/worktrees/name
|
|
864
|
-
const match = content.match(/gitdir:\s*(.+)/);
|
|
865
|
-
if (match) {
|
|
866
|
-
const gitDir = match[1].trim();
|
|
867
|
-
// Go up from .git/worktrees/name to get the main repo path
|
|
868
|
-
// The gitdir points to something like: /main/repo/.git/worktrees/worktree-name
|
|
869
|
-
const mainGitDir = path.resolve(gitDir, "..", "..", "..");
|
|
870
|
-
return mainGitDir;
|
|
871
|
-
}
|
|
872
|
-
return null;
|
|
873
|
-
} catch {
|
|
874
|
-
return null;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
async cleanupOrphanedWorktrees(associatedWorktreePaths: string[]): Promise<{
|
|
879
|
-
deleted: string[];
|
|
880
|
-
errors: Array<{ path: string; error: string }>;
|
|
881
|
-
}> {
|
|
882
|
-
this.logger.info("Starting cleanup of orphaned worktrees");
|
|
883
|
-
|
|
884
|
-
const allWorktrees = await this.listWorktrees();
|
|
885
|
-
const deleted: string[] = [];
|
|
886
|
-
const errors: Array<{ path: string; error: string }> = [];
|
|
887
|
-
|
|
888
|
-
const associatedPathsSet = new Set(
|
|
889
|
-
associatedWorktreePaths.map((p) => path.resolve(p)),
|
|
890
|
-
);
|
|
891
|
-
|
|
892
|
-
for (const worktree of allWorktrees) {
|
|
893
|
-
const resolvedPath = path.resolve(worktree.worktreePath);
|
|
894
|
-
|
|
895
|
-
if (!associatedPathsSet.has(resolvedPath)) {
|
|
896
|
-
this.logger.info("Found orphaned worktree", {
|
|
897
|
-
path: worktree.worktreePath,
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
try {
|
|
901
|
-
await this.deleteWorktree(worktree.worktreePath);
|
|
902
|
-
deleted.push(worktree.worktreePath);
|
|
903
|
-
this.logger.info("Deleted orphaned worktree", {
|
|
904
|
-
path: worktree.worktreePath,
|
|
905
|
-
});
|
|
906
|
-
} catch (error) {
|
|
907
|
-
const errorMessage =
|
|
908
|
-
error instanceof Error ? error.message : String(error);
|
|
909
|
-
errors.push({
|
|
910
|
-
path: worktree.worktreePath,
|
|
911
|
-
error: errorMessage,
|
|
912
|
-
});
|
|
913
|
-
this.logger.error("Failed to delete orphaned worktree", {
|
|
914
|
-
path: worktree.worktreePath,
|
|
915
|
-
error: errorMessage,
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
this.logger.info("Cleanup completed", {
|
|
922
|
-
deleted: deleted.length,
|
|
923
|
-
errors: errors.length,
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
return { deleted, errors };
|
|
927
|
-
}
|
|
928
|
-
}
|