@markjaquith/agency 2.16.0 → 2.18.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.
- package/README.md +33 -4
- package/cli.ts +93 -8
- package/package.json +1 -1
- package/skills/agency/SKILL.md +13 -6
- package/src/cli-parser.test.ts +93 -0
- package/src/cli-parser.ts +229 -29
- package/src/cli.test.ts +144 -4
- package/src/commands/context.ts +3 -0
- package/src/commands/init.ts +3 -1
- package/src/commands/next.ts +64 -0
- package/src/commands/pr.ts +2 -0
- package/src/commands/read-only.test.ts +2 -0
- package/src/commands/validate.test.ts +2 -0
- package/src/commands/work.test.ts +85 -0
- package/src/commands/work.ts +40 -9
- package/src/commands/workbase.test.ts +63 -2
- package/src/commands/workbase.ts +77 -10
- package/src/protocol.ts +6 -0
- package/src/services/GraphService.test.ts +36 -0
- package/src/services/GraphService.ts +8 -1
- package/src/services/PullRequestService.test.ts +20 -1
- package/src/services/PullRequestService.ts +14 -1
- package/src/services/ReadinessService.test.ts +223 -0
- package/src/services/ReadinessService.ts +230 -0
- package/src/services/WorkbaseService.test.ts +147 -4
- package/src/services/WorkbaseService.ts +214 -12
- package/src/services/WorktreeService.test.ts +12 -0
- package/src/services/WorktreeService.ts +6 -2
- package/src/test-utils.ts +2 -0
- package/src/workbase/schemas.test.ts +17 -6
- package/src/workbase/schemas.ts +16 -1
- package/src/workbase/workbase-choice.ts +2 -0
|
@@ -6,7 +6,9 @@ import { dirname, join, relative, resolve } from "node:path"
|
|
|
6
6
|
import { FileSystemService } from "./FileSystemService"
|
|
7
7
|
import { parseFrontmatter } from "../workbase/frontmatter"
|
|
8
8
|
import {
|
|
9
|
+
EntityId,
|
|
9
10
|
EpicFrontmatter,
|
|
11
|
+
LegacyWorkbaseRegistry,
|
|
10
12
|
PhaseFrontmatter,
|
|
11
13
|
TaskFrontmatter,
|
|
12
14
|
WorkbaseConfig,
|
|
@@ -15,6 +17,8 @@ import {
|
|
|
15
17
|
type EpicFrontmatter as EpicData,
|
|
16
18
|
type PhaseFrontmatter as PhaseData,
|
|
17
19
|
type TaskFrontmatter as TaskData,
|
|
20
|
+
type WorkbaseRegistry as WorkbaseRegistryData,
|
|
21
|
+
type WorkbaseRegistration,
|
|
18
22
|
} from "../workbase/schemas"
|
|
19
23
|
import { validateWorktreeCreateCommand } from "../workbase/worktree-command"
|
|
20
24
|
|
|
@@ -81,12 +85,17 @@ const registryPath = (configDirectory?: string) =>
|
|
|
81
85
|
"workbases.json",
|
|
82
86
|
)
|
|
83
87
|
|
|
88
|
+
const registrationId = (path: string) =>
|
|
89
|
+
`wb-${new Bun.CryptoHasher("sha256").update(path).digest("hex").slice(0, 12)}`
|
|
90
|
+
|
|
91
|
+
const emptyRegistry: WorkbaseRegistryData = { version: 2, workbases: [] }
|
|
92
|
+
|
|
84
93
|
const readRegistry = (configDirectory?: string) =>
|
|
85
94
|
Effect.gen(function* () {
|
|
86
95
|
const fs = yield* FileSystemService
|
|
87
96
|
const path = registryPath(configDirectory)
|
|
88
97
|
if (!(yield* fs.exists(path))) {
|
|
89
|
-
return { path, registry:
|
|
98
|
+
return { path, registry: emptyRegistry }
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
const content = yield* fs.readFile(path)
|
|
@@ -102,13 +111,37 @@ const readRegistry = (configDirectory?: string) =>
|
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
const decoded = decode(WorkbaseRegistry, input)
|
|
105
|
-
if (
|
|
114
|
+
if (decoded.success) return { path, registry: decoded.value }
|
|
115
|
+
|
|
116
|
+
const legacy = decode(LegacyWorkbaseRegistry, input)
|
|
117
|
+
if (!legacy.success) {
|
|
106
118
|
return yield* new WorkbaseRegistryError({
|
|
107
119
|
path,
|
|
108
120
|
message: `Invalid workbase registry in ${path}:\n${decoded.error}`,
|
|
109
121
|
})
|
|
110
122
|
}
|
|
111
|
-
|
|
123
|
+
const registry: WorkbaseRegistryData = {
|
|
124
|
+
version: 2 as const,
|
|
125
|
+
workbases: legacy.value.workbases.map((workbasePath) => ({
|
|
126
|
+
id: registrationId(workbasePath),
|
|
127
|
+
path: workbasePath,
|
|
128
|
+
})),
|
|
129
|
+
}
|
|
130
|
+
return { path, registry }
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const writeRegistry = (
|
|
134
|
+
path: string,
|
|
135
|
+
registry: {
|
|
136
|
+
readonly version: 2
|
|
137
|
+
readonly workbases: readonly WorkbaseRegistration[]
|
|
138
|
+
readonly defaultId?: string
|
|
139
|
+
},
|
|
140
|
+
) =>
|
|
141
|
+
Effect.gen(function* () {
|
|
142
|
+
const fs = yield* FileSystemService
|
|
143
|
+
yield* fs.createDirectory(dirname(path))
|
|
144
|
+
yield* fs.writeJSON(path, registry)
|
|
112
145
|
})
|
|
113
146
|
|
|
114
147
|
const findCycles = (nodes: readonly Dependency[]): readonly string[] => {
|
|
@@ -285,26 +318,195 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
|
285
318
|
return { root, config: decoded.value }
|
|
286
319
|
}),
|
|
287
320
|
|
|
288
|
-
register: (startPath: string, configDirectory?: string) =>
|
|
321
|
+
register: (startPath: string, configDirectory?: string, name?: string) =>
|
|
289
322
|
Effect.gen(function* () {
|
|
290
323
|
const service = yield* WorkbaseService
|
|
291
324
|
const fs = yield* FileSystemService
|
|
292
325
|
const discovered = yield* service.discover(startPath)
|
|
293
326
|
const root = yield* fs.realPath(discovered)
|
|
294
327
|
const { path, registry } = yield* readRegistry(configDirectory)
|
|
295
|
-
if (
|
|
328
|
+
if (name !== undefined) {
|
|
329
|
+
const decodedName = decode(EntityId, name)
|
|
330
|
+
if (!decodedName.success) {
|
|
331
|
+
return yield* new WorkbaseRegistryError({
|
|
332
|
+
path,
|
|
333
|
+
message: `Invalid workbase name '${name}': names must contain only letters, numbers, dots, underscores, and hyphens`,
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const named = name
|
|
338
|
+
? registry.workbases.find(
|
|
339
|
+
(entry) =>
|
|
340
|
+
(entry.name === name && entry.path !== root) ||
|
|
341
|
+
entry.id === name,
|
|
342
|
+
)
|
|
343
|
+
: undefined
|
|
344
|
+
if (named) {
|
|
345
|
+
return yield* new WorkbaseRegistryError({
|
|
346
|
+
path,
|
|
347
|
+
message: `Workbase name '${name}' is already registered for ${named.path}`,
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
const existing = registry.workbases.find(
|
|
351
|
+
(entry) => entry.path === root,
|
|
352
|
+
)
|
|
353
|
+
const registration = {
|
|
354
|
+
id: existing?.id ?? registrationId(root),
|
|
355
|
+
...(name
|
|
356
|
+
? { name }
|
|
357
|
+
: existing?.name
|
|
358
|
+
? { name: existing.name }
|
|
359
|
+
: {}),
|
|
360
|
+
path: root,
|
|
361
|
+
}
|
|
362
|
+
const idNameCollision = registry.workbases.find(
|
|
363
|
+
(entry) => entry.name === registration.id && entry.path !== root,
|
|
364
|
+
)
|
|
365
|
+
if (idNameCollision) {
|
|
366
|
+
return yield* new WorkbaseRegistryError({
|
|
367
|
+
path,
|
|
368
|
+
message: `Workbase ID '${registration.id}' conflicts with the registered name for ${idNameCollision.path}`,
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
const workbases = existing
|
|
372
|
+
? registry.workbases.map((entry) =>
|
|
373
|
+
entry.id === existing.id ? registration : entry,
|
|
374
|
+
)
|
|
375
|
+
: [...registry.workbases, registration]
|
|
376
|
+
yield* writeRegistry(path, { ...registry, workbases })
|
|
377
|
+
return registration
|
|
378
|
+
}),
|
|
379
|
+
|
|
380
|
+
listRegistered: (configDirectory?: string) =>
|
|
381
|
+
readRegistry(configDirectory).pipe(
|
|
382
|
+
Effect.map(({ registry }) =>
|
|
383
|
+
registry.workbases.map((entry) => entry.path),
|
|
384
|
+
),
|
|
385
|
+
),
|
|
386
|
+
|
|
387
|
+
listRegistrations: (configDirectory?: string) =>
|
|
388
|
+
readRegistry(configDirectory).pipe(
|
|
389
|
+
Effect.map(({ registry }) => ({
|
|
390
|
+
workbases: registry.workbases,
|
|
391
|
+
defaultId: registry.defaultId,
|
|
392
|
+
})),
|
|
393
|
+
),
|
|
394
|
+
|
|
395
|
+
resolveRegistered: (
|
|
396
|
+
selector: string,
|
|
397
|
+
configDirectory?: string,
|
|
398
|
+
basePath: string = process.cwd(),
|
|
399
|
+
) =>
|
|
400
|
+
Effect.gen(function* () {
|
|
401
|
+
const service = yield* WorkbaseService
|
|
402
|
+
const fs = yield* FileSystemService
|
|
403
|
+
const { path, registry } = yield* readRegistry(configDirectory)
|
|
404
|
+
const candidatePath = resolve(basePath, selector)
|
|
405
|
+
const direct =
|
|
406
|
+
registry.workbases.find((entry) => entry.id === selector) ??
|
|
407
|
+
registry.workbases.find((entry) => entry.name === selector) ??
|
|
408
|
+
registry.workbases.find((entry) => entry.path === candidatePath)
|
|
409
|
+
if (direct) return direct.path
|
|
410
|
+
if (yield* fs.exists(candidatePath))
|
|
411
|
+
return yield* service.discover(candidatePath)
|
|
412
|
+
return yield* new WorkbaseRegistryError({
|
|
413
|
+
path,
|
|
414
|
+
message: `Unknown workbase selector '${selector}'`,
|
|
415
|
+
})
|
|
416
|
+
}),
|
|
417
|
+
|
|
418
|
+
removeRegistered: (
|
|
419
|
+
selector: string,
|
|
420
|
+
configDirectory?: string,
|
|
421
|
+
basePath: string = process.cwd(),
|
|
422
|
+
) =>
|
|
423
|
+
Effect.gen(function* () {
|
|
424
|
+
const fs = yield* FileSystemService
|
|
425
|
+
const { path, registry } = yield* readRegistry(configDirectory)
|
|
426
|
+
const candidatePath = resolve(basePath, selector)
|
|
427
|
+
const canonicalCandidate = (yield* fs.exists(candidatePath))
|
|
428
|
+
? yield* fs.realPath(candidatePath)
|
|
429
|
+
: candidatePath
|
|
430
|
+
const entry =
|
|
431
|
+
registry.workbases.find((item) => item.id === selector) ??
|
|
432
|
+
registry.workbases.find((item) => item.name === selector) ??
|
|
433
|
+
registry.workbases.find((item) => item.path === canonicalCandidate)
|
|
434
|
+
if (!entry) {
|
|
435
|
+
return yield* new WorkbaseRegistryError({
|
|
436
|
+
path,
|
|
437
|
+
message: `Unknown workbase selector '${selector}'`,
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
const workbases = registry.workbases.filter(
|
|
441
|
+
(item) => item.id !== entry.id,
|
|
442
|
+
)
|
|
443
|
+
const next = {
|
|
444
|
+
version: 2 as const,
|
|
445
|
+
workbases,
|
|
446
|
+
...(registry.defaultId && registry.defaultId !== entry.id
|
|
447
|
+
? { defaultId: registry.defaultId }
|
|
448
|
+
: {}),
|
|
449
|
+
}
|
|
450
|
+
yield* writeRegistry(path, next)
|
|
451
|
+
return entry
|
|
452
|
+
}),
|
|
296
453
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
454
|
+
pruneRegistered: (configDirectory?: string) =>
|
|
455
|
+
Effect.gen(function* () {
|
|
456
|
+
const fs = yield* FileSystemService
|
|
457
|
+
const { path, registry } = yield* readRegistry(configDirectory)
|
|
458
|
+
const kept: WorkbaseRegistration[] = []
|
|
459
|
+
const removed: WorkbaseRegistration[] = []
|
|
460
|
+
for (const entry of registry.workbases) {
|
|
461
|
+
if (yield* fs.exists(join(entry.path, "agency.json")))
|
|
462
|
+
kept.push(entry)
|
|
463
|
+
else removed.push(entry)
|
|
464
|
+
}
|
|
465
|
+
const defaultId = kept.some(
|
|
466
|
+
(entry) => entry.id === registry.defaultId,
|
|
467
|
+
)
|
|
468
|
+
? registry.defaultId
|
|
469
|
+
: undefined
|
|
470
|
+
yield* writeRegistry(path, {
|
|
471
|
+
version: 2,
|
|
472
|
+
workbases: kept,
|
|
473
|
+
...(defaultId ? { defaultId } : {}),
|
|
301
474
|
})
|
|
302
|
-
return
|
|
475
|
+
return removed
|
|
303
476
|
}),
|
|
304
477
|
|
|
305
|
-
|
|
478
|
+
setDefault: (selector: string | null, configDirectory?: string) =>
|
|
479
|
+
Effect.gen(function* () {
|
|
480
|
+
const { path, registry } = yield* readRegistry(configDirectory)
|
|
481
|
+
if (selector === null) {
|
|
482
|
+
yield* writeRegistry(path, {
|
|
483
|
+
version: 2,
|
|
484
|
+
workbases: registry.workbases,
|
|
485
|
+
})
|
|
486
|
+
return null
|
|
487
|
+
}
|
|
488
|
+
const entry = registry.workbases.find(
|
|
489
|
+
(item) => item.id === selector || item.name === selector,
|
|
490
|
+
)
|
|
491
|
+
if (!entry) {
|
|
492
|
+
return yield* new WorkbaseRegistryError({
|
|
493
|
+
path,
|
|
494
|
+
message: `Unknown registered workbase selector '${selector}'`,
|
|
495
|
+
})
|
|
496
|
+
}
|
|
497
|
+
yield* writeRegistry(path, {
|
|
498
|
+
version: 2,
|
|
499
|
+
workbases: registry.workbases,
|
|
500
|
+
defaultId: entry.id,
|
|
501
|
+
})
|
|
502
|
+
return entry
|
|
503
|
+
}),
|
|
504
|
+
|
|
505
|
+
getDefault: (configDirectory?: string) =>
|
|
306
506
|
readRegistry(configDirectory).pipe(
|
|
307
|
-
Effect.map(({ registry }) =>
|
|
507
|
+
Effect.map(({ registry }) =>
|
|
508
|
+
registry.workbases.find((entry) => entry.id === registry.defaultId),
|
|
509
|
+
),
|
|
308
510
|
),
|
|
309
511
|
|
|
310
512
|
validate: (startPath: string = process.cwd()) =>
|
|
@@ -206,6 +206,18 @@ describe("WorktreeService", () => {
|
|
|
206
206
|
expect(
|
|
207
207
|
await Bun.file(join(root, "tasks", "valid-target", "code")).exists(),
|
|
208
208
|
).toBe(false)
|
|
209
|
+
|
|
210
|
+
await expect(
|
|
211
|
+
runTestEffect(
|
|
212
|
+
WorktreeService.pipe(
|
|
213
|
+
Effect.flatMap((service) =>
|
|
214
|
+
service.materialize("valid-target", undefined, root, {
|
|
215
|
+
force: true,
|
|
216
|
+
}),
|
|
217
|
+
),
|
|
218
|
+
),
|
|
219
|
+
),
|
|
220
|
+
).resolves.toMatchObject({ repo: "agency" })
|
|
209
221
|
})
|
|
210
222
|
|
|
211
223
|
test("uses a configured worktree creation command", async () => {
|
|
@@ -84,6 +84,10 @@ const isCommitId = (ref: string) => /^[0-9a-f]{40,64}$/i.test(ref)
|
|
|
84
84
|
const originRef = (ref: string) =>
|
|
85
85
|
ref.replace(/^refs\/remotes\/origin\//, "").replace(/^origin\//, "")
|
|
86
86
|
|
|
87
|
+
interface MaterializeOptions extends BaseCommandOptions {
|
|
88
|
+
readonly force?: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
88
92
|
"WorktreeService",
|
|
89
93
|
{
|
|
@@ -92,7 +96,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
92
96
|
taskId: string,
|
|
93
97
|
phaseId?: string,
|
|
94
98
|
startPath: string = process.cwd(),
|
|
95
|
-
options:
|
|
99
|
+
options: MaterializeOptions = {},
|
|
96
100
|
) =>
|
|
97
101
|
Effect.gen(function* () {
|
|
98
102
|
const fs = yield* FileSystemService
|
|
@@ -105,7 +109,7 @@ export class WorktreeService extends Effect.Service<WorktreeService>()(
|
|
|
105
109
|
const { root, config } = yield* workbase.loadConfig(startPath)
|
|
106
110
|
const report = yield* workbase.validate(root)
|
|
107
111
|
const validationIssue = report.issues[0]
|
|
108
|
-
if (validationIssue) {
|
|
112
|
+
if (validationIssue && !options.force) {
|
|
109
113
|
return yield* new WorktreeError({
|
|
110
114
|
message: `${validationIssue.path}: ${validationIssue.message}`,
|
|
111
115
|
})
|
package/src/test-utils.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { ContextService } from "./services/ContextService"
|
|
|
17
17
|
import { GraphService } from "./services/GraphService"
|
|
18
18
|
import { ClaimService } from "./services/ClaimService"
|
|
19
19
|
import { SyncService } from "./services/SyncService"
|
|
20
|
+
import { ReadinessService } from "./services/ReadinessService"
|
|
20
21
|
|
|
21
22
|
export const createTempDir = () => mkdtemp(join(tmpdir(), "agency-test-"))
|
|
22
23
|
|
|
@@ -38,6 +39,7 @@ const TestLayer = Layer.mergeAll(
|
|
|
38
39
|
GraphService.Default,
|
|
39
40
|
ClaimService.Default,
|
|
40
41
|
SyncService.Default,
|
|
42
|
+
ReadinessService.Default,
|
|
41
43
|
)
|
|
42
44
|
|
|
43
45
|
export async function runTestEffect<A, E>(
|
|
@@ -185,23 +185,34 @@ describe("workbase registry", () => {
|
|
|
185
185
|
test("accepts registered paths", () => {
|
|
186
186
|
expect(
|
|
187
187
|
Schema.decodeUnknownSync(WorkbaseRegistry)({
|
|
188
|
-
version:
|
|
189
|
-
workbases: [
|
|
188
|
+
version: 2,
|
|
189
|
+
workbases: [
|
|
190
|
+
{ id: "wb-one", name: "one", path: "/work/one" },
|
|
191
|
+
{ id: "wb-two", path: "/work/two" },
|
|
192
|
+
],
|
|
193
|
+
defaultId: "wb-one",
|
|
190
194
|
}),
|
|
191
|
-
).toEqual({
|
|
195
|
+
).toEqual({
|
|
196
|
+
version: 2,
|
|
197
|
+
workbases: [
|
|
198
|
+
{ id: "wb-one", name: "one", path: "/work/one" },
|
|
199
|
+
{ id: "wb-two", path: "/work/two" },
|
|
200
|
+
],
|
|
201
|
+
defaultId: "wb-one",
|
|
202
|
+
})
|
|
192
203
|
})
|
|
193
204
|
|
|
194
205
|
test("rejects invalid versions and empty paths", () => {
|
|
195
206
|
expect(() =>
|
|
196
207
|
Schema.decodeUnknownSync(WorkbaseRegistry)({
|
|
197
|
-
version:
|
|
208
|
+
version: 3,
|
|
198
209
|
workbases: [],
|
|
199
210
|
}),
|
|
200
211
|
).toThrow()
|
|
201
212
|
expect(() =>
|
|
202
213
|
Schema.decodeUnknownSync(WorkbaseRegistry)({
|
|
203
|
-
version:
|
|
204
|
-
workbases: [""],
|
|
214
|
+
version: 2,
|
|
215
|
+
workbases: [{ id: "wb-one", path: "" }],
|
|
205
216
|
}),
|
|
206
217
|
).toThrow()
|
|
207
218
|
})
|
package/src/workbase/schemas.ts
CHANGED
|
@@ -54,11 +54,23 @@ export const WorkbaseConfig = Schema.Struct({
|
|
|
54
54
|
worktreeCreateCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)),
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
-
export const
|
|
57
|
+
export const LegacyWorkbaseRegistry = Schema.Struct({
|
|
58
58
|
version: Schema.Literal(1),
|
|
59
59
|
workbases: Schema.Array(NonEmptyString),
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
+
export const WorkbaseRegistration = Schema.Struct({
|
|
63
|
+
id: EntityId,
|
|
64
|
+
name: Schema.optional(EntityId),
|
|
65
|
+
path: NonEmptyString,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
export const WorkbaseRegistry = Schema.Struct({
|
|
69
|
+
version: Schema.Literal(2),
|
|
70
|
+
workbases: Schema.Array(WorkbaseRegistration),
|
|
71
|
+
defaultId: Schema.optional(EntityId),
|
|
72
|
+
})
|
|
73
|
+
|
|
62
74
|
export const Dependency = Schema.Struct({
|
|
63
75
|
id: EntityId,
|
|
64
76
|
dependsOn: Schema.optional(Schema.Array(EntityId)),
|
|
@@ -107,6 +119,9 @@ export const PhaseFrontmatter = Schema.Struct({
|
|
|
107
119
|
|
|
108
120
|
export type WorkbaseConfig = Schema.Schema.Type<typeof WorkbaseConfig>
|
|
109
121
|
export type WorkbaseRegistry = Schema.Schema.Type<typeof WorkbaseRegistry>
|
|
122
|
+
export type WorkbaseRegistration = Schema.Schema.Type<
|
|
123
|
+
typeof WorkbaseRegistration
|
|
124
|
+
>
|
|
110
125
|
export type Dependency = Schema.Schema.Type<typeof Dependency>
|
|
111
126
|
export type RepositoryReference = Schema.Schema.Type<typeof RepositoryReference>
|
|
112
127
|
export type WorkStatus = Schema.Schema.Type<typeof WorkStatus>
|
|
@@ -30,6 +30,8 @@ export const resolveWorkbase = (
|
|
|
30
30
|
return yield* workbase.discover(startPath).pipe(
|
|
31
31
|
Effect.catchTag("WorkbaseNotFoundError", () =>
|
|
32
32
|
Effect.gen(function* () {
|
|
33
|
+
const defaultWorkbase = yield* workbase.getDefault()
|
|
34
|
+
if (defaultWorkbase) return defaultWorkbase.path
|
|
33
35
|
const registered = yield* workbase.listRegistered()
|
|
34
36
|
if (registered.length === 0) {
|
|
35
37
|
return yield* Effect.fail(
|