@markjaquith/agency 2.49.0 → 2.51.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 +56 -9
- package/cli.ts +56 -1
- package/package.json +1 -1
- package/schemas/agency-graph-v1.schema.json +142 -38
- package/src/cli-parser.test.ts +81 -0
- package/src/cli-parser.ts +78 -1
- package/src/cli.test.ts +4 -4
- package/src/commands/init.test.ts +2 -0
- package/src/commands/review.ts +38 -0
- package/src/commands/task.ts +27 -5
- package/src/commands/vcs.test.ts +75 -0
- package/src/commands/vcs.ts +72 -0
- package/src/commands/work.test.ts +2 -0
- package/src/commands/work.ts +2 -2
- package/src/commands/worktree.ts +1 -1
- package/src/graph-schema.test.ts +1 -1
- package/src/graph-schema.ts +36 -13
- package/src/protocol.ts +1 -0
- package/src/services/ArchiveService.test.ts +2 -2
- package/src/services/ArchiveService.ts +24 -0
- package/src/services/ClaimService.ts +3 -2
- package/src/services/ContextService.ts +118 -17
- package/src/services/DoctorService.ts +58 -7
- package/src/services/GraphMutationService.ts +5 -0
- package/src/services/GraphService.ts +119 -54
- package/src/services/PhaseService.ts +196 -70
- package/src/services/PullRequestService.test.ts +2 -2
- package/src/services/PullRequestService.ts +37 -33
- package/src/services/ReadinessService.ts +7 -3
- package/src/services/RepositoryService.test.ts +31 -1
- package/src/services/RepositoryService.ts +63 -31
- package/src/services/ReviewService.test.ts +472 -0
- package/src/services/ReviewService.ts +427 -0
- package/src/services/SyncService.test.ts +69 -2
- package/src/services/SyncService.ts +161 -90
- package/src/services/TaskPhaseService.test.ts +80 -0
- package/src/services/TaskService.ts +82 -9
- package/src/services/VcsMigrationService.test.ts +211 -0
- package/src/services/VcsMigrationService.ts +816 -0
- package/src/services/VersionControlService.test.ts +100 -0
- package/src/services/VersionControlService.ts +479 -0
- package/src/services/WorkbaseService.ts +7 -2
- package/src/services/WorktreeService.test.ts +88 -17
- package/src/services/WorktreeService.ts +865 -329
- package/src/test-utils.ts +12 -0
- package/src/work-view.ts +14 -6
- package/src/workbase/AGENTS.md +2 -2
- package/src/workbase/schemas.test.ts +57 -0
- package/src/workbase/schemas.ts +57 -0
- package/src/workbase/version-control.ts +5 -0
|
@@ -8,6 +8,7 @@ import { RepositoryService } from "./RepositoryService"
|
|
|
8
8
|
import { aggregateProgress, readinessState } from "../readiness"
|
|
9
9
|
import { parseFrontmatter } from "../workbase/frontmatter"
|
|
10
10
|
import { documentRevision } from "../workbase/document-revision"
|
|
11
|
+
import { VersionControlService } from "./VersionControlService"
|
|
11
12
|
import {
|
|
12
13
|
EpicFrontmatter,
|
|
13
14
|
PhaseFrontmatter,
|
|
@@ -54,6 +55,7 @@ interface CheckoutInspection {
|
|
|
54
55
|
readonly checkoutCommit: string | null
|
|
55
56
|
readonly checkoutBranch: string | null
|
|
56
57
|
readonly detached: boolean | null
|
|
58
|
+
readonly dirty: boolean | null
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
type ExecutionData = PhaseData & Partial<Pick<TaskData, "ticketUrl" | "epic">>
|
|
@@ -92,6 +94,18 @@ const runGit = (fs: FileSystemService, cwd: string, args: readonly string[]) =>
|
|
|
92
94
|
Effect.catchAll(() => Effect.succeed(null)),
|
|
93
95
|
)
|
|
94
96
|
|
|
97
|
+
const runGitText = (
|
|
98
|
+
fs: FileSystemService,
|
|
99
|
+
cwd: string,
|
|
100
|
+
args: readonly string[],
|
|
101
|
+
) =>
|
|
102
|
+
fs.runCommand(["git", "-C", cwd, ...args], { captureOutput: true }).pipe(
|
|
103
|
+
Effect.map((result) =>
|
|
104
|
+
result.exitCode === 0 ? result.stdout.trim() : null,
|
|
105
|
+
),
|
|
106
|
+
Effect.catchAll(() => Effect.succeed(null)),
|
|
107
|
+
)
|
|
108
|
+
|
|
95
109
|
const worktreePaths = (output: string | null) =>
|
|
96
110
|
new Set(
|
|
97
111
|
(output ?? "")
|
|
@@ -114,6 +128,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
114
128
|
const fs = yield* FileSystemService
|
|
115
129
|
const workbase = yield* WorkbaseService
|
|
116
130
|
const repositoryService = yield* RepositoryService
|
|
131
|
+
const versionControl = yield* VersionControlService
|
|
117
132
|
const cwd = resolve(options.cwd ?? process.cwd())
|
|
118
133
|
const suppliedTarget = options.target ?? "."
|
|
119
134
|
const candidate = resolve(cwd, suppliedTarget)
|
|
@@ -121,6 +136,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
121
136
|
const { root, config } = yield* workbase.loadConfig(
|
|
122
137
|
candidateExists ? candidate : cwd,
|
|
123
138
|
)
|
|
139
|
+
const backend = yield* versionControl.forWorkbase(root)
|
|
124
140
|
|
|
125
141
|
if (relative(root, candidate) === "") {
|
|
126
142
|
const compact = !options.full
|
|
@@ -201,6 +217,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
201
217
|
root,
|
|
202
218
|
configPath: join(root, "agency.json"),
|
|
203
219
|
version: config.version,
|
|
220
|
+
vcs: config.vcs ?? "git",
|
|
204
221
|
},
|
|
205
222
|
target: { kind: "workbase", path: root },
|
|
206
223
|
hint: compact
|
|
@@ -765,9 +782,13 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
765
782
|
: task?.data && "repo" in task.data
|
|
766
783
|
? (task.data as ExecutionData)
|
|
767
784
|
: null
|
|
768
|
-
const
|
|
769
|
-
?
|
|
770
|
-
|
|
785
|
+
const reviewData =
|
|
786
|
+
task?.data && "review" in task.data ? task.data.review : null
|
|
787
|
+
const references: readonly RepositoryReference[] = reviewData
|
|
788
|
+
? [{ repo: reviewData.repo, ref: reviewData.commit }]
|
|
789
|
+
: executionData
|
|
790
|
+
? (executionData.repos ?? [])
|
|
791
|
+
: (epic?.data.repos ?? [])
|
|
771
792
|
const entityDirectory = target.path.replace(
|
|
772
793
|
/\/(?:EPIC|TASK|PHASE)\.md$/,
|
|
773
794
|
"",
|
|
@@ -784,9 +805,45 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
784
805
|
const inspectCheckout = (
|
|
785
806
|
repositoryPath: string,
|
|
786
807
|
checkoutPath: string,
|
|
808
|
+
expectedBranch: string | null = null,
|
|
787
809
|
) =>
|
|
788
810
|
Effect.gen(function* (): Generator<any, CheckoutInspection, any> {
|
|
789
811
|
const materialized = yield* fs.isDirectory(checkoutPath)
|
|
812
|
+
if (backend.kind === "jj") {
|
|
813
|
+
const listed = yield* Effect.either(
|
|
814
|
+
backend.listWorkspaces(repositoryPath),
|
|
815
|
+
)
|
|
816
|
+
const workspaces = Either.isRight(listed) ? listed.right : []
|
|
817
|
+
if (Either.isLeft(listed)) {
|
|
818
|
+
inspectionWarnings.push(
|
|
819
|
+
`Unable to inspect jj workspace registrations for ${repositoryPath}`,
|
|
820
|
+
)
|
|
821
|
+
}
|
|
822
|
+
const canonicalCheckoutPath = materialized
|
|
823
|
+
? yield* fs.realPath(checkoutPath)
|
|
824
|
+
: resolve(checkoutPath)
|
|
825
|
+
const registered = workspaces.some(
|
|
826
|
+
(workspace) => workspace.path === canonicalCheckoutPath,
|
|
827
|
+
)
|
|
828
|
+
if (!materialized) {
|
|
829
|
+
return {
|
|
830
|
+
materialized: false,
|
|
831
|
+
registered,
|
|
832
|
+
checkoutCommit: null,
|
|
833
|
+
checkoutBranch: null,
|
|
834
|
+
detached: null,
|
|
835
|
+
dirty: null,
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return {
|
|
839
|
+
materialized: true,
|
|
840
|
+
registered,
|
|
841
|
+
checkoutCommit: yield* backend.workspaceHead(checkoutPath),
|
|
842
|
+
checkoutBranch: expectedBranch,
|
|
843
|
+
detached: expectedBranch === null,
|
|
844
|
+
dirty: yield* backend.workspaceDirty(checkoutPath),
|
|
845
|
+
}
|
|
846
|
+
}
|
|
790
847
|
const listed = yield* runGit(fs, repositoryPath, [
|
|
791
848
|
"worktree",
|
|
792
849
|
"list",
|
|
@@ -812,6 +869,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
812
869
|
checkoutCommit: null,
|
|
813
870
|
checkoutBranch: null,
|
|
814
871
|
detached: null,
|
|
872
|
+
dirty: null,
|
|
815
873
|
}
|
|
816
874
|
}
|
|
817
875
|
const checkoutCommit = yield* runGit(fs, checkoutPath, [
|
|
@@ -824,6 +882,10 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
824
882
|
"--short",
|
|
825
883
|
"HEAD",
|
|
826
884
|
])
|
|
885
|
+
const checkoutStatus = yield* runGitText(fs, checkoutPath, [
|
|
886
|
+
"status",
|
|
887
|
+
"--porcelain",
|
|
888
|
+
])
|
|
827
889
|
const resolvedCheckoutPath = yield* fs.realPath(checkoutPath)
|
|
828
890
|
registered = registered || listedPaths.has(resolvedCheckoutPath)
|
|
829
891
|
if (checkoutCommit === null) {
|
|
@@ -837,6 +899,8 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
837
899
|
checkoutCommit,
|
|
838
900
|
checkoutBranch,
|
|
839
901
|
detached: checkoutBranch === null,
|
|
902
|
+
dirty:
|
|
903
|
+
checkoutStatus === null ? null : checkoutStatus.length > 0,
|
|
840
904
|
}
|
|
841
905
|
})
|
|
842
906
|
|
|
@@ -846,14 +910,22 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
846
910
|
const repositoryPath =
|
|
847
911
|
repository?.path ?? join(root, "repos", executionData.repo)
|
|
848
912
|
const checkoutPath = join(codePath, executionData.repo)
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
const baseCommit = yield*
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
913
|
+
let branchCommit = yield* backend.resolveRevision(
|
|
914
|
+
repositoryPath,
|
|
915
|
+
executionData.branch,
|
|
916
|
+
)
|
|
917
|
+
const baseCommit = yield* backend.resolveRevision(
|
|
918
|
+
repositoryPath,
|
|
919
|
+
executionData.base,
|
|
920
|
+
)
|
|
921
|
+
const checkout = yield* inspectCheckout(
|
|
922
|
+
repositoryPath,
|
|
923
|
+
checkoutPath,
|
|
924
|
+
executionData.branch,
|
|
925
|
+
)
|
|
926
|
+
if (backend.kind === "jj" && branchCommit === null) {
|
|
927
|
+
branchCommit = checkout.checkoutCommit
|
|
928
|
+
}
|
|
857
929
|
if (branchCommit === null) {
|
|
858
930
|
inspectionWarnings.push(
|
|
859
931
|
`Unable to resolve branch '${executionData.branch}' in ${repositoryPath}`,
|
|
@@ -872,7 +944,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
872
944
|
base: executionData.base,
|
|
873
945
|
branchCommit,
|
|
874
946
|
baseCommit,
|
|
875
|
-
...
|
|
947
|
+
...checkout,
|
|
876
948
|
}
|
|
877
949
|
})
|
|
878
950
|
: null
|
|
@@ -883,10 +955,10 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
883
955
|
const repositoryPath =
|
|
884
956
|
repository?.path ?? join(root, "repos", reference.repo)
|
|
885
957
|
const checkoutPath = join(codePath, reference.repo)
|
|
886
|
-
const resolvedCommit = yield*
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
958
|
+
const resolvedCommit = yield* backend.resolveRevision(
|
|
959
|
+
repositoryPath,
|
|
960
|
+
reference.ref,
|
|
961
|
+
)
|
|
890
962
|
if (resolvedCommit === null) {
|
|
891
963
|
inspectionWarnings.push(
|
|
892
964
|
`Unable to resolve reference '${reference.ref}' in ${repositoryPath}`,
|
|
@@ -901,6 +973,17 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
901
973
|
...(yield* inspectCheckout(repositoryPath, checkoutPath)),
|
|
902
974
|
})
|
|
903
975
|
}
|
|
976
|
+
const reviewSourceCommit = reviewData
|
|
977
|
+
? yield* runGit(fs, join(root, "repos", reviewData.repo), [
|
|
978
|
+
"ls-remote",
|
|
979
|
+
"origin",
|
|
980
|
+
reviewData.source.kind === "pull-request"
|
|
981
|
+
? reviewData.source.fetchRef
|
|
982
|
+
: reviewData.source.ref
|
|
983
|
+
.replace(/^refs\/remotes\/origin\//, "")
|
|
984
|
+
.replace(/^origin\//, ""),
|
|
985
|
+
])
|
|
986
|
+
: null
|
|
904
987
|
|
|
905
988
|
const checkoutStates = [writable, ...referenceCheckouts].filter(
|
|
906
989
|
(value): value is NonNullable<typeof value> => value !== null,
|
|
@@ -933,6 +1016,7 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
933
1016
|
root,
|
|
934
1017
|
configPath: join(root, "agency.json"),
|
|
935
1018
|
version: config.version,
|
|
1019
|
+
vcs: config.vcs ?? "git",
|
|
936
1020
|
},
|
|
937
1021
|
target,
|
|
938
1022
|
documents: {
|
|
@@ -956,7 +1040,11 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
956
1040
|
aggregate: aggregateProgress(aggregateStatuses),
|
|
957
1041
|
},
|
|
958
1042
|
authority: {
|
|
959
|
-
mode:
|
|
1043
|
+
mode: reviewData
|
|
1044
|
+
? "review"
|
|
1045
|
+
: executionData
|
|
1046
|
+
? "execution"
|
|
1047
|
+
: "orchestration",
|
|
960
1048
|
writable: writable
|
|
961
1049
|
? {
|
|
962
1050
|
repo: writable.repo,
|
|
@@ -972,6 +1060,9 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
972
1060
|
repositoryPath: reference.repositoryPath,
|
|
973
1061
|
checkoutPath: reference.checkoutPath,
|
|
974
1062
|
})),
|
|
1063
|
+
documents: {
|
|
1064
|
+
writable: reviewData && task ? [task.path] : [],
|
|
1065
|
+
},
|
|
975
1066
|
},
|
|
976
1067
|
workspace: options.compact
|
|
977
1068
|
? {
|
|
@@ -997,6 +1088,16 @@ export class ContextService extends Effect.Service<ContextService>()(
|
|
|
997
1088
|
references: referenceCheckouts,
|
|
998
1089
|
warnings: inspectionWarnings,
|
|
999
1090
|
},
|
|
1091
|
+
review: reviewData
|
|
1092
|
+
? {
|
|
1093
|
+
...reviewData,
|
|
1094
|
+
sourceObservation: {
|
|
1095
|
+
available: reviewSourceCommit !== null,
|
|
1096
|
+
commit: reviewSourceCommit?.split(/\s+/)[0] ?? null,
|
|
1097
|
+
},
|
|
1098
|
+
checkout: referenceCheckouts[0] ?? null,
|
|
1099
|
+
}
|
|
1100
|
+
: null,
|
|
1000
1101
|
pr: executionData?.pr
|
|
1001
1102
|
? normalizePullRequestRecord(executionData.pr)
|
|
1002
1103
|
: { url: null, state: "none" },
|
|
@@ -126,6 +126,14 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
const gitAvailable = yield* tool("tool.git", "git", "error", "Git")
|
|
129
|
+
const jjAvailable = yield* tool(
|
|
130
|
+
"tool.jj",
|
|
131
|
+
"jj",
|
|
132
|
+
config.vcs === "jj" ? "error" : "optional",
|
|
133
|
+
"Jujutsu",
|
|
134
|
+
)
|
|
135
|
+
const versionControlAvailable =
|
|
136
|
+
gitAvailable && (config.vcs !== "jj" || jjAvailable)
|
|
129
137
|
yield* tool(
|
|
130
138
|
"capability.runner.opencode",
|
|
131
139
|
"opencode",
|
|
@@ -282,6 +290,8 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
282
290
|
}
|
|
283
291
|
|
|
284
292
|
const refs = new Map<string, Set<string>>()
|
|
293
|
+
const reviewSources: { repo: string; task: string; ref: string }[] =
|
|
294
|
+
[]
|
|
285
295
|
const declareRef = (repo: string, ref: string) => {
|
|
286
296
|
const values = refs.get(repo) ?? new Set<string>()
|
|
287
297
|
values.add(ref)
|
|
@@ -293,7 +303,19 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
293
303
|
declareRef(reference.repo, reference.ref)
|
|
294
304
|
}
|
|
295
305
|
for (const task of yield* tasks.list(root)) {
|
|
296
|
-
if ("
|
|
306
|
+
if ("review" in task.data) {
|
|
307
|
+
declareRef(task.data.review.repo, task.data.review.commit)
|
|
308
|
+
reviewSources.push({
|
|
309
|
+
repo: task.data.review.repo,
|
|
310
|
+
task: task.id,
|
|
311
|
+
ref:
|
|
312
|
+
task.data.review.source.kind === "pull-request"
|
|
313
|
+
? task.data.review.source.fetchRef
|
|
314
|
+
: task.data.review.source.ref
|
|
315
|
+
.replace(/^refs\/remotes\/origin\//, "")
|
|
316
|
+
.replace(/^origin\//, ""),
|
|
317
|
+
})
|
|
318
|
+
} else if ("repo" in task.data) {
|
|
297
319
|
declareRef(task.data.repo, task.data.base)
|
|
298
320
|
for (const reference of task.data.repos ?? [])
|
|
299
321
|
declareRef(reference.repo, reference.ref)
|
|
@@ -307,18 +329,17 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
307
329
|
}
|
|
308
330
|
}
|
|
309
331
|
|
|
310
|
-
const repositoryList =
|
|
332
|
+
const repositoryList = versionControlAvailable
|
|
311
333
|
? yield* repositories.list(root)
|
|
312
334
|
: []
|
|
313
|
-
if (!
|
|
335
|
+
if (!versionControlAvailable) {
|
|
314
336
|
add({
|
|
315
337
|
id: "repository.inspection",
|
|
316
338
|
category: "repository",
|
|
317
339
|
level: "warning",
|
|
318
340
|
status: "fail",
|
|
319
|
-
message:
|
|
320
|
-
|
|
321
|
-
remediation: "Install 'git' and rerun 'agency doctor'.",
|
|
341
|
+
message: `Repository, ref, remote, and workspace checks were skipped because the ${config.vcs ?? "git"} backend is unavailable`,
|
|
342
|
+
remediation: `Install '${config.vcs === "jj" ? "jj" : "git"}' and rerun 'agency doctor'.`,
|
|
322
343
|
})
|
|
323
344
|
}
|
|
324
345
|
for (const repository of repositoryList) {
|
|
@@ -357,6 +378,36 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
357
378
|
})
|
|
358
379
|
if (!repositoryValid) continue
|
|
359
380
|
|
|
381
|
+
for (const source of reviewSources.filter(
|
|
382
|
+
(item) => item.repo === repository.alias,
|
|
383
|
+
)) {
|
|
384
|
+
const observed = yield* fs.runCommand(
|
|
385
|
+
[
|
|
386
|
+
"git",
|
|
387
|
+
"-C",
|
|
388
|
+
repository.path,
|
|
389
|
+
"ls-remote",
|
|
390
|
+
"origin",
|
|
391
|
+
source.ref,
|
|
392
|
+
],
|
|
393
|
+
{ captureOutput: true },
|
|
394
|
+
)
|
|
395
|
+
const available =
|
|
396
|
+
observed.exitCode === 0 && Boolean(observed.stdout.trim())
|
|
397
|
+
add({
|
|
398
|
+
id: `review.${source.task}.source`,
|
|
399
|
+
category: "repository",
|
|
400
|
+
level: "warning",
|
|
401
|
+
status: available ? "pass" : "fail",
|
|
402
|
+
message: available
|
|
403
|
+
? `Review source '${source.ref}' is available for task '${source.task}'`
|
|
404
|
+
: `Review source '${source.ref}' is unavailable for task '${source.task}'; its pin remains usable`,
|
|
405
|
+
remediation: available
|
|
406
|
+
? undefined
|
|
407
|
+
: "Use the pinned checkout or refresh after restoring the source.",
|
|
408
|
+
})
|
|
409
|
+
}
|
|
410
|
+
|
|
360
411
|
for (const ref of [...(refs.get(repository.alias) ?? [])].sort()) {
|
|
361
412
|
const local = yield* fs.runCommand(
|
|
362
413
|
[
|
|
@@ -395,7 +446,7 @@ export class DoctorService extends Effect.Service<DoctorService>()(
|
|
|
395
446
|
}
|
|
396
447
|
}
|
|
397
448
|
|
|
398
|
-
if (validation.valid &&
|
|
449
|
+
if (validation.valid && versionControlAvailable) {
|
|
399
450
|
const inspected = yield* Effect.either(worktrees.list(root))
|
|
400
451
|
if (Either.isLeft(inspected)) {
|
|
401
452
|
add({
|
|
@@ -360,6 +360,11 @@ export class GraphMutationService extends Effect.Service<GraphMutationService>()
|
|
|
360
360
|
"base",
|
|
361
361
|
"pr",
|
|
362
362
|
].some((key) => updates[key as keyof TaskUpdates] !== undefined)
|
|
363
|
+
if ("review" in record.data && executionChange) {
|
|
364
|
+
return yield* new GraphMutationError({
|
|
365
|
+
message: `Review task '${id}' does not have writable execution metadata`,
|
|
366
|
+
})
|
|
367
|
+
}
|
|
363
368
|
if ("phases" in record.data && executionChange) {
|
|
364
369
|
return yield* new GraphMutationError({
|
|
365
370
|
message: `Task '${id}' has multiple phases; update execution metadata on a phase instead`,
|
|
@@ -54,7 +54,10 @@ interface RepositoryRecord {
|
|
|
54
54
|
readonly target: string | null
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
type ExecutionData =
|
|
57
|
+
type ExecutionData =
|
|
58
|
+
| PhaseData
|
|
59
|
+
| Extract<TaskData, { readonly repo: string }>
|
|
60
|
+
| Extract<TaskData, { readonly review: unknown }>
|
|
58
61
|
|
|
59
62
|
export interface GraphOptions {
|
|
60
63
|
readonly cwd?: string
|
|
@@ -548,10 +551,18 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
548
551
|
const executionRepositories = (
|
|
549
552
|
data: TaskData | PhaseData,
|
|
550
553
|
): readonly string[] =>
|
|
551
|
-
"
|
|
552
|
-
? [data.
|
|
553
|
-
:
|
|
554
|
+
"review" in data
|
|
555
|
+
? [data.review.repo]
|
|
556
|
+
: "repo" in data
|
|
557
|
+
? [data.repo, ...(data.repos ?? []).map((item) => item.repo)]
|
|
558
|
+
: []
|
|
554
559
|
const executionEdges = (id: string, data: TaskData | PhaseData) => {
|
|
560
|
+
if ("review" in data) {
|
|
561
|
+
edges.push(
|
|
562
|
+
edge("references", id, repositoryNodeId(data.review.repo)),
|
|
563
|
+
)
|
|
564
|
+
return
|
|
565
|
+
}
|
|
555
566
|
if (!("repo" in data)) return
|
|
556
567
|
edges.push(edge("writes", id, repositoryNodeId(data.repo)))
|
|
557
568
|
for (const reference of data.repos ?? []) {
|
|
@@ -650,8 +661,9 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
650
661
|
const executionDetails = (entityPath: string, data: ExecutionData) =>
|
|
651
662
|
Effect.gen(function* () {
|
|
652
663
|
const directory = entityPath.replace(/\/(?:TASK|PHASE)\.md$/, "")
|
|
653
|
-
const
|
|
654
|
-
const
|
|
664
|
+
const repo = "review" in data ? data.review.repo : data.repo
|
|
665
|
+
const checkoutPath = join(directory, "code", repo)
|
|
666
|
+
const repositoryPath = join(root, "repos", repo)
|
|
655
667
|
const materialized = yield* fs.isDirectory(checkoutPath)
|
|
656
668
|
const result: {
|
|
657
669
|
workspace?: GraphExecutionWorkspace
|
|
@@ -666,58 +678,110 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
666
678
|
}
|
|
667
679
|
}
|
|
668
680
|
if (include.has("git")) {
|
|
669
|
-
result.git =
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
681
|
+
result.git =
|
|
682
|
+
"review" in data
|
|
683
|
+
? {
|
|
684
|
+
pinnedCommit: data.review.commit,
|
|
685
|
+
sourceCommit: ((value) =>
|
|
686
|
+
value?.split(/\s+/)[0] ?? null)(
|
|
687
|
+
yield* run(fs, [
|
|
688
|
+
"git",
|
|
689
|
+
"-C",
|
|
690
|
+
repositoryPath,
|
|
691
|
+
"ls-remote",
|
|
692
|
+
"origin",
|
|
693
|
+
data.review.source.kind === "pull-request"
|
|
694
|
+
? data.review.source.fetchRef
|
|
695
|
+
: data.review.source.ref
|
|
696
|
+
.replace(/^refs\/remotes\/origin\//, "")
|
|
697
|
+
.replace(/^origin\//, ""),
|
|
698
|
+
]),
|
|
699
|
+
),
|
|
700
|
+
checkoutCommit: materialized
|
|
701
|
+
? yield* run(fs, [
|
|
702
|
+
"git",
|
|
703
|
+
"-C",
|
|
704
|
+
checkoutPath,
|
|
705
|
+
"rev-parse",
|
|
706
|
+
"HEAD",
|
|
707
|
+
])
|
|
708
|
+
: null,
|
|
709
|
+
checkoutBranch: materialized
|
|
710
|
+
? yield* run(fs, [
|
|
711
|
+
"git",
|
|
712
|
+
"-C",
|
|
713
|
+
checkoutPath,
|
|
714
|
+
"symbolic-ref",
|
|
715
|
+
"--quiet",
|
|
716
|
+
"--short",
|
|
717
|
+
"HEAD",
|
|
718
|
+
])
|
|
719
|
+
: null,
|
|
720
|
+
dirty: materialized
|
|
721
|
+
? ((status) =>
|
|
722
|
+
status === null ? null : status.length > 0)(
|
|
723
|
+
yield* runText(fs, [
|
|
724
|
+
"git",
|
|
725
|
+
"-C",
|
|
726
|
+
checkoutPath,
|
|
727
|
+
"status",
|
|
728
|
+
"--porcelain",
|
|
729
|
+
]),
|
|
730
|
+
)
|
|
731
|
+
: null,
|
|
732
|
+
}
|
|
733
|
+
: {
|
|
734
|
+
branch: data.branch,
|
|
735
|
+
base: data.base,
|
|
736
|
+
branchCommit: yield* run(fs, [
|
|
710
737
|
"git",
|
|
711
738
|
"-C",
|
|
712
|
-
|
|
713
|
-
"
|
|
714
|
-
|
|
739
|
+
repositoryPath,
|
|
740
|
+
"rev-parse",
|
|
741
|
+
`${data.branch}^{commit}`,
|
|
715
742
|
]),
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
743
|
+
baseCommit: yield* run(fs, [
|
|
744
|
+
"git",
|
|
745
|
+
"-C",
|
|
746
|
+
repositoryPath,
|
|
747
|
+
"rev-parse",
|
|
748
|
+
`${data.base}^{commit}`,
|
|
749
|
+
]),
|
|
750
|
+
checkoutCommit: materialized
|
|
751
|
+
? yield* run(fs, [
|
|
752
|
+
"git",
|
|
753
|
+
"-C",
|
|
754
|
+
checkoutPath,
|
|
755
|
+
"rev-parse",
|
|
756
|
+
"HEAD",
|
|
757
|
+
])
|
|
758
|
+
: null,
|
|
759
|
+
checkoutBranch: materialized
|
|
760
|
+
? yield* run(fs, [
|
|
761
|
+
"git",
|
|
762
|
+
"-C",
|
|
763
|
+
checkoutPath,
|
|
764
|
+
"symbolic-ref",
|
|
765
|
+
"--quiet",
|
|
766
|
+
"--short",
|
|
767
|
+
"HEAD",
|
|
768
|
+
])
|
|
769
|
+
: null,
|
|
770
|
+
dirty: materialized
|
|
771
|
+
? ((status) =>
|
|
772
|
+
status === null ? null : status.length > 0)(
|
|
773
|
+
yield* runText(fs, [
|
|
774
|
+
"git",
|
|
775
|
+
"-C",
|
|
776
|
+
checkoutPath,
|
|
777
|
+
"status",
|
|
778
|
+
"--porcelain",
|
|
779
|
+
]),
|
|
780
|
+
)
|
|
781
|
+
: null,
|
|
782
|
+
}
|
|
719
783
|
}
|
|
720
|
-
if (include.has("pr")) {
|
|
784
|
+
if (include.has("pr") && !("review" in data)) {
|
|
721
785
|
if (!data.pr) {
|
|
722
786
|
result.pr = { url: null, state: "none" }
|
|
723
787
|
} else {
|
|
@@ -879,6 +943,7 @@ export class GraphService extends Effect.Service<GraphService>()(
|
|
|
879
943
|
version: GRAPH_VERSION,
|
|
880
944
|
workbase: {
|
|
881
945
|
version: config.version,
|
|
946
|
+
vcs: config.vcs ?? "git",
|
|
882
947
|
...(include.has("workspace") ? { root } : {}),
|
|
883
948
|
},
|
|
884
949
|
filters,
|