@markjaquith/agency 2.73.2 → 3.0.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 +35 -134
- package/cli-main.ts +0 -26
- package/cli.ts +1 -17
- package/package.json +1 -2
- package/schemas/agency-graph-v1.schema.json +1 -1
- package/src/cli-parser.test.ts +0 -19
- package/src/cli-parser.ts +0 -23
- package/src/cli.test.ts +3 -3
- package/src/commands/context.test.ts +11 -71
- package/src/commands/context.ts +7 -7
- package/src/commands/init.test.ts +1 -2
- package/src/commands/pr.test.ts +2 -87
- package/src/commands/pr.ts +2 -79
- package/src/commands/push.test.ts +1 -2
- package/src/graph-schema.ts +1 -1
- package/src/services/ArchiveBulkService.test.ts +1 -142
- package/src/services/ArchiveService.test.ts +1 -172
- package/src/services/ArchiveService.ts +0 -26
- package/src/services/ContextService.ts +7 -50
- package/src/services/DoctorService.ts +8 -24
- package/src/services/GraphService.test.ts +1 -0
- package/src/services/GraphService.ts +2 -5
- package/src/services/IntegrationService.test.ts +4 -0
- package/src/services/PhaseService.ts +70 -191
- package/src/services/PullRequestService.test.ts +1 -45
- package/src/services/PullRequestService.ts +1 -22
- package/src/services/PushService.test.ts +29 -295
- package/src/services/PushService.ts +10 -239
- package/src/services/RepositoryService.test.ts +1 -61
- package/src/services/RepositoryService.ts +2 -13
- package/src/services/ReviewService.test.ts +1 -84
- package/src/services/ReviewService.ts +9 -48
- package/src/services/SyncService.test.ts +0 -81
- package/src/services/SyncService.ts +12 -39
- package/src/services/TaskPhaseService.test.ts +0 -80
- package/src/services/TaskService.ts +1 -9
- package/src/services/VersionControlService.test.ts +4 -117
- package/src/services/VersionControlService.ts +3 -426
- package/src/services/WorkbaseService.test.ts +0 -20
- package/src/services/WorkbaseService.ts +1 -18
- package/src/services/WorktreeService.test.ts +1 -705
- package/src/services/WorktreeService.ts +395 -1604
- package/src/services/push-validation.ts +0 -7
- package/src/test-utils.ts +0 -4
- package/src/workbase/AGENTS.md +7 -7
- package/src/workbase/checkout-command.test.ts +1 -1
- package/src/workbase/checkout-command.ts +1 -1
- package/src/workbase/delivery-command.test.ts +4 -35
- package/src/workbase/delivery-command.ts +2 -15
- package/src/workbase/schemas.test.ts +0 -19
- package/src/workbase/schemas.ts +1 -2
- package/src/commands/vcs.test.ts +0 -75
- package/src/commands/vcs.ts +0 -72
- package/src/services/VcsMigrationService.test.ts +0 -348
- package/src/services/VcsMigrationService.ts +0 -857
- package/src/vcs-status-fast.ts +0 -310
- package/src/workbase/version-control.ts +0 -5
- package/src/workbase/workspace-command.test.ts +0 -70
- package/src/workbase/workspace-command.ts +0 -63
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
} from "./WorktreeService"
|
|
14
14
|
import {
|
|
15
15
|
GitVersionControlService,
|
|
16
|
-
JjVersionControlService,
|
|
17
16
|
VersionControlService,
|
|
18
17
|
} from "./VersionControlService"
|
|
19
18
|
import {
|
|
@@ -368,7 +367,6 @@ const WorktreeLayer = Layer.mergeAll(
|
|
|
368
367
|
FileSystemService.Default,
|
|
369
368
|
WorkbaseService.Default,
|
|
370
369
|
GitVersionControlService.Default,
|
|
371
|
-
JjVersionControlService.Default,
|
|
372
370
|
VersionControlService.Default,
|
|
373
371
|
TaskService.Default,
|
|
374
372
|
PhaseService.Default,
|
|
@@ -400,22 +398,6 @@ const restoreWorktreeSnapshots = async (
|
|
|
400
398
|
continue
|
|
401
399
|
} catch {}
|
|
402
400
|
await mkdir(dirname(snapshot.path), { recursive: true })
|
|
403
|
-
if (snapshot.vcs === "jj") {
|
|
404
|
-
await runGit([
|
|
405
|
-
"jj",
|
|
406
|
-
"-R",
|
|
407
|
-
snapshot.repositoryPath,
|
|
408
|
-
"workspace",
|
|
409
|
-
"add",
|
|
410
|
-
"--name",
|
|
411
|
-
snapshot.workspaceName!,
|
|
412
|
-
"-r",
|
|
413
|
-
snapshot.head,
|
|
414
|
-
snapshot.path,
|
|
415
|
-
])
|
|
416
|
-
await runGit(["jj", "-R", snapshot.path, "edit", snapshot.head])
|
|
417
|
-
continue
|
|
418
|
-
}
|
|
419
401
|
await runGit(
|
|
420
402
|
snapshot.branch
|
|
421
403
|
? [
|
|
@@ -724,7 +706,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
724
706
|
removedWorktrees.push(
|
|
725
707
|
...(yield* worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
726
708
|
dryRun: true,
|
|
727
|
-
persistResume: false,
|
|
728
709
|
})),
|
|
729
710
|
)
|
|
730
711
|
}
|
|
@@ -783,7 +764,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
783
764
|
worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
784
765
|
snapshots,
|
|
785
766
|
lockHeld: true,
|
|
786
|
-
persistResume: false,
|
|
787
767
|
}),
|
|
788
768
|
)
|
|
789
769
|
} catch (cause) {
|
|
@@ -897,7 +877,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
897
877
|
const result = yield* worktrees
|
|
898
878
|
.remove(unit.taskId, executionPhaseId(unit), root, {
|
|
899
879
|
dryRun: true,
|
|
900
|
-
persistResume: false,
|
|
901
880
|
task: context.task,
|
|
902
881
|
phase: unit.phaseId
|
|
903
882
|
? context.phases.find((phase) => phase.id === unit.phaseId)
|
|
@@ -1075,7 +1054,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1075
1054
|
worktrees.remove(unit.taskId, unit.phaseId, root, {
|
|
1076
1055
|
snapshots,
|
|
1077
1056
|
lockHeld: true,
|
|
1078
|
-
persistResume: false,
|
|
1079
1057
|
task: contexts.get(unit.taskId)!.task,
|
|
1080
1058
|
phase: unit.phaseId
|
|
1081
1059
|
? contexts
|
|
@@ -1232,7 +1210,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1232
1210
|
root,
|
|
1233
1211
|
{
|
|
1234
1212
|
dryRun: true,
|
|
1235
|
-
persistResume: false,
|
|
1236
1213
|
},
|
|
1237
1214
|
)),
|
|
1238
1215
|
)
|
|
@@ -1288,7 +1265,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1288
1265
|
{
|
|
1289
1266
|
snapshots,
|
|
1290
1267
|
lockHeld: true,
|
|
1291
|
-
persistResume: false,
|
|
1292
1268
|
},
|
|
1293
1269
|
),
|
|
1294
1270
|
)
|
|
@@ -1381,7 +1357,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1381
1357
|
yield* rejectExistingDestination(destination, "Archive")
|
|
1382
1358
|
const removedWorktrees = yield* worktrees.remove(taskId, id, root, {
|
|
1383
1359
|
dryRun: true,
|
|
1384
|
-
persistResume: false,
|
|
1385
1360
|
})
|
|
1386
1361
|
const at = new Date().toISOString()
|
|
1387
1362
|
const content = yield* declarationContent(task, {
|
|
@@ -1430,7 +1405,6 @@ export class ArchiveService extends Effect.Service<ArchiveService>()(
|
|
|
1430
1405
|
worktrees.remove(taskId, id, root, {
|
|
1431
1406
|
snapshots,
|
|
1432
1407
|
lockHeld: true,
|
|
1433
|
-
persistResume: false,
|
|
1434
1408
|
}),
|
|
1435
1409
|
)
|
|
1436
1410
|
},
|
|
@@ -350,7 +350,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
350
350
|
root,
|
|
351
351
|
configPath: join(root, "agency.json"),
|
|
352
352
|
version: config.version,
|
|
353
|
-
vcs:
|
|
353
|
+
vcs: "git",
|
|
354
354
|
},
|
|
355
355
|
target: { kind: "workbase", path: root },
|
|
356
356
|
hint: compact
|
|
@@ -1025,51 +1025,9 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1025
1025
|
const inspectCheckout = (
|
|
1026
1026
|
repositoryPath: string,
|
|
1027
1027
|
checkoutPath: string,
|
|
1028
|
-
expectedBranch: string | null = null,
|
|
1029
|
-
branchCommit: string | null = null,
|
|
1030
1028
|
) =>
|
|
1031
1029
|
Effect.gen(function* (): Generator<any, CheckoutInspection, any> {
|
|
1032
1030
|
const materialized = yield* fs.isDirectory(checkoutPath)
|
|
1033
|
-
if (backend.kind === "jj") {
|
|
1034
|
-
const listed = yield* Effect.either(
|
|
1035
|
-
backend.listWorkspaces(repositoryPath),
|
|
1036
|
-
)
|
|
1037
|
-
const workspaces = Either.isRight(listed) ? listed.right : []
|
|
1038
|
-
if (Either.isLeft(listed)) {
|
|
1039
|
-
inspectionWarnings.push(
|
|
1040
|
-
`Unable to inspect jj workspace registrations for ${repositoryPath}`,
|
|
1041
|
-
)
|
|
1042
|
-
}
|
|
1043
|
-
const canonicalCheckoutPath = materialized
|
|
1044
|
-
? yield* fs.realPath(checkoutPath)
|
|
1045
|
-
: resolve(checkoutPath)
|
|
1046
|
-
const workspace = workspaces.find(
|
|
1047
|
-
(workspace) => workspace.path === canonicalCheckoutPath,
|
|
1048
|
-
)
|
|
1049
|
-
const registered = workspace !== undefined
|
|
1050
|
-
if (!materialized) {
|
|
1051
|
-
return {
|
|
1052
|
-
materialized: false,
|
|
1053
|
-
registered,
|
|
1054
|
-
checkoutCommit: null,
|
|
1055
|
-
checkoutBranch: null,
|
|
1056
|
-
detached: null,
|
|
1057
|
-
dirty: null,
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
return {
|
|
1061
|
-
materialized: true,
|
|
1062
|
-
registered,
|
|
1063
|
-
checkoutCommit: workspace?.commit ?? null,
|
|
1064
|
-
checkoutBranch:
|
|
1065
|
-
expectedBranch && branchCommit === workspace?.commit
|
|
1066
|
-
? expectedBranch
|
|
1067
|
-
: null,
|
|
1068
|
-
detached:
|
|
1069
|
-
!expectedBranch || branchCommit !== workspace?.commit,
|
|
1070
|
-
dirty: workspace ? false : null,
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
1031
|
const listed = yield* runGit(fs, repositoryPath, [
|
|
1074
1032
|
"worktree",
|
|
1075
1033
|
"list",
|
|
@@ -1136,7 +1094,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1136
1094
|
const repositoryPath =
|
|
1137
1095
|
repository?.path ?? join(root, "repos", executionData.repo)
|
|
1138
1096
|
const checkoutPath = join(codePath, executionData.repo)
|
|
1139
|
-
|
|
1097
|
+
const branchCommit = yield* backend.resolveRevision(
|
|
1140
1098
|
repositoryPath,
|
|
1141
1099
|
executionData.branch,
|
|
1142
1100
|
)
|
|
@@ -1147,8 +1105,6 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1147
1105
|
const checkout = yield* inspectCheckout(
|
|
1148
1106
|
repositoryPath,
|
|
1149
1107
|
checkoutPath,
|
|
1150
|
-
executionData.branch,
|
|
1151
|
-
branchCommit,
|
|
1152
1108
|
)
|
|
1153
1109
|
if (branchCommit === null) {
|
|
1154
1110
|
inspectionWarnings.push(
|
|
@@ -1239,9 +1195,10 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1239
1195
|
? "complete"
|
|
1240
1196
|
: "partial"
|
|
1241
1197
|
|
|
1198
|
+
const compact = !options.full
|
|
1242
1199
|
const projectDocument = <T>(document: Document<T> | null) =>
|
|
1243
1200
|
document
|
|
1244
|
-
?
|
|
1201
|
+
? compact
|
|
1245
1202
|
? {
|
|
1246
1203
|
id: document.id,
|
|
1247
1204
|
path: document.path,
|
|
@@ -1255,12 +1212,12 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1255
1212
|
(task?.data && "repo" in task.data ? task.data : null)
|
|
1256
1213
|
|
|
1257
1214
|
return {
|
|
1258
|
-
projection:
|
|
1215
|
+
projection: compact ? "compact" : "complete",
|
|
1259
1216
|
workbase: {
|
|
1260
1217
|
root,
|
|
1261
1218
|
configPath: join(root, "agency.json"),
|
|
1262
1219
|
version: config.version,
|
|
1263
|
-
vcs:
|
|
1220
|
+
vcs: "git",
|
|
1264
1221
|
},
|
|
1265
1222
|
target,
|
|
1266
1223
|
documents: {
|
|
@@ -1308,7 +1265,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
1308
1265
|
writable: writableDocuments,
|
|
1309
1266
|
},
|
|
1310
1267
|
},
|
|
1311
|
-
workspace:
|
|
1268
|
+
workspace: compact
|
|
1312
1269
|
? {
|
|
1313
1270
|
codePath,
|
|
1314
1271
|
materialization,
|
|
@@ -125,20 +125,12 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
125
125
|
return available
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"jj",
|
|
134
|
-
config.vcs === "jj" ? "error" : "optional",
|
|
135
|
-
"Jujutsu",
|
|
136
|
-
),
|
|
137
|
-
],
|
|
138
|
-
{ concurrency: "unbounded", batching: true },
|
|
128
|
+
const versionControlAvailable = yield* tool(
|
|
129
|
+
"tool.git",
|
|
130
|
+
"git",
|
|
131
|
+
"error",
|
|
132
|
+
"Git",
|
|
139
133
|
)
|
|
140
|
-
const versionControlAvailable =
|
|
141
|
-
gitAvailable && (config.vcs !== "jj" || jjAvailable)
|
|
142
134
|
yield* Effect.all(
|
|
143
135
|
[
|
|
144
136
|
tool(
|
|
@@ -180,15 +172,6 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
180
172
|
] as const,
|
|
181
173
|
]
|
|
182
174
|
: []),
|
|
183
|
-
...(config.workspaceCreateCommand
|
|
184
|
-
? [
|
|
185
|
-
[
|
|
186
|
-
"integration.workspace-create",
|
|
187
|
-
config.workspaceCreateCommand,
|
|
188
|
-
"Workspace creator",
|
|
189
|
-
] as const,
|
|
190
|
-
]
|
|
191
|
-
: []),
|
|
192
175
|
...Object.entries(config.repositories ?? {}).flatMap(
|
|
193
176
|
([alias, repository]) =>
|
|
194
177
|
repository.postCheckoutCommand
|
|
@@ -384,8 +367,9 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
384
367
|
category: "repository",
|
|
385
368
|
level: "warning",
|
|
386
369
|
status: "fail",
|
|
387
|
-
message:
|
|
388
|
-
|
|
370
|
+
message:
|
|
371
|
+
"Repository, ref, remote, and workspace checks were skipped because Git is unavailable",
|
|
372
|
+
remediation: "Install 'git' and rerun 'agency doctor'.",
|
|
389
373
|
})
|
|
390
374
|
}
|
|
391
375
|
for (const repository of repositoryList) {
|
|
@@ -121,6 +121,7 @@ describe("GraphService", () => {
|
|
|
121
121
|
const graph = await getGraph(root)
|
|
122
122
|
|
|
123
123
|
expect(Schema.decodeUnknownSync(AgencyGraph)(graph)).toEqual(graph)
|
|
124
|
+
expect(graph.workbase.vcs).toBe("git")
|
|
124
125
|
expect(graph.nodes.map((node) => node.id)).toEqual([
|
|
125
126
|
"epic:delivery",
|
|
126
127
|
"execution-unit:phase/ship/implement",
|
|
@@ -821,10 +821,7 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
821
821
|
checkoutCommit: materialized
|
|
822
822
|
? yield* backend.workspaceHead(checkoutPath)
|
|
823
823
|
: null,
|
|
824
|
-
checkoutBranch:
|
|
825
|
-
backend.kind === "jj" && materialized
|
|
826
|
-
? data.branch
|
|
827
|
-
: actualBranch,
|
|
824
|
+
checkoutBranch: actualBranch,
|
|
828
825
|
dirty: materialized
|
|
829
826
|
? yield* backend.workspaceDirty(checkoutPath)
|
|
830
827
|
: null,
|
|
@@ -1022,7 +1019,7 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
1022
1019
|
version: GRAPH_VERSION,
|
|
1023
1020
|
workbase: {
|
|
1024
1021
|
version: config.version,
|
|
1025
|
-
vcs:
|
|
1022
|
+
vcs: "git",
|
|
1026
1023
|
...(include.has("workspace") ? { root } : {}),
|
|
1027
1024
|
},
|
|
1028
1025
|
filters,
|
|
@@ -186,6 +186,10 @@ describe("IntegrationService", () => {
|
|
|
186
186
|
}
|
|
187
187
|
expect(managedWorkbaseAgents).toContain("agency push --json")
|
|
188
188
|
expect(managedWorkbaseAgents).toContain("agency-execution-v1")
|
|
189
|
+
expect(managedWorkbaseAgents).toContain("agency context . --full --json")
|
|
190
|
+
expect(managedWorkbaseAgents).toContain(
|
|
191
|
+
"Workers use full context here because they need the assigned document prose",
|
|
192
|
+
)
|
|
189
193
|
expect(managedWorkbaseAgents).toContain(
|
|
190
194
|
"Never pass `--work` or `--auto` to `agency task create`",
|
|
191
195
|
)
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
buildNonPrCompletion,
|
|
31
31
|
type NonPrCompletionInput,
|
|
32
32
|
} from "../workbase/completion"
|
|
33
|
-
import { VersionControlService } from "./VersionControlService"
|
|
34
33
|
|
|
35
34
|
class PhaseError extends Data.TaggedError("PhaseError")<{
|
|
36
35
|
readonly message: string
|
|
@@ -93,10 +92,8 @@ export class PhaseService extends Effect.Service<PhaseService>()(
|
|
|
93
92
|
Effect.gen(function* () {
|
|
94
93
|
const fs = yield* FileSystemService
|
|
95
94
|
const workbase = yield* WorkbaseService
|
|
96
|
-
const versionControl = yield* VersionControlService
|
|
97
95
|
const tasks = yield* TaskService
|
|
98
96
|
const root = yield* workbase.discover(startPath)
|
|
99
|
-
const backend = yield* versionControl.forWorkbase(root)
|
|
100
97
|
const taskId = yield* decodeId(input.taskId, "task")
|
|
101
98
|
const id = yield* decodeId(input.id, "phase")
|
|
102
99
|
const task = yield* tasks.show(taskId, root)
|
|
@@ -274,213 +271,95 @@ export class PhaseService extends Effect.Service<PhaseService>()(
|
|
|
274
271
|
firstData.repo,
|
|
275
272
|
...(firstData.repos ?? []).map((reference) => reference.repo),
|
|
276
273
|
]
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
alias
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
continue
|
|
308
|
-
}
|
|
309
|
-
const expected = await realpath(oldPath)
|
|
310
|
-
const registered = (
|
|
311
|
-
await runBackend(
|
|
312
|
-
backend.listWorkspaces(join(root, "repos", alias)),
|
|
313
|
-
)
|
|
314
|
-
).find((workspace) => workspace.path === expected)
|
|
315
|
-
if (!registered?.name)
|
|
316
|
-
throw new Error(
|
|
317
|
-
`Cannot convert task '${taskId}'; checkout '${alias}' is not registered as a jj workspace`,
|
|
318
|
-
)
|
|
319
|
-
if (await runBackend(backend.workspaceDirty(oldPath)))
|
|
320
|
-
throw new Error(
|
|
321
|
-
`Cannot convert task '${taskId}'; jj workspace '${alias}' has uncommitted changes`,
|
|
322
|
-
)
|
|
323
|
-
const head = await runBackend(
|
|
324
|
-
backend.workspaceHead(oldPath),
|
|
325
|
-
)
|
|
326
|
-
if (!head)
|
|
327
|
-
throw new Error(
|
|
328
|
-
`Cannot resolve jj workspace '${alias}' before conversion`,
|
|
329
|
-
)
|
|
330
|
-
workspaces.push({
|
|
331
|
-
alias,
|
|
332
|
-
name: registered.name,
|
|
333
|
-
head,
|
|
334
|
-
oldPath,
|
|
335
|
-
newPath: join(firstCodePath, alias),
|
|
336
|
-
})
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
apply: async () => {
|
|
340
|
-
await mkdir(firstCodePath, { recursive: true })
|
|
341
|
-
for (const workspace of workspaces) {
|
|
342
|
-
const repositoryPath = join(
|
|
343
|
-
root,
|
|
344
|
-
"repos",
|
|
345
|
-
workspace.alias,
|
|
346
|
-
)
|
|
347
|
-
await runBackend(
|
|
348
|
-
backend.removeWorkspace({
|
|
349
|
-
repositoryPath,
|
|
350
|
-
workspacePath: workspace.oldPath,
|
|
351
|
-
workspaceName: workspace.name,
|
|
352
|
-
}),
|
|
353
|
-
)
|
|
354
|
-
await runBackend(
|
|
355
|
-
backend.createWorkspace({
|
|
356
|
-
repositoryPath,
|
|
357
|
-
workspacePath: workspace.newPath,
|
|
358
|
-
workspaceName: workspace.name,
|
|
359
|
-
revision: workspace.head,
|
|
360
|
-
}),
|
|
361
|
-
)
|
|
362
|
-
}
|
|
363
|
-
await rm(oldCodePath, { recursive: true, force: true })
|
|
364
|
-
},
|
|
365
|
-
rollback: async () => {
|
|
366
|
-
await mkdir(oldCodePath, { recursive: true })
|
|
367
|
-
for (const workspace of [...workspaces].reverse()) {
|
|
368
|
-
const repositoryPath = join(
|
|
369
|
-
root,
|
|
370
|
-
"repos",
|
|
371
|
-
workspace.alias,
|
|
372
|
-
)
|
|
373
|
-
await runBackend(
|
|
374
|
-
backend.removeWorkspace({
|
|
375
|
-
repositoryPath,
|
|
376
|
-
workspacePath: workspace.newPath,
|
|
377
|
-
workspaceName: workspace.name,
|
|
378
|
-
}),
|
|
379
|
-
)
|
|
380
|
-
await runBackend(
|
|
381
|
-
backend.createWorkspace({
|
|
382
|
-
repositoryPath,
|
|
383
|
-
workspacePath: workspace.oldPath,
|
|
384
|
-
workspaceName: workspace.name,
|
|
385
|
-
revision: workspace.head,
|
|
386
|
-
}),
|
|
274
|
+
const repair = async (basePath: string) => {
|
|
275
|
+
for (const alias of checkoutAliases) {
|
|
276
|
+
const checkoutPath = join(basePath, alias)
|
|
277
|
+
try {
|
|
278
|
+
await lstat(checkoutPath)
|
|
279
|
+
} catch {
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
const result = Bun.spawnSync([
|
|
283
|
+
"git",
|
|
284
|
+
"-C",
|
|
285
|
+
join(root, "repos", alias),
|
|
286
|
+
"worktree",
|
|
287
|
+
"repair",
|
|
288
|
+
checkoutPath,
|
|
289
|
+
])
|
|
290
|
+
if (result.exitCode !== 0) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Failed to repair moved worktree for '${alias}': ${new TextDecoder().decode(result.stderr)}`,
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
steps.push({
|
|
298
|
+
label: `move and repair code for ${taskId}/${firstPhaseId}`,
|
|
299
|
+
preflight: async () => {
|
|
300
|
+
for (const entry of await readdir(oldCodePath)) {
|
|
301
|
+
if (!checkoutAliases.includes(entry))
|
|
302
|
+
throw new Error(
|
|
303
|
+
`Cannot convert task '${taskId}'; code contains unmanaged entry '${entry}'`,
|
|
387
304
|
)
|
|
388
|
-
|
|
389
|
-
await rm(firstDirectory, { recursive: true, force: true })
|
|
390
|
-
},
|
|
391
|
-
manualRecovery: `Restore jj workspaces from ${firstCodePath} to ${oldCodePath}`,
|
|
392
|
-
})
|
|
393
|
-
} else {
|
|
394
|
-
const repair = async (basePath: string) => {
|
|
305
|
+
}
|
|
395
306
|
for (const alias of checkoutAliases) {
|
|
396
|
-
const checkoutPath = join(
|
|
307
|
+
const checkoutPath = join(oldCodePath, alias)
|
|
397
308
|
try {
|
|
398
309
|
await lstat(checkoutPath)
|
|
399
310
|
} catch {
|
|
400
311
|
continue
|
|
401
312
|
}
|
|
402
|
-
const
|
|
313
|
+
const listed = Bun.spawnSync([
|
|
403
314
|
"git",
|
|
404
315
|
"-C",
|
|
405
316
|
join(root, "repos", alias),
|
|
406
317
|
"worktree",
|
|
407
|
-
"
|
|
408
|
-
|
|
318
|
+
"list",
|
|
319
|
+
"--porcelain",
|
|
409
320
|
])
|
|
410
|
-
if (
|
|
321
|
+
if (listed.exitCode !== 0)
|
|
411
322
|
throw new Error(
|
|
412
|
-
`Failed to
|
|
323
|
+
`Failed to inspect worktrees for '${alias}'`,
|
|
413
324
|
)
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
for (const entry of await readdir(oldCodePath)) {
|
|
421
|
-
if (!checkoutAliases.includes(entry))
|
|
422
|
-
throw new Error(
|
|
423
|
-
`Cannot convert task '${taskId}'; code contains unmanaged entry '${entry}'`,
|
|
424
|
-
)
|
|
425
|
-
}
|
|
426
|
-
for (const alias of checkoutAliases) {
|
|
427
|
-
const checkoutPath = join(oldCodePath, alias)
|
|
325
|
+
const expected = await realpath(checkoutPath)
|
|
326
|
+
let registered = false
|
|
327
|
+
for (const line of new TextDecoder()
|
|
328
|
+
.decode(listed.stdout)
|
|
329
|
+
.split("\n")) {
|
|
330
|
+
if (!line.startsWith("worktree ")) continue
|
|
428
331
|
try {
|
|
429
|
-
await
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
"git",
|
|
435
|
-
"-C",
|
|
436
|
-
join(root, "repos", alias),
|
|
437
|
-
"worktree",
|
|
438
|
-
"list",
|
|
439
|
-
"--porcelain",
|
|
440
|
-
])
|
|
441
|
-
if (listed.exitCode !== 0)
|
|
442
|
-
throw new Error(
|
|
443
|
-
`Failed to inspect worktrees for '${alias}'`,
|
|
444
|
-
)
|
|
445
|
-
const expected = await realpath(checkoutPath)
|
|
446
|
-
let registered = false
|
|
447
|
-
for (const line of new TextDecoder()
|
|
448
|
-
.decode(listed.stdout)
|
|
449
|
-
.split("\n")) {
|
|
450
|
-
if (!line.startsWith("worktree ")) continue
|
|
451
|
-
try {
|
|
452
|
-
if ((await realpath(line.slice(9))) === expected) {
|
|
453
|
-
registered = true
|
|
454
|
-
break
|
|
455
|
-
}
|
|
456
|
-
} catch {}
|
|
457
|
-
}
|
|
458
|
-
if (!registered)
|
|
459
|
-
throw new Error(
|
|
460
|
-
`Cannot convert task '${taskId}'; checkout '${alias}' is not registered as a Git worktree`,
|
|
461
|
-
)
|
|
462
|
-
}
|
|
463
|
-
},
|
|
464
|
-
apply: async () => {
|
|
465
|
-
await mkdir(firstDirectory, { recursive: true })
|
|
466
|
-
await rename(oldCodePath, firstCodePath)
|
|
467
|
-
try {
|
|
468
|
-
await repair(firstCodePath)
|
|
469
|
-
} catch (cause) {
|
|
470
|
-
await rename(firstCodePath, oldCodePath)
|
|
471
|
-
await repair(oldCodePath)
|
|
472
|
-
await rm(firstDirectory, { recursive: true, force: true })
|
|
473
|
-
throw cause
|
|
332
|
+
if ((await realpath(line.slice(9))) === expected) {
|
|
333
|
+
registered = true
|
|
334
|
+
break
|
|
335
|
+
}
|
|
336
|
+
} catch {}
|
|
474
337
|
}
|
|
475
|
-
|
|
476
|
-
|
|
338
|
+
if (!registered)
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Cannot convert task '${taskId}'; checkout '${alias}' is not registered as a Git worktree`,
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
apply: async () => {
|
|
345
|
+
await mkdir(firstDirectory, { recursive: true })
|
|
346
|
+
await rename(oldCodePath, firstCodePath)
|
|
347
|
+
try {
|
|
348
|
+
await repair(firstCodePath)
|
|
349
|
+
} catch (cause) {
|
|
477
350
|
await rename(firstCodePath, oldCodePath)
|
|
478
351
|
await repair(oldCodePath)
|
|
479
352
|
await rm(firstDirectory, { recursive: true, force: true })
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
353
|
+
throw cause
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
rollback: async () => {
|
|
357
|
+
await rename(firstCodePath, oldCodePath)
|
|
358
|
+
await repair(oldCodePath)
|
|
359
|
+
await rm(firstDirectory, { recursive: true, force: true })
|
|
360
|
+
},
|
|
361
|
+
manualRecovery: `Move ${firstCodePath} back to ${oldCodePath} and run git worktree repair`,
|
|
362
|
+
})
|
|
484
363
|
}
|
|
485
364
|
steps.push(
|
|
486
365
|
documentWriteStep(root, [
|
|
@@ -98,7 +98,7 @@ describe("PullRequestService", () => {
|
|
|
98
98
|
await Bun.write(
|
|
99
99
|
path,
|
|
100
100
|
`#!/usr/bin/env bun
|
|
101
|
-
await Bun.write(${JSON.stringify(ghCallPath)}, JSON.stringify({ args: Bun.argv.slice(2), cwd: process.cwd()
|
|
101
|
+
await Bun.write(${JSON.stringify(ghCallPath)}, JSON.stringify({ args: Bun.argv.slice(2), cwd: process.cwd() }))
|
|
102
102
|
process.stdout.write(${JSON.stringify(stdout)})
|
|
103
103
|
process.stderr.write(${JSON.stringify(stderr)})
|
|
104
104
|
process.exit(${exitCode})
|
|
@@ -111,7 +111,6 @@ process.exit(${exitCode})
|
|
|
111
111
|
(await Bun.file(ghCallPath).json()) as {
|
|
112
112
|
args: string[]
|
|
113
113
|
cwd: string
|
|
114
|
-
gitDir?: string
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
const expectRemoteBranch = async (branch: string, exists = true) => {
|
|
@@ -243,49 +242,6 @@ process.exit(${exitCode})
|
|
|
243
242
|
expect(updated.endsWith(`${body}\n`)).toBe(true)
|
|
244
243
|
})
|
|
245
244
|
|
|
246
|
-
test("creates a PR from a non-colocated jj workspace with backing Git context", async () => {
|
|
247
|
-
if (!Bun.which("jj")) return
|
|
248
|
-
await rm(join(root, "repos/agency"), { recursive: true, force: true })
|
|
249
|
-
await requireCommand([
|
|
250
|
-
"jj",
|
|
251
|
-
"git",
|
|
252
|
-
"clone",
|
|
253
|
-
"--no-colocate",
|
|
254
|
-
remotePath,
|
|
255
|
-
join(root, "repos/agency"),
|
|
256
|
-
])
|
|
257
|
-
await requireCommand(
|
|
258
|
-
["jj", "config", "set", "--repo", "user.name", "Agency Test"],
|
|
259
|
-
join(root, "repos/agency"),
|
|
260
|
-
)
|
|
261
|
-
await requireCommand(
|
|
262
|
-
["jj", "config", "set", "--repo", "user.email", "agency@example.com"],
|
|
263
|
-
join(root, "repos/agency"),
|
|
264
|
-
)
|
|
265
|
-
await Bun.write(
|
|
266
|
-
join(root, "agency.json"),
|
|
267
|
-
JSON.stringify({ version: 2, vcs: "jj" }),
|
|
268
|
-
)
|
|
269
|
-
await createTask("jj-example", "task/jj-example")
|
|
270
|
-
const workspace = await materialize("jj-example")
|
|
271
|
-
await Bun.write(join(workspace.writablePath!, "CHANGE.md"), "jj change\n")
|
|
272
|
-
await requireCommand(
|
|
273
|
-
["jj", "describe", "-m", "feat: add jj example"],
|
|
274
|
-
workspace.writablePath!,
|
|
275
|
-
)
|
|
276
|
-
await requireCommand(["jj", "new"], workspace.writablePath!)
|
|
277
|
-
const url = "https://github.com/example/agency/pull/44"
|
|
278
|
-
await writeFakeGh({ stdout: `${url}\n` })
|
|
279
|
-
|
|
280
|
-
expect(await createPullRequest("jj-example")).toBe(url)
|
|
281
|
-
await expectRemoteBranch("task/jj-example")
|
|
282
|
-
const ghCall = await readGhCall()
|
|
283
|
-
expect(ghCall.args).toContain("--head")
|
|
284
|
-
expect(ghCall.args).toContain("--repo")
|
|
285
|
-
expect(ghCall.gitDir).toContain(".jj")
|
|
286
|
-
expect(await Bun.file(join(root, "repos/agency/.git")).exists()).toBe(false)
|
|
287
|
-
})
|
|
288
|
-
|
|
289
245
|
test("passes --draft to gh", async () => {
|
|
290
246
|
await createTask()
|
|
291
247
|
await writeFakeGh({
|