@ashulab/carrier 0.0.0-stage → 0.1.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.
Files changed (193) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +265 -2
  3. package/dist/chat/claude.js +367 -0
  4. package/dist/chat/codex.js +375 -0
  5. package/dist/chat/config.js +61 -0
  6. package/dist/chat/db.js +9 -0
  7. package/dist/chat/fake.js +203 -0
  8. package/dist/chat/folder.js +56 -0
  9. package/dist/chat/git.js +27 -0
  10. package/dist/chat/group.js +28 -0
  11. package/dist/chat/health.js +49 -0
  12. package/dist/chat/host.js +123 -0
  13. package/dist/chat/idle.js +20 -0
  14. package/dist/chat/migrations/0000_lame_quasar.sql +15 -0
  15. package/dist/chat/migrations/0001_quiet_penance.sql +23 -0
  16. package/dist/chat/migrations/0002_slim_ezekiel_stane.sql +1 -0
  17. package/dist/chat/migrations/0003_naive_iron_lad.sql +5 -0
  18. package/dist/chat/migrations/0004_skinny_dakota_north.sql +1 -0
  19. package/dist/chat/migrations/0005_tidy_strong_guy.sql +1 -0
  20. package/dist/chat/migrations/meta/0000_snapshot.json +121 -0
  21. package/dist/chat/migrations/meta/0001_snapshot.json +277 -0
  22. package/dist/chat/migrations/meta/0002_snapshot.json +284 -0
  23. package/dist/chat/migrations/meta/0003_snapshot.json +320 -0
  24. package/dist/chat/migrations/meta/0004_snapshot.json +327 -0
  25. package/dist/chat/migrations/meta/0005_snapshot.json +334 -0
  26. package/dist/chat/migrations/meta/_journal.json +48 -0
  27. package/dist/chat/names.js +97 -0
  28. package/dist/chat/plan.js +21 -0
  29. package/dist/chat/projects.js +242 -0
  30. package/dist/chat/runner.js +6 -0
  31. package/dist/chat/schema.js +48 -0
  32. package/dist/chat/server.js +202 -0
  33. package/dist/chat/sessions.js +766 -0
  34. package/dist/chat/sweep.js +64 -0
  35. package/dist/chat/transcript.js +31 -0
  36. package/dist/chat/words.js +20 -0
  37. package/dist/chat/workspace.js +134 -0
  38. package/dist/chat/worktree.js +142 -0
  39. package/dist/config/default-path.js +7 -0
  40. package/dist/config/engine-places.js +9 -0
  41. package/dist/config/json-schema.js +15 -0
  42. package/dist/config/load.js +48 -0
  43. package/dist/config/resolve.js +41 -0
  44. package/dist/config/schema.js +225 -0
  45. package/dist/control/epic.js +219 -0
  46. package/dist/control/events.js +48 -0
  47. package/dist/control/files.js +262 -0
  48. package/dist/control/replies.js +1 -0
  49. package/dist/control/server.js +237 -0
  50. package/dist/control/sessions.js +68 -0
  51. package/dist/doctor/doctor.js +255 -0
  52. package/dist/doctor/fixes.js +35 -0
  53. package/dist/execution/agent-runner.js +1 -0
  54. package/dist/execution/caller-result.js +61 -0
  55. package/dist/execution/events.js +19 -0
  56. package/dist/execution/failure.js +3 -0
  57. package/dist/execution/fake.js +82 -0
  58. package/dist/execution/git-workspaces.js +136 -0
  59. package/dist/execution/import-result.js +23 -0
  60. package/dist/execution/workspaces.js +1 -0
  61. package/dist/format/duration.js +12 -0
  62. package/dist/harnesses/agy.js +131 -0
  63. package/dist/harnesses/claude.js +246 -0
  64. package/dist/harnesses/codex.js +118 -0
  65. package/dist/harnesses/common.js +59 -0
  66. package/dist/harnesses/credentials.js +38 -0
  67. package/dist/harnesses/host-runner.js +225 -0
  68. package/dist/harnesses/local.js +25 -0
  69. package/dist/harnesses/names.js +9 -0
  70. package/dist/harnesses/replay-events.js +57 -0
  71. package/dist/integrations/available-repos.js +29 -0
  72. package/dist/integrations/forge-cwd.js +27 -0
  73. package/dist/integrations/github-graphql.js +216 -0
  74. package/dist/integrations/github.js +231 -0
  75. package/dist/logging/logger.js +53 -0
  76. package/dist/logging/redact.js +21 -0
  77. package/dist/orchestration/blockers.js +25 -0
  78. package/dist/orchestration/changes-slices.js +5 -0
  79. package/dist/orchestration/clock.js +7 -0
  80. package/dist/orchestration/engine.js +1068 -0
  81. package/dist/orchestration/fake.js +152 -0
  82. package/dist/orchestration/final-pr.js +15 -0
  83. package/dist/orchestration/github.js +31 -0
  84. package/dist/orchestration/reserve.js +32 -0
  85. package/dist/orchestration/resume-refused.js +7 -0
  86. package/dist/orchestration/retention.js +145 -0
  87. package/dist/orchestration/retry.js +16 -0
  88. package/dist/prompts/build-prompt.js +13 -0
  89. package/dist/prompts/fix.js +21 -0
  90. package/dist/prompts/intro.js +21 -0
  91. package/dist/prompts/invoke.js +2 -0
  92. package/dist/prompts/persona.js +12 -0
  93. package/dist/prompts/result.js +32 -0
  94. package/dist/prompts/review.js +15 -0
  95. package/dist/prompts/ship.js +8 -0
  96. package/dist/status/snapshot.js +92 -0
  97. package/dist/status/types.js +1 -0
  98. package/dist/storage/caller-results.js +7 -0
  99. package/dist/storage/db.js +8 -0
  100. package/dist/storage/epic-review-threads.js +19 -0
  101. package/dist/storage/epic-slices.js +17 -0
  102. package/dist/storage/events.js +52 -0
  103. package/dist/storage/executions.js +31 -0
  104. package/dist/storage/lock.js +21 -0
  105. package/dist/storage/migrations/0000_nosy_demogoblin.sql +5 -0
  106. package/dist/storage/migrations/0001_worried_grey_gargoyle.sql +3 -0
  107. package/dist/storage/migrations/0002_tough_squirrel_girl.sql +5 -0
  108. package/dist/storage/migrations/0003_loose_maria_hill.sql +4 -0
  109. package/dist/storage/migrations/0004_long_cerise.sql +18 -0
  110. package/dist/storage/migrations/0005_review_result.sql +2 -0
  111. package/dist/storage/migrations/0006_melted_menace.sql +10 -0
  112. package/dist/storage/migrations/0007_powerful_bloodscream.sql +2 -0
  113. package/dist/storage/migrations/0008_panel_state.sql +4 -0
  114. package/dist/storage/migrations/0009_workable_tiger_shark.sql +5 -0
  115. package/dist/storage/migrations/0010_nasty_blue_marvel.sql +4 -0
  116. package/dist/storage/migrations/0011_steep_lila_cheney.sql +13 -0
  117. package/dist/storage/migrations/0012_talented_big_bertha.sql +1 -0
  118. package/dist/storage/migrations/0013_third_mulholland_black.sql +17 -0
  119. package/dist/storage/migrations/0014_perfect_marten_broadcloak.sql +20 -0
  120. package/dist/storage/migrations/0015_long_bloodaxe.sql +1 -0
  121. package/dist/storage/migrations/0016_neat_true_believers.sql +5 -0
  122. package/dist/storage/migrations/0017_previous_swarm.sql +6 -0
  123. package/dist/storage/migrations/0018_opposite_plazm.sql +2 -0
  124. package/dist/storage/migrations/0019_hesitant_triton.sql +3 -0
  125. package/dist/storage/migrations/meta/0000_snapshot.json +49 -0
  126. package/dist/storage/migrations/meta/0001_snapshot.json +74 -0
  127. package/dist/storage/migrations/meta/0002_snapshot.json +105 -0
  128. package/dist/storage/migrations/meta/0003_snapshot.json +134 -0
  129. package/dist/storage/migrations/meta/0004_snapshot.json +257 -0
  130. package/dist/storage/migrations/meta/0005_snapshot.json +271 -0
  131. package/dist/storage/migrations/meta/0006_snapshot.json +337 -0
  132. package/dist/storage/migrations/meta/0007_snapshot.json +351 -0
  133. package/dist/storage/migrations/meta/0008_snapshot.json +379 -0
  134. package/dist/storage/migrations/meta/0009_snapshot.json +414 -0
  135. package/dist/storage/migrations/meta/0010_snapshot.json +442 -0
  136. package/dist/storage/migrations/meta/0011_snapshot.json +531 -0
  137. package/dist/storage/migrations/meta/0012_snapshot.json +538 -0
  138. package/dist/storage/migrations/meta/0013_snapshot.json +655 -0
  139. package/dist/storage/migrations/meta/0014_snapshot.json +772 -0
  140. package/dist/storage/migrations/meta/0015_snapshot.json +779 -0
  141. package/dist/storage/migrations/meta/0016_snapshot.json +793 -0
  142. package/dist/storage/migrations/meta/0017_snapshot.json +835 -0
  143. package/dist/storage/migrations/meta/0018_snapshot.json +849 -0
  144. package/dist/storage/migrations/meta/0019_snapshot.json +870 -0
  145. package/dist/storage/migrations/meta/_journal.json +146 -0
  146. package/dist/storage/schema.js +43 -0
  147. package/dist/terminal/cli/args.js +91 -0
  148. package/dist/terminal/cli/cli-error.js +11 -0
  149. package/dist/terminal/cli/commands/chat-host.js +18 -0
  150. package/dist/terminal/cli/commands/chat.js +321 -0
  151. package/dist/terminal/cli/commands/control.js +46 -0
  152. package/dist/terminal/cli/commands/doctor.js +15 -0
  153. package/dist/terminal/cli/commands/init.js +43 -0
  154. package/dist/terminal/cli/commands/restart.js +92 -0
  155. package/dist/terminal/cli/commands/run.js +20 -0
  156. package/dist/terminal/cli/commands/schema.js +4 -0
  157. package/dist/terminal/cli/commands/start.js +21 -0
  158. package/dist/terminal/cli/commands/status.js +61 -0
  159. package/dist/terminal/cli/commands/watch.js +112 -0
  160. package/dist/terminal/cli/compose.js +163 -0
  161. package/dist/terminal/cli/config-file.js +24 -0
  162. package/dist/terminal/cli/control-client.js +94 -0
  163. package/dist/terminal/cli/detached.js +41 -0
  164. package/dist/terminal/cli/interrupt.js +39 -0
  165. package/dist/terminal/cli/main.js +85 -0
  166. package/dist/terminal/cli/open-url.js +9 -0
  167. package/dist/terminal/dashboard/dashboard.js +204 -0
  168. package/dist/terminal/dashboard/history.js +38 -0
  169. package/dist/terminal/dashboard/interactive.js +60 -0
  170. package/dist/terminal/dashboard/keys.js +22 -0
  171. package/dist/terminal/dashboard/logs.js +85 -0
  172. package/dist/terminal/dashboard/panel.js +62 -0
  173. package/dist/terminal/dashboard/plain.js +32 -0
  174. package/dist/terminal/dashboard/printable.js +2 -0
  175. package/dist/terminal/dashboard/ui-state.js +110 -0
  176. package/dist/terminal/prompts/clack.js +121 -0
  177. package/dist/terminal/prompts/types.js +3 -0
  178. package/dist/terminal/wizard/build-config.js +66 -0
  179. package/dist/terminal/wizard/deps.js +94 -0
  180. package/dist/terminal/wizard/flags.js +1 -0
  181. package/dist/terminal/wizard/forge-registry.js +23 -0
  182. package/dist/terminal/wizard/prior.js +20 -0
  183. package/dist/terminal/wizard/questions/agent.js +61 -0
  184. package/dist/terminal/wizard/questions/identities.js +28 -0
  185. package/dist/terminal/wizard/questions/identity.js +30 -0
  186. package/dist/terminal/wizard/questions/options.js +93 -0
  187. package/dist/terminal/wizard/questions/out.js +16 -0
  188. package/dist/terminal/wizard/questions/persona.js +47 -0
  189. package/dist/terminal/wizard/questions/repos.js +61 -0
  190. package/dist/terminal/wizard/summary.js +31 -0
  191. package/dist/terminal/wizard/testing.js +106 -0
  192. package/dist/terminal/wizard/wizard.js +172 -0
  193. package/package.json +46 -3
@@ -0,0 +1,334 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "6cb6556e-bc67-49c1-9183-85fa2da88fd8",
5
+ "prevId": "e9c3e32d-c642-40dd-854c-c7b2eb0a53b7",
6
+ "tables": {
7
+ "chat_events": {
8
+ "name": "chat_events",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "integer",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": true
16
+ },
17
+ "sessionId": {
18
+ "name": "sessionId",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "at": {
25
+ "name": "at",
26
+ "type": "integer",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false
30
+ },
31
+ "kind": {
32
+ "name": "kind",
33
+ "type": "text",
34
+ "primaryKey": false,
35
+ "notNull": true,
36
+ "autoincrement": false
37
+ },
38
+ "data": {
39
+ "name": "data",
40
+ "type": "text",
41
+ "primaryKey": false,
42
+ "notNull": true,
43
+ "autoincrement": false
44
+ }
45
+ },
46
+ "indexes": {
47
+ "chat_events_session": {
48
+ "name": "chat_events_session",
49
+ "columns": [
50
+ "sessionId",
51
+ "id"
52
+ ],
53
+ "isUnique": false
54
+ }
55
+ },
56
+ "foreignKeys": {
57
+ "chat_events_sessionId_sessions_id_fk": {
58
+ "name": "chat_events_sessionId_sessions_id_fk",
59
+ "tableFrom": "chat_events",
60
+ "tableTo": "sessions",
61
+ "columnsFrom": [
62
+ "sessionId"
63
+ ],
64
+ "columnsTo": [
65
+ "id"
66
+ ],
67
+ "onDelete": "no action",
68
+ "onUpdate": "no action"
69
+ }
70
+ },
71
+ "compositePrimaryKeys": {},
72
+ "uniqueConstraints": {},
73
+ "checkConstraints": {}
74
+ },
75
+ "chat_turns": {
76
+ "name": "chat_turns",
77
+ "columns": {
78
+ "id": {
79
+ "name": "id",
80
+ "type": "integer",
81
+ "primaryKey": true,
82
+ "notNull": true,
83
+ "autoincrement": true
84
+ },
85
+ "sessionId": {
86
+ "name": "sessionId",
87
+ "type": "text",
88
+ "primaryKey": false,
89
+ "notNull": true,
90
+ "autoincrement": false
91
+ },
92
+ "startedAt": {
93
+ "name": "startedAt",
94
+ "type": "integer",
95
+ "primaryKey": false,
96
+ "notNull": true,
97
+ "autoincrement": false
98
+ },
99
+ "model": {
100
+ "name": "model",
101
+ "type": "text",
102
+ "primaryKey": false,
103
+ "notNull": false,
104
+ "autoincrement": false
105
+ },
106
+ "outcome": {
107
+ "name": "outcome",
108
+ "type": "text",
109
+ "primaryKey": false,
110
+ "notNull": true,
111
+ "autoincrement": false
112
+ },
113
+ "durationMs": {
114
+ "name": "durationMs",
115
+ "type": "integer",
116
+ "primaryKey": false,
117
+ "notNull": true,
118
+ "autoincrement": false
119
+ },
120
+ "toolCalls": {
121
+ "name": "toolCalls",
122
+ "type": "integer",
123
+ "primaryKey": false,
124
+ "notNull": true,
125
+ "autoincrement": false
126
+ },
127
+ "usage": {
128
+ "name": "usage",
129
+ "type": "text",
130
+ "primaryKey": false,
131
+ "notNull": false,
132
+ "autoincrement": false
133
+ }
134
+ },
135
+ "indexes": {
136
+ "chat_turns_session": {
137
+ "name": "chat_turns_session",
138
+ "columns": [
139
+ "sessionId"
140
+ ],
141
+ "isUnique": false
142
+ }
143
+ },
144
+ "foreignKeys": {
145
+ "chat_turns_sessionId_sessions_id_fk": {
146
+ "name": "chat_turns_sessionId_sessions_id_fk",
147
+ "tableFrom": "chat_turns",
148
+ "tableTo": "sessions",
149
+ "columnsFrom": [
150
+ "sessionId"
151
+ ],
152
+ "columnsTo": [
153
+ "id"
154
+ ],
155
+ "onDelete": "no action",
156
+ "onUpdate": "no action"
157
+ }
158
+ },
159
+ "compositePrimaryKeys": {},
160
+ "uniqueConstraints": {},
161
+ "checkConstraints": {}
162
+ },
163
+ "sessions": {
164
+ "name": "sessions",
165
+ "columns": {
166
+ "id": {
167
+ "name": "id",
168
+ "type": "text",
169
+ "primaryKey": true,
170
+ "notNull": true,
171
+ "autoincrement": false
172
+ },
173
+ "agent": {
174
+ "name": "agent",
175
+ "type": "text",
176
+ "primaryKey": false,
177
+ "notNull": true,
178
+ "autoincrement": false
179
+ },
180
+ "harness": {
181
+ "name": "harness",
182
+ "type": "text",
183
+ "primaryKey": false,
184
+ "notNull": true,
185
+ "autoincrement": false
186
+ },
187
+ "folder": {
188
+ "name": "folder",
189
+ "type": "text",
190
+ "primaryKey": false,
191
+ "notNull": true,
192
+ "autoincrement": false
193
+ },
194
+ "workspaceKind": {
195
+ "name": "workspaceKind",
196
+ "type": "text",
197
+ "primaryKey": false,
198
+ "notNull": true,
199
+ "autoincrement": false,
200
+ "default": "'folder'"
201
+ },
202
+ "worktreeBranch": {
203
+ "name": "worktreeBranch",
204
+ "type": "text",
205
+ "primaryKey": false,
206
+ "notNull": false,
207
+ "autoincrement": false
208
+ },
209
+ "worktreeFrom": {
210
+ "name": "worktreeFrom",
211
+ "type": "text",
212
+ "primaryKey": false,
213
+ "notNull": false,
214
+ "autoincrement": false
215
+ },
216
+ "worktreeBase": {
217
+ "name": "worktreeBase",
218
+ "type": "text",
219
+ "primaryKey": false,
220
+ "notNull": false,
221
+ "autoincrement": false
222
+ },
223
+ "worktreePath": {
224
+ "name": "worktreePath",
225
+ "type": "text",
226
+ "primaryKey": false,
227
+ "notNull": false,
228
+ "autoincrement": false
229
+ },
230
+ "title": {
231
+ "name": "title",
232
+ "type": "text",
233
+ "primaryKey": false,
234
+ "notNull": false,
235
+ "autoincrement": false
236
+ },
237
+ "options": {
238
+ "name": "options",
239
+ "type": "text",
240
+ "primaryKey": false,
241
+ "notNull": false,
242
+ "autoincrement": false
243
+ },
244
+ "plan": {
245
+ "name": "plan",
246
+ "type": "text",
247
+ "primaryKey": false,
248
+ "notNull": false,
249
+ "autoincrement": false
250
+ },
251
+ "nativeId": {
252
+ "name": "nativeId",
253
+ "type": "text",
254
+ "primaryKey": false,
255
+ "notNull": false,
256
+ "autoincrement": false
257
+ },
258
+ "state": {
259
+ "name": "state",
260
+ "type": "text",
261
+ "primaryKey": false,
262
+ "notNull": true,
263
+ "autoincrement": false,
264
+ "default": "'idle'"
265
+ },
266
+ "pid": {
267
+ "name": "pid",
268
+ "type": "integer",
269
+ "primaryKey": false,
270
+ "notNull": false,
271
+ "autoincrement": false
272
+ },
273
+ "pgid": {
274
+ "name": "pgid",
275
+ "type": "integer",
276
+ "primaryKey": false,
277
+ "notNull": false,
278
+ "autoincrement": false
279
+ },
280
+ "command": {
281
+ "name": "command",
282
+ "type": "text",
283
+ "primaryKey": false,
284
+ "notNull": false,
285
+ "autoincrement": false
286
+ },
287
+ "createdAt": {
288
+ "name": "createdAt",
289
+ "type": "integer",
290
+ "primaryKey": false,
291
+ "notNull": true,
292
+ "autoincrement": false
293
+ },
294
+ "lastActiveAt": {
295
+ "name": "lastActiveAt",
296
+ "type": "integer",
297
+ "primaryKey": false,
298
+ "notNull": true,
299
+ "autoincrement": false
300
+ },
301
+ "archived": {
302
+ "name": "archived",
303
+ "type": "integer",
304
+ "primaryKey": false,
305
+ "notNull": true,
306
+ "autoincrement": false,
307
+ "default": false
308
+ },
309
+ "keptAt": {
310
+ "name": "keptAt",
311
+ "type": "integer",
312
+ "primaryKey": false,
313
+ "notNull": false,
314
+ "autoincrement": false
315
+ }
316
+ },
317
+ "indexes": {},
318
+ "foreignKeys": {},
319
+ "compositePrimaryKeys": {},
320
+ "uniqueConstraints": {},
321
+ "checkConstraints": {}
322
+ }
323
+ },
324
+ "views": {},
325
+ "enums": {},
326
+ "_meta": {
327
+ "schemas": {},
328
+ "tables": {},
329
+ "columns": {}
330
+ },
331
+ "internal": {
332
+ "indexes": {}
333
+ }
334
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1793147024821,
9
+ "tag": "0000_lame_quasar",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "6",
15
+ "when": 1793147813563,
16
+ "tag": "0001_quiet_penance",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "6",
22
+ "when": 1793148987442,
23
+ "tag": "0002_slim_ezekiel_stane",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "6",
29
+ "when": 1793197685547,
30
+ "tag": "0003_naive_iron_lad",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1793839872276,
37
+ "tag": "0004_skinny_dakota_north",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "6",
43
+ "when": 1793882078864,
44
+ "tag": "0005_tidy_strong_guy",
45
+ "breakpoints": true
46
+ }
47
+ ]
48
+ }
@@ -0,0 +1,97 @@
1
+ import { lstatSync } from 'node:fs';
2
+ import { basename, join, sep } from 'node:path';
3
+ import { carrierHome } from '../config/default-path.js';
4
+ import { ChatRefused } from './runner.js';
5
+ import { adjectives, places, units } from './words.js';
6
+ import { inspectFolder } from './workspace.js';
7
+ /** Every worktree branch is `carrier/<name>`; a name is the part after this prefix. */
8
+ export const BRANCH_PREFIX = 'carrier/';
9
+ const MAX_NAME = 60;
10
+ const ROLLS = 50;
11
+ /** Why `name` cannot be one, in words, or null when it can: lowercase letters, digits, `-`, `_` and `.`, starting with a letter or a digit, at most 60 characters, and a valid git ref name. */
12
+ export function invalidReason(name) {
13
+ if (name === '')
14
+ return 'The name is empty';
15
+ if (name.length > MAX_NAME)
16
+ return `The name is longer than ${MAX_NAME} characters`;
17
+ if (/^[a-z0-9]/.test(name) === false)
18
+ return 'The name must start with a lowercase letter or a digit';
19
+ if (/^[a-z0-9._-]+$/.test(name) === false)
20
+ return 'The name may only have lowercase letters, digits, hyphens, underscores and dots';
21
+ if (name.includes('..'))
22
+ return 'The name cannot have two dots in a row';
23
+ if (name.endsWith('.'))
24
+ return 'The name cannot end with a dot';
25
+ if (name.endsWith('.lock'))
26
+ return 'The name cannot end with .lock';
27
+ return null;
28
+ }
29
+ /** `<adjective>-<unit>-<place>`, each word picked with `random` (a number in [0, 1), `Math.random` by default). */
30
+ export function generateName(random = Math.random) {
31
+ const pick = (words) => words[Math.floor(random() * words.length)];
32
+ return `${pick(adjectives)}-${pick(units)}-${pick(places)}`;
33
+ }
34
+ /** Where every session worktree lives. */
35
+ export const worktreesDir = () => join(carrierHome(), 'chat', 'worktrees');
36
+ /** Where a worktree called `name` of this repository lives: `<CARRIER_HOME>/chat/worktrees/<owner>/<repo>/<name>`, or `local/<folder name>` without a GitHub origin. A path that would lie anywhere else is refused. */
37
+ export function worktreePath(info, name) {
38
+ const [owner, repo] = info.repo === null ? ['local', basename(info.folder)] : [info.repo.owner, info.repo.name];
39
+ const path = join(worktreesDir(), owner, repo, name);
40
+ if (path.startsWith(worktreesDir() + sep) === false)
41
+ throw new ChatRefused('That worktree would not be inside Carrier\'s worktrees folder');
42
+ return path;
43
+ }
44
+ /** Whether something is at `path`. Only a path that cannot exist answers false; any other error is not a free name. */
45
+ const exists = (path) => {
46
+ try {
47
+ lstatSync(path);
48
+ return true;
49
+ }
50
+ catch (error) {
51
+ if (['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
52
+ return false;
53
+ throw error;
54
+ }
55
+ };
56
+ /** What a name is up against in a repository: the three places where `carrier/<name>` must not exist yet. */
57
+ export async function repository(git, folder) {
58
+ const info = await inspectFolder(git, folder);
59
+ if (info.git === false)
60
+ throw new ChatRefused('Worktrees need a git repository');
61
+ if (info.bare)
62
+ throw new ChatRefused('Worktrees need a git repository with a working folder, and this one is bare');
63
+ const has = (branch) => info.branches.some((candidate) => candidate.name === branch);
64
+ /** Why a valid name is taken, in words, or null when it is free. */
65
+ const takenBecause = (name) => {
66
+ if (has(`${BRANCH_PREFIX}${name}`))
67
+ return `A branch called ${BRANCH_PREFIX}${name} already exists`;
68
+ if (has(`origin/${BRANCH_PREFIX}${name}`))
69
+ return `The remote already has a branch called ${BRANCH_PREFIX}${name}`;
70
+ if (exists(worktreePath(info, name)))
71
+ return 'A folder for that worktree already exists';
72
+ return null;
73
+ };
74
+ /** A generated name that is free, rolled again while it is taken and never numbered. */
75
+ const freeName = (random = Math.random) => {
76
+ for (let roll = 0; roll < ROLLS; roll++) {
77
+ const name = generateName(random);
78
+ if (takenBecause(name) === null)
79
+ return name;
80
+ }
81
+ throw new ChatRefused(`Every name that was tried (${ROLLS}) is already taken; type one instead`);
82
+ };
83
+ return { info, takenBecause, freeName };
84
+ }
85
+ /** A free name for a worktree of `folder` (already checked, a real path). */
86
+ export async function suggestName(git, folder, random) {
87
+ return { name: (await repository(git, folder)).freeName(random) };
88
+ }
89
+ /** Whether a name typed for a worktree of `folder` (already checked, a real path) is valid and free, and why not when it is not. */
90
+ export async function checkName(git, folder, name) {
91
+ const { takenBecause } = await repository(git, folder);
92
+ const invalid = invalidReason(name);
93
+ if (invalid !== null)
94
+ return { valid: false, free: false, reason: invalid };
95
+ const taken = takenBecause(name);
96
+ return taken === null ? { valid: true, free: true } : { valid: true, free: false, reason: taken };
97
+ }
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+ import { repoNamePattern } from '../config/schema.js';
3
+ import { projectNamePattern } from './projects.js';
4
+ // Never a leading dash (it would be an option of git) nor whitespace.
5
+ const branch = z.string().min(1).max(1000).regex(/^[^-\s][^\s]*$/);
6
+ const workspace = z.discriminatedUnion('kind', [
7
+ z.strictObject({ kind: z.literal('folder'), folder: z.string().min(1) }),
8
+ z.strictObject({ kind: z.literal('repo'), repo: z.string().regex(repoNamePattern) }),
9
+ z.strictObject({ kind: z.literal('project'), name: z.string().regex(projectNamePattern) }),
10
+ ]);
11
+ /** What a new session is before it exists: where it works, which agent, and the first message. The host prepares it when it is sent. */
12
+ export const plan = z
13
+ .strictObject({
14
+ workspace,
15
+ agent: z.string().min(1),
16
+ options: z.record(z.string(), z.string()).optional(),
17
+ text: z.string().min(1),
18
+ branch: branch.optional(),
19
+ worktree: z.strictObject({ name: z.string().max(1000).optional() }).optional(),
20
+ })
21
+ .refine((p) => p.workspace.kind !== 'project' || (p.branch === undefined && p.worktree === undefined)); // a project has no commits