@clipboard-health/groundcrew 4.42.0 → 4.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/commands/openWorkspace.d.ts +24 -0
- package/dist/commands/openWorkspace.d.ts.map +1 -0
- package/dist/commands/openWorkspace.js +339 -0
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +1 -0
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +8 -14
- package/dist/commands/reviewer.d.ts +2 -0
- package/dist/commands/reviewer.d.ts.map +1 -1
- package/dist/commands/reviewer.js +9 -4
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +2 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +17 -6
- package/dist/commands/task.d.ts.map +1 -1
- package/dist/commands/task.js +10 -6
- package/dist/lib/agentLaunch.d.ts +5 -1
- package/dist/lib/agentLaunch.d.ts.map +1 -1
- package/dist/lib/agentLaunch.js +10 -6
- package/dist/lib/config.d.ts +27 -10
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +17 -0
- package/dist/lib/launchCommand.d.ts +15 -1
- package/dist/lib/launchCommand.d.ts.map +1 -1
- package/dist/lib/launchCommand.js +23 -5
- package/dist/lib/pullRequests.d.ts +26 -0
- package/dist/lib/pullRequests.d.ts.map +1 -1
- package/dist/lib/pullRequests.js +51 -0
- package/dist/lib/runState.d.ts +6 -0
- package/dist/lib/runState.d.ts.map +1 -1
- package/dist/lib/runState.js +8 -3
- package/dist/lib/workspaceLiveness.d.ts +8 -0
- package/dist/lib/workspaceLiveness.d.ts.map +1 -0
- package/dist/lib/workspaceLiveness.js +17 -0
- package/dist/lib/worktreeRunState.d.ts +20 -0
- package/dist/lib/worktreeRunState.d.ts.map +1 -0
- package/dist/lib/worktreeRunState.js +35 -0
- package/dist/lib/worktrees.d.ts +15 -1
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +117 -25
- package/docs/commands.md +18 -0
- package/docs/configuration.md +29 -28
- package/docs/runners.md +20 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -106,6 +106,8 @@ crew run [--watch] # one-shot or --watch f
|
|
|
106
106
|
crew start <TASK> # provision + launch one task now
|
|
107
107
|
crew stop <TASK> [--reason <text>] # stop workspace, keep worktree
|
|
108
108
|
crew resume <TASK> # reopen a paused task
|
|
109
|
+
crew open <pr> | --branch <name> [--repo <owner/repo>] # iterate on an existing PR or branch
|
|
110
|
+
[--prompt <text> | --prompt-file <path>] [--task <id>] [--dry-run]
|
|
109
111
|
crew cleanup [--force] <TASK> # tear down every worktree for a task
|
|
110
112
|
crew upgrade [<version>] # reinstall crew globally through npm
|
|
111
113
|
```
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA4RA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCvD"}
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { cleanupWorkspaceCli } from "./commands/cleanupWorkspace.js";
|
|
|
3
3
|
import { doctor } from "./commands/doctor.js";
|
|
4
4
|
import { initConfigCli } from "./commands/init.js";
|
|
5
5
|
import { interruptWorkspaceCli } from "./commands/interruptWorkspace.js";
|
|
6
|
+
import { openWorkspaceCli } from "./commands/openWorkspace.js";
|
|
6
7
|
import { orchestrate } from "./commands/orchestrator.js";
|
|
7
8
|
import { resumeWorkspaceCli } from "./commands/resumeWorkspace.js";
|
|
8
9
|
import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
|
|
@@ -169,6 +170,11 @@ const SUBCOMMANDS = {
|
|
|
169
170
|
usage: "<task>",
|
|
170
171
|
invoke: resumeWorkspaceCli,
|
|
171
172
|
},
|
|
173
|
+
open: {
|
|
174
|
+
summary: "Open an existing PR or branch in a new worktree and launch a session",
|
|
175
|
+
usage: "<pr> | --branch <name> [--repo <owner/repo>] [--agent <agent>] [--prompt <text> | --prompt-file <path>] [--task <id>] [--dry-run]",
|
|
176
|
+
invoke: openWorkspaceCli,
|
|
177
|
+
},
|
|
172
178
|
setup: {
|
|
173
179
|
summary: "Removed repository bootstrap command",
|
|
174
180
|
usage: "repos",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ResolvedConfig } from "../lib/config.ts";
|
|
2
|
+
interface PullRequestInput {
|
|
3
|
+
kind: "pr";
|
|
4
|
+
pr: string;
|
|
5
|
+
repositoryHint?: string;
|
|
6
|
+
}
|
|
7
|
+
interface BranchInput {
|
|
8
|
+
kind: "branch";
|
|
9
|
+
branch: string;
|
|
10
|
+
}
|
|
11
|
+
export interface OpenWorkspaceOptions {
|
|
12
|
+
input: PullRequestInput | BranchInput;
|
|
13
|
+
repository?: string;
|
|
14
|
+
agent?: string;
|
|
15
|
+
/** Resolved prompt text; when undefined the agent opens interactively. */
|
|
16
|
+
promptText?: string;
|
|
17
|
+
taskOverride?: string;
|
|
18
|
+
dryRun?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function openWorkspace(config: ResolvedConfig, options: OpenWorkspaceOptions): Promise<void>;
|
|
21
|
+
export declare function parseOpenWorkspaceArgs(argv: string[]): OpenWorkspaceOptions;
|
|
22
|
+
export declare function openWorkspaceCli(argv: string[]): Promise<void>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=openWorkspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/openWorkspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AActF,UAAU,gBAAgB;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA6JD,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAwGf;AA4ID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAE3E;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { composeAgentLaunch, openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
|
|
4
|
+
import { loadConfig, repositoryBaseDir } from "../lib/config.js";
|
|
5
|
+
import { resolvePullRequest } from "../lib/pullRequests.js";
|
|
6
|
+
import { resolvePrepareWorktreeCommand } from "../lib/repositoryHooks.js";
|
|
7
|
+
import { recordRunState, readRunState } from "../lib/runState.js";
|
|
8
|
+
import { stageBuildSecrets, stagePromptText, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
|
|
9
|
+
import { normalizePlainTaskId } from "../lib/taskId.js";
|
|
10
|
+
import { debug, errorMessage, log, okMark } from "../lib/util.js";
|
|
11
|
+
import { failIfWorkspaceAlreadyLive } from "../lib/workspaceLiveness.js";
|
|
12
|
+
import { resolveLaunchDir, worktrees } from "../lib/worktrees.js";
|
|
13
|
+
const OPEN_USAGE = "Usage: crew open <pr> | --branch <name> [--repo <owner/repo>] [--agent <agent>] [--prompt <text> | --prompt-file <path>] [--task <id>] [--dry-run]";
|
|
14
|
+
const PULL_REQUEST_URL_PATTERN = /^https?:\/\/github\.com\/(?<repository>[^/]+\/[^/]+)\/pull\/(?<pr>\d+)/;
|
|
15
|
+
function parsePullRequestReference(reference) {
|
|
16
|
+
const groups = PULL_REQUEST_URL_PATTERN.exec(reference)?.groups;
|
|
17
|
+
if (groups?.["repository"] !== undefined && groups["pr"] !== undefined) {
|
|
18
|
+
return { repository: groups["repository"], pr: groups["pr"] };
|
|
19
|
+
}
|
|
20
|
+
return { pr: reference };
|
|
21
|
+
}
|
|
22
|
+
function slugifyBranch(branch) {
|
|
23
|
+
return branch
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.replaceAll(/[^a-z0-9]+/g, "-")
|
|
26
|
+
.replaceAll(/^-+|-+$/g, "");
|
|
27
|
+
}
|
|
28
|
+
function repositoryCloneDir(config, repository) {
|
|
29
|
+
return path.resolve(repositoryBaseDir(config, repository), repository);
|
|
30
|
+
}
|
|
31
|
+
function assertKnownRepository(config, repository) {
|
|
32
|
+
if (!config.workspace.knownRepositories.includes(repository)) {
|
|
33
|
+
throw new Error(`Repository "${repository}" is not in workspace.knownRepositories: ${config.workspace.knownRepositories.join(", ")}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function repositoryBasename(repository) {
|
|
37
|
+
const lastSlash = repository.lastIndexOf("/");
|
|
38
|
+
return lastSlash === -1 ? repository : repository.slice(lastSlash + 1);
|
|
39
|
+
}
|
|
40
|
+
function resolveRepositoryHint(config, repositoryHint) {
|
|
41
|
+
const normalizedHint = repositoryHint.toLowerCase();
|
|
42
|
+
const exactMatches = config.workspace.knownRepositories.filter((repository) => repository.toLowerCase() === normalizedHint);
|
|
43
|
+
const [exactMatch] = exactMatches;
|
|
44
|
+
if (exactMatches.length === 1 && exactMatch !== undefined) {
|
|
45
|
+
return exactMatch;
|
|
46
|
+
}
|
|
47
|
+
const basename = repositoryBasename(repositoryHint).toLowerCase();
|
|
48
|
+
const basenameMatches = config.workspace.knownRepositories.filter((repository) => repositoryBasename(repository).toLowerCase() === basename);
|
|
49
|
+
const [basenameMatch] = basenameMatches;
|
|
50
|
+
if (basenameMatches.length === 1 && basenameMatch !== undefined) {
|
|
51
|
+
return basenameMatch;
|
|
52
|
+
}
|
|
53
|
+
if (basenameMatches.length > 1) {
|
|
54
|
+
throw new Error(`Repository hint "${repositoryHint}" matches multiple configured repositories: ${basenameMatches.join(", ")}. Pass --repo <name> to choose one.`);
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Repository hint "${repositoryHint}" does not match workspace.knownRepositories: ${config.workspace.knownRepositories.join(", ")}. Pass --repo <name> to choose one.`);
|
|
57
|
+
}
|
|
58
|
+
function resolveOpenRepository(config, options) {
|
|
59
|
+
if (options.repository !== undefined) {
|
|
60
|
+
assertKnownRepository(config, options.repository);
|
|
61
|
+
return options.repository;
|
|
62
|
+
}
|
|
63
|
+
if (options.input.kind === "pr" && options.input.repositoryHint !== undefined) {
|
|
64
|
+
return resolveRepositoryHint(config, options.input.repositoryHint);
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`crew open: --repo <owner/repo> is required\n${OPEN_USAGE}`);
|
|
67
|
+
}
|
|
68
|
+
function failIfAlreadyTracked(config, task, repository) {
|
|
69
|
+
const hasWorktree = worktrees
|
|
70
|
+
.findByTask(config, task)
|
|
71
|
+
.some((entry) => entry.repository === repository);
|
|
72
|
+
if (hasWorktree || readRunState(config, task) !== undefined) {
|
|
73
|
+
throw new Error(`Task ${task} already has a worktree or run state. Use 'crew resume ${task}' to continue it, or 'crew status ${task}' to inspect it.`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function resolveTarget(config, options, repository) {
|
|
77
|
+
if (options.input.kind === "branch") {
|
|
78
|
+
const { branch } = options.input;
|
|
79
|
+
return {
|
|
80
|
+
branch,
|
|
81
|
+
title: branch,
|
|
82
|
+
task: normalizePlainTaskId(options.taskOverride ?? slugifyBranch(branch)),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const pullRequest = await resolvePullRequest({
|
|
86
|
+
repoDir: repositoryCloneDir(config, repository),
|
|
87
|
+
pr: options.input.pr,
|
|
88
|
+
});
|
|
89
|
+
if (pullRequest.isCrossRepository) {
|
|
90
|
+
throw new Error(`PR #${pullRequest.number} is from a fork (cross-repository); crew open cannot fetch fork branches. Check the branch out locally, then run crew open --branch <name> --repo ${repository}.`);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
branch: pullRequest.branch,
|
|
94
|
+
title: pullRequest.title,
|
|
95
|
+
task: normalizePlainTaskId(options.taskOverride ?? `pr-${pullRequest.number}`),
|
|
96
|
+
url: pullRequest.url,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function rollback(arguments_) {
|
|
100
|
+
log(`Open failed; rolling back worktree ${arguments_.entry.repository}-${arguments_.entry.task}...`);
|
|
101
|
+
try {
|
|
102
|
+
await worktrees.teardown(arguments_.config, [arguments_.entry], { force: true });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
log(`Worktree teardown failed during rollback: ${errorMessage(error)}`);
|
|
106
|
+
}
|
|
107
|
+
for (const dir of [arguments_.promptDir, arguments_.srtSettingsDir]) {
|
|
108
|
+
if (dir !== undefined) {
|
|
109
|
+
try {
|
|
110
|
+
rmSync(dir, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// already gone
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export async function openWorkspace(config, options) {
|
|
119
|
+
const repository = resolveOpenRepository(config, options);
|
|
120
|
+
const repoDir = repositoryCloneDir(config, repository);
|
|
121
|
+
if (!existsSync(repoDir)) {
|
|
122
|
+
throw new Error(`Repository not found: ${repoDir}`);
|
|
123
|
+
}
|
|
124
|
+
const target = await resolveTarget(config, options, repository);
|
|
125
|
+
await failIfWorkspaceAlreadyLive(config, target.task, "opening");
|
|
126
|
+
failIfAlreadyTracked(config, target.task, repository);
|
|
127
|
+
const agent = options.agent ?? config.agents.default;
|
|
128
|
+
const definition = config.agents.definitions[agent];
|
|
129
|
+
if (definition === undefined) {
|
|
130
|
+
throw new Error(`Unknown agent: ${agent}`);
|
|
131
|
+
}
|
|
132
|
+
if (options.dryRun === true) {
|
|
133
|
+
log(`[dry-run] Would open ${target.task} on branch ${target.branch} in ${repository} (${agent})`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const { runner, networkEgress, sandboxName, workspaceKind, ensureReady } = await prepareAgentLaunch({
|
|
137
|
+
config,
|
|
138
|
+
agent,
|
|
139
|
+
definition,
|
|
140
|
+
purpose: "runs",
|
|
141
|
+
});
|
|
142
|
+
await ensureReady();
|
|
143
|
+
const created = await worktrees.open(config, {
|
|
144
|
+
repository,
|
|
145
|
+
task: target.task,
|
|
146
|
+
branch: target.branch,
|
|
147
|
+
});
|
|
148
|
+
const launchDir = resolveLaunchDir(config, repository, created.dir);
|
|
149
|
+
const omitPromptArgument = options.promptText === undefined;
|
|
150
|
+
const stagedPrompt = stagePromptText({
|
|
151
|
+
prefix: "groundcrew-open",
|
|
152
|
+
task: target.task,
|
|
153
|
+
text: options.promptText ?? "",
|
|
154
|
+
});
|
|
155
|
+
let srtSettingsDir;
|
|
156
|
+
try {
|
|
157
|
+
const prepareWorktreeCommand = resolvePrepareWorktreeCommand({
|
|
158
|
+
worktreeDir: launchDir,
|
|
159
|
+
defaultHooks: config.defaults.hooks,
|
|
160
|
+
});
|
|
161
|
+
const secretsFile = prepareWorktreeCommand === undefined ? undefined : stageBuildSecrets(stagedPrompt.directory);
|
|
162
|
+
let launchCommand;
|
|
163
|
+
({ launchCommand, srtSettingsDir } = composeAgentLaunch({
|
|
164
|
+
runner,
|
|
165
|
+
networkEgress,
|
|
166
|
+
task: target.task,
|
|
167
|
+
definition,
|
|
168
|
+
promptFile: stagedPrompt.file,
|
|
169
|
+
worktreeDir: created.dir,
|
|
170
|
+
workingDir: launchDir,
|
|
171
|
+
secretsFile,
|
|
172
|
+
prepareWorktreeCommand,
|
|
173
|
+
sandboxName,
|
|
174
|
+
workspaceKind,
|
|
175
|
+
omitPromptArgument,
|
|
176
|
+
}));
|
|
177
|
+
const launchCmd = stageWorkspaceLaunchCommand(stagedPrompt.directory, launchCommand);
|
|
178
|
+
await openAgentWorkspace({
|
|
179
|
+
config,
|
|
180
|
+
name: target.task,
|
|
181
|
+
cwd: launchDir,
|
|
182
|
+
command: launchCmd,
|
|
183
|
+
agent,
|
|
184
|
+
color: definition.color,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
await rollback({ config, entry: created, promptDir: stagedPrompt.directory, srtSettingsDir });
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
recordRunState({
|
|
192
|
+
config,
|
|
193
|
+
state: {
|
|
194
|
+
task: target.task,
|
|
195
|
+
repository,
|
|
196
|
+
agent,
|
|
197
|
+
worktreeDir: created.dir,
|
|
198
|
+
branchName: target.branch,
|
|
199
|
+
workspaceName: target.task,
|
|
200
|
+
state: "running",
|
|
201
|
+
title: target.title,
|
|
202
|
+
adoptedBranch: true,
|
|
203
|
+
...(target.url === undefined ? {} : { url: target.url }),
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
log(`${okMark()} "${target.task}" opened on branch ${target.branch} (${agent})`);
|
|
207
|
+
debug(` Worktree: ${launchDir}`);
|
|
208
|
+
if (omitPromptArgument) {
|
|
209
|
+
debug(" Launched interactively (no prompt).");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function readValue(argv, index, flag) {
|
|
213
|
+
const value = argv[index + 1];
|
|
214
|
+
if (value === undefined || value.startsWith("-")) {
|
|
215
|
+
throw new Error(`crew open: ${flag} requires a value\n${OPEN_USAGE}`);
|
|
216
|
+
}
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
function parseArguments(argv) {
|
|
220
|
+
const parsed = { dryRun: false };
|
|
221
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
222
|
+
const argument = argv[index];
|
|
223
|
+
/* v8 ignore next @preserve -- loop bound guarantees argv[index] is defined; the guard narrows the type */
|
|
224
|
+
if (argument === undefined) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
switch (argument) {
|
|
228
|
+
case "--repo": {
|
|
229
|
+
parsed.repository = readValue(argv, index, "--repo");
|
|
230
|
+
index += 1;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "--agent": {
|
|
234
|
+
parsed.agent = readValue(argv, index, "--agent");
|
|
235
|
+
index += 1;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "--prompt": {
|
|
239
|
+
parsed.promptText = readValue(argv, index, "--prompt");
|
|
240
|
+
index += 1;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case "--prompt-file": {
|
|
244
|
+
parsed.promptFile = readValue(argv, index, "--prompt-file");
|
|
245
|
+
index += 1;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
case "--branch": {
|
|
249
|
+
parsed.branch = readValue(argv, index, "--branch");
|
|
250
|
+
index += 1;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "--task": {
|
|
254
|
+
parsed.task = readValue(argv, index, "--task");
|
|
255
|
+
index += 1;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case "--dry-run": {
|
|
259
|
+
parsed.dryRun = true;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
default: {
|
|
263
|
+
if (argument.startsWith("-")) {
|
|
264
|
+
throw new Error(`crew open: unknown option: ${argument}\n${OPEN_USAGE}`);
|
|
265
|
+
}
|
|
266
|
+
if (parsed.positional !== undefined) {
|
|
267
|
+
throw new Error(`crew open: unexpected extra argument: ${argument}\n${OPEN_USAGE}`);
|
|
268
|
+
}
|
|
269
|
+
parsed.positional = argument;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return parsed;
|
|
274
|
+
}
|
|
275
|
+
function resolvePromptText(parsed) {
|
|
276
|
+
if (parsed.promptText !== undefined && parsed.promptFile !== undefined) {
|
|
277
|
+
throw new Error(`crew open: --prompt and --prompt-file are mutually exclusive\n${OPEN_USAGE}`);
|
|
278
|
+
}
|
|
279
|
+
if (parsed.promptText !== undefined) {
|
|
280
|
+
return parsed.promptText;
|
|
281
|
+
}
|
|
282
|
+
if (parsed.promptFile !== undefined) {
|
|
283
|
+
try {
|
|
284
|
+
return readFileSync(parsed.promptFile, "utf8");
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
throw new Error(`crew open: could not read --prompt-file ${parsed.promptFile}`, {
|
|
288
|
+
cause: error,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
function toOpenWorkspaceOptions(parsed) {
|
|
295
|
+
if (parsed.branch !== undefined && parsed.positional !== undefined) {
|
|
296
|
+
throw new Error(`crew open: pass either a PR or --branch, not both\n${OPEN_USAGE}`);
|
|
297
|
+
}
|
|
298
|
+
const promptText = resolvePromptText(parsed);
|
|
299
|
+
const common = {
|
|
300
|
+
...(parsed.agent === undefined ? {} : { agent: parsed.agent }),
|
|
301
|
+
...(promptText === undefined ? {} : { promptText }),
|
|
302
|
+
...(parsed.task === undefined ? {} : { taskOverride: parsed.task }),
|
|
303
|
+
dryRun: parsed.dryRun,
|
|
304
|
+
};
|
|
305
|
+
if (parsed.branch !== undefined) {
|
|
306
|
+
if (parsed.repository === undefined) {
|
|
307
|
+
throw new Error(`crew open: --branch requires --repo <owner/repo>\n${OPEN_USAGE}`);
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
input: { kind: "branch", branch: parsed.branch },
|
|
311
|
+
repository: parsed.repository,
|
|
312
|
+
...common,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (parsed.positional === undefined) {
|
|
316
|
+
throw new Error(`crew open: a PR (number or URL) or --branch is required\n${OPEN_USAGE}`);
|
|
317
|
+
}
|
|
318
|
+
const reference = parsePullRequestReference(parsed.positional);
|
|
319
|
+
if (parsed.repository === undefined && reference.repository === undefined) {
|
|
320
|
+
throw new Error(`crew open: --repo <owner/repo> is required when the PR is given by number\n${OPEN_USAGE}`);
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
input: {
|
|
324
|
+
kind: "pr",
|
|
325
|
+
pr: reference.pr,
|
|
326
|
+
...(reference.repository === undefined ? {} : { repositoryHint: reference.repository }),
|
|
327
|
+
},
|
|
328
|
+
...(parsed.repository === undefined ? {} : { repository: parsed.repository }),
|
|
329
|
+
...common,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
export function parseOpenWorkspaceArgs(argv) {
|
|
333
|
+
return toOpenWorkspaceOptions(parseArguments(argv));
|
|
334
|
+
}
|
|
335
|
+
export async function openWorkspaceCli(argv) {
|
|
336
|
+
const options = parseOpenWorkspaceArgs(argv);
|
|
337
|
+
const config = await loadConfig();
|
|
338
|
+
await openWorkspace(config, options);
|
|
339
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoEH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAiBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoEH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAiBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiE7E"}
|
|
@@ -94,6 +94,7 @@ export async function orchestrate(options) {
|
|
|
94
94
|
const cleaner = createCleaner({ config });
|
|
95
95
|
const reviewer = createReviewer({
|
|
96
96
|
board,
|
|
97
|
+
config,
|
|
97
98
|
findPullRequests: findPullRequestsForBranch,
|
|
98
99
|
});
|
|
99
100
|
const dispatcher = createDispatcher({ config, board });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAiBnE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAiBnE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AA2ID,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA4Ff;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
|
|
@@ -10,7 +10,7 @@ import { removeStagedPrompt, stageBuildSecrets, stagePromptText, stageWorkspaceL
|
|
|
10
10
|
import { taskSourceWritePathsForCompletion } from "../lib/taskSourceFilesystem.js";
|
|
11
11
|
import { naturalIdFromCanonical, toCanonicalId } from "../lib/taskSource.js";
|
|
12
12
|
import { errorMessage, log } from "../lib/util.js";
|
|
13
|
-
import {
|
|
13
|
+
import { failIfWorkspaceAlreadyLive } from "../lib/workspaceLiveness.js";
|
|
14
14
|
import { resolveLaunchDir, worktrees } from "../lib/worktrees.js";
|
|
15
15
|
function parseArguments(argv) {
|
|
16
16
|
const [task, ...extras] = argv;
|
|
@@ -60,7 +60,10 @@ async function contextFromState(config, task, state, worktree) {
|
|
|
60
60
|
task,
|
|
61
61
|
repository: state.repository,
|
|
62
62
|
agent: state.agent,
|
|
63
|
-
|
|
63
|
+
// Prefer the branch recorded in run state: `crew open` worktrees check out
|
|
64
|
+
// an existing PR branch that diverges from the `<prefix>-<task>` name the
|
|
65
|
+
// worktree-dir scan derives, and run state is the source of truth for it.
|
|
66
|
+
worktree: { ...worktree, branchName: state.branchName },
|
|
64
67
|
title: details?.title ?? task.toUpperCase(),
|
|
65
68
|
description: details?.description ?? "",
|
|
66
69
|
completionTaskId,
|
|
@@ -113,25 +116,15 @@ function renderResumePrompt(context) {
|
|
|
113
116
|
"Run the repository's documented verification before stopping, then leave the branch ready or open a PR when possible.",
|
|
114
117
|
].join("\n");
|
|
115
118
|
}
|
|
116
|
-
async function failIfWorkspaceAlreadyLive(config, task) {
|
|
117
|
-
const probe = await workspaces.probe(config);
|
|
118
|
-
if (probe.kind === "unavailable") {
|
|
119
|
-
const detail = probe.error === undefined ? "" : `: ${errorMessage(probe.error)}`;
|
|
120
|
-
throw new Error(`Could not verify whether workspace for ${task} is already live${detail}. Retry or inspect the workspace backend manually before resuming.`);
|
|
121
|
-
}
|
|
122
|
-
if (probe.names.has(task)) {
|
|
123
|
-
throw new Error(`Workspace for ${task} is already live; attach to it instead of resuming.`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
119
|
export async function resumeWorkspace(config, options) {
|
|
127
120
|
const task = options.task.toLowerCase();
|
|
128
|
-
await failIfWorkspaceAlreadyLive(config, task);
|
|
121
|
+
await failIfWorkspaceAlreadyLive(config, task, "resuming");
|
|
129
122
|
const context = await buildResumeContext(config, task);
|
|
130
123
|
const definition = config.agents.definitions[context.agent];
|
|
131
124
|
if (definition === undefined) {
|
|
132
125
|
throw new Error(`Unknown agent: ${context.agent}`);
|
|
133
126
|
}
|
|
134
|
-
const { runner, sandboxName, workspaceKind, ensureReady } = await prepareAgentLaunch({
|
|
127
|
+
const { runner, networkEgress, sandboxName, workspaceKind, ensureReady } = await prepareAgentLaunch({
|
|
135
128
|
config,
|
|
136
129
|
agent: context.agent,
|
|
137
130
|
definition,
|
|
@@ -162,6 +155,7 @@ export async function resumeWorkspace(config, options) {
|
|
|
162
155
|
: undefined;
|
|
163
156
|
({ launchCommand, srtSettingsDir } = composeAgentLaunch({
|
|
164
157
|
runner,
|
|
158
|
+
networkEgress,
|
|
165
159
|
task,
|
|
166
160
|
definition,
|
|
167
161
|
promptFile: stagedPrompt.file,
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* `Cleaner`.
|
|
23
23
|
*/
|
|
24
24
|
import type { Board } from "../lib/board.ts";
|
|
25
|
+
import type { ResolvedConfig } from "../lib/config.ts";
|
|
25
26
|
import type { PullRequestSummary } from "../lib/pullRequests.ts";
|
|
26
27
|
import { type BoardState } from "../lib/taskSource.ts";
|
|
27
28
|
import type { WorktreeEntry } from "../lib/worktrees.ts";
|
|
@@ -39,6 +40,7 @@ export type FindPullRequests = (arguments_: {
|
|
|
39
40
|
interface ReviewerDeps {
|
|
40
41
|
board: Board;
|
|
41
42
|
findPullRequests: FindPullRequests;
|
|
43
|
+
config?: ResolvedConfig;
|
|
42
44
|
}
|
|
43
45
|
/** Per-tick inputs, mirroring the other orchestrator steps' shape. */
|
|
44
46
|
interface ReviewArguments {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewer.d.ts","sourceRoot":"","sources":["../../src/commands/reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"reviewer.d.ts","sourceRoot":"","sources":["../../src/commands/reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,UAAU,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAC;AAE7C,UAAU,YAAY;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,sEAAsE;AACtE,UAAU,eAAe;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AA+CD,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CA8H3D"}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { naturalIdFromCanonical, } from "../lib/taskSource.js";
|
|
25
25
|
import { debug, errorMessage, log, logEvent } from "../lib/util.js";
|
|
26
|
+
import { effectiveBranchName } from "../lib/worktreeRunState.js";
|
|
26
27
|
// Maps a worktree's PRs to the transition its task should make. A merged PR
|
|
27
28
|
// means the work landed → done; an open PR on an in-progress task means it's
|
|
28
29
|
// up for review. `merged` wins over `open`. An open PR on an already in-review
|
|
@@ -52,7 +53,7 @@ function matchingWorktreeEntries(arguments_) {
|
|
|
52
53
|
return worktreeEntries.filter((entry) => entry.task === task && entry.repository === issue.repository);
|
|
53
54
|
}
|
|
54
55
|
export function createReviewer(deps) {
|
|
55
|
-
const { board, findPullRequests } = deps;
|
|
56
|
+
const { board, config, findPullRequests } = deps;
|
|
56
57
|
async function runOnce(arguments_) {
|
|
57
58
|
const { state, worktreeEntries, dryRun, signal } = arguments_;
|
|
58
59
|
const candidates = state.issues.filter((issue) => issue.status === "in-progress" || issue.status === "in-review");
|
|
@@ -65,6 +66,7 @@ export function createReviewer(deps) {
|
|
|
65
66
|
issue,
|
|
66
67
|
worktreeEntries,
|
|
67
68
|
dryRun,
|
|
69
|
+
...(config === undefined ? {} : { config }),
|
|
68
70
|
...(signal === undefined ? {} : { signal }),
|
|
69
71
|
});
|
|
70
72
|
}
|
|
@@ -74,10 +76,13 @@ export function createReviewer(deps) {
|
|
|
74
76
|
// Unsupported writebacks are skipped without claiming success and may retry
|
|
75
77
|
// on later ticks.
|
|
76
78
|
async function advanceIfReviewable(arguments_) {
|
|
77
|
-
const { issue, worktreeEntries, dryRun, signal } = arguments_;
|
|
79
|
+
const { issue, worktreeEntries, dryRun, config: reviewerConfig, signal } = arguments_;
|
|
78
80
|
const task = naturalIdFromCanonical(issue.id);
|
|
79
81
|
const entries = matchingWorktreeEntries({ issue, worktreeEntries, task });
|
|
80
82
|
for (const entry of entries) {
|
|
83
|
+
const branchName = reviewerConfig === undefined
|
|
84
|
+
? entry.branchName
|
|
85
|
+
: effectiveBranchName({ config: reviewerConfig, entry });
|
|
81
86
|
// The injected lookup is contracted never to reject (failures resolve to
|
|
82
87
|
// []), but we still guard it so one bad lookup can never abort the tick
|
|
83
88
|
// and starve the other candidates. A failure means "can't tell yet" →
|
|
@@ -87,12 +92,12 @@ export function createReviewer(deps) {
|
|
|
87
92
|
// oxlint-disable-next-line no-await-in-loop -- a task almost always has one worktree; sequential lookups are fine.
|
|
88
93
|
pullRequests = await findPullRequests({
|
|
89
94
|
cwd: entry.dir,
|
|
90
|
-
branchName
|
|
95
|
+
branchName,
|
|
91
96
|
...(signal === undefined ? {} : { signal }),
|
|
92
97
|
});
|
|
93
98
|
}
|
|
94
99
|
catch (error) {
|
|
95
|
-
debug(`PR lookup failed for ${task} (${
|
|
100
|
+
debug(`PR lookup failed for ${task} (${branchName}): ${errorMessage(error)}`);
|
|
96
101
|
continue;
|
|
97
102
|
}
|
|
98
103
|
const transition = intendedTransition(pullRequests, issue.status);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyBnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyBnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6FAA6F;IAC7F,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAuBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CAqJf;AAgJD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAkDf"}
|
|
@@ -34,7 +34,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
|
|
|
34
34
|
if (!definition) {
|
|
35
35
|
throw new Error(`Unknown agent: ${agent}`);
|
|
36
36
|
}
|
|
37
|
-
const { runner, sandboxName, workspaceKind, ensureReady } = await prepareAgentLaunch({
|
|
37
|
+
const { runner, networkEgress, sandboxName, workspaceKind, ensureReady } = await prepareAgentLaunch({
|
|
38
38
|
config,
|
|
39
39
|
agent,
|
|
40
40
|
definition,
|
|
@@ -99,6 +99,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
|
|
|
99
99
|
: undefined;
|
|
100
100
|
const { launchCommand, srtSettingsDir: stagedSrtSettingsDir } = composeAgentLaunch({
|
|
101
101
|
runner,
|
|
102
|
+
networkEgress,
|
|
102
103
|
task,
|
|
103
104
|
definition,
|
|
104
105
|
promptFile: stagedPrompt.file,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA8qBD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
|
package/dist/commands/status.js
CHANGED
|
@@ -8,6 +8,7 @@ import { isGroundcrewIssue, naturalIdFromCanonical, } from "../lib/taskSource.js
|
|
|
8
8
|
import { errorMessage, withLogOutputSuppressed, writeOutput } from "../lib/util.js";
|
|
9
9
|
import { workspaces } from "../lib/workspaces.js";
|
|
10
10
|
import { worktrees } from "../lib/worktrees.js";
|
|
11
|
+
import { effectiveBranchNameFromRunState } from "../lib/worktreeRunState.js";
|
|
11
12
|
const RECENT_LOG_LINE_COUNT = 10;
|
|
12
13
|
function escapeRegExp(value) {
|
|
13
14
|
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
@@ -40,7 +41,9 @@ async function writeTaskWorktrees(config, task) {
|
|
|
40
41
|
writeOutput("(none)");
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
44
|
+
const runState = readRunState(config, task);
|
|
43
45
|
for (const entry of entries) {
|
|
46
|
+
const branchName = effectiveBranchNameFromRunState({ entry, runState });
|
|
44
47
|
// oxlint-disable-next-line no-await-in-loop -- status output is easier to read in worktree order.
|
|
45
48
|
const dirtiness = await worktrees.probeWorkingTree({
|
|
46
49
|
worktreeDir: entry.dir,
|
|
@@ -48,10 +51,10 @@ async function writeTaskWorktrees(config, task) {
|
|
|
48
51
|
// oxlint-disable-next-line no-await-in-loop -- one gh lookup per worktree is acceptable; multi-worktree-per-task is rare.
|
|
49
52
|
const prs = await findPullRequestsForBranch({
|
|
50
53
|
cwd: entry.dir,
|
|
51
|
-
branchName
|
|
54
|
+
branchName,
|
|
52
55
|
});
|
|
53
56
|
writeOutput(`- ${entry.repository} ${entry.kind}`);
|
|
54
|
-
writeOutput(` branch: ${
|
|
57
|
+
writeOutput(` branch: ${branchName}`);
|
|
55
58
|
writeOutput(` dir: ${entry.dir}`);
|
|
56
59
|
writeOutput(` git: ${formatDirtiness(dirtiness)}`);
|
|
57
60
|
if (prs.length > 0) {
|
|
@@ -325,14 +328,22 @@ async function writeInventoryWorktrees(config, probe, statusByTask) {
|
|
|
325
328
|
writeOutput("(none)");
|
|
326
329
|
return;
|
|
327
330
|
}
|
|
328
|
-
const accessHints = await collectAccessHints(config, entries);
|
|
329
|
-
const pullRequests = await collectPullRequests(entries);
|
|
330
331
|
const runStates = new Map();
|
|
331
|
-
const
|
|
332
|
-
for (const [index, entry] of entries.entries()) {
|
|
332
|
+
for (const entry of entries) {
|
|
333
333
|
if (!runStates.has(entry.task)) {
|
|
334
334
|
runStates.set(entry.task, readRunState(config, entry.task));
|
|
335
335
|
}
|
|
336
|
+
}
|
|
337
|
+
const accessHints = await collectAccessHints(config, entries);
|
|
338
|
+
const pullRequests = await collectPullRequests(entries.map((entry) => ({
|
|
339
|
+
dir: entry.dir,
|
|
340
|
+
branchName: effectiveBranchNameFromRunState({
|
|
341
|
+
entry,
|
|
342
|
+
runState: runStates.get(entry.task),
|
|
343
|
+
}),
|
|
344
|
+
})));
|
|
345
|
+
const now = new Date();
|
|
346
|
+
for (const [index, entry] of entries.entries()) {
|
|
336
347
|
const runState = runStates.get(entry.task);
|
|
337
348
|
const accessHint = accessHints.get(entry.task);
|
|
338
349
|
// `collectPullRequests` guarantees an entry for every worktree dir seen
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AA+yBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB3D"}
|