@clipboard-health/groundcrew 4.52.7 → 4.52.9
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 +3 -0
- package/crew.config.example.ts +14 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/commands/completions.d.ts.map +1 -1
- package/dist/commands/completions.js +17 -0
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +3 -0
- package/dist/commands/prStageSync.d.ts +74 -0
- package/dist/commands/prStageSync.d.ts.map +1 -0
- package/dist/commands/prStageSync.js +397 -0
- package/dist/commands/stage.d.ts +9 -0
- package/dist/commands/stage.d.ts.map +1 -0
- package/dist/commands/stage.js +138 -0
- package/dist/lib/cmuxAdapter.d.ts +15 -0
- package/dist/lib/cmuxAdapter.d.ts.map +1 -1
- package/dist/lib/cmuxAdapter.js +62 -1
- package/dist/lib/cmuxStatusFields.d.ts +33 -0
- package/dist/lib/cmuxStatusFields.d.ts.map +1 -0
- package/dist/lib/cmuxStatusFields.js +62 -0
- package/dist/lib/config.d.ts +42 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +51 -0
- package/dist/lib/prStageRules.d.ts +40 -0
- package/dist/lib/prStageRules.d.ts.map +1 -0
- package/dist/lib/prStageRules.js +108 -0
- package/dist/lib/pullRequestDetails.d.ts +26 -0
- package/dist/lib/pullRequestDetails.d.ts.map +1 -0
- package/dist/lib/pullRequestDetails.js +114 -0
- package/dist/lib/pullRequests.d.ts +7 -0
- package/dist/lib/pullRequests.d.ts.map +1 -1
- package/dist/lib/pullRequests.js +27 -15
- package/docs/commands.md +12 -0
- package/docs/configuration.md +36 -33
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -123,6 +123,9 @@ crew cleanup [--force] <TASK> # tear down every worktre
|
|
|
123
123
|
crew cleanup [--force] --all # tear down every idle worktree (no live workspace)
|
|
124
124
|
crew upgrade [<version>] # reinstall crew globally through npm
|
|
125
125
|
crew completions <bash|zsh|fish> # print a shell completion script
|
|
126
|
+
crew stage label-add <pr-url> <label> # add a configured pr-stage-sync gating label
|
|
127
|
+
crew stage label-remove <pr-url> <label> # remove a configured pr-stage-sync gating label
|
|
128
|
+
crew stage refresh # run one pr-stage-sync pass now
|
|
126
129
|
```
|
|
127
130
|
|
|
128
131
|
See [command details](./docs/commands.md) for status output, doctor behavior, and the stop/resume workflow.
|
package/crew.config.example.ts
CHANGED
|
@@ -224,4 +224,18 @@ export default {
|
|
|
224
224
|
// // evidence with it. Default: `${XDG_STATE_HOME:-~/.local/state}/groundcrew/groundcrew.log`.
|
|
225
225
|
// file: "~/Library/Logs/groundcrew/groundcrew.log",
|
|
226
226
|
// },
|
|
227
|
+
//
|
|
228
|
+
// // Paints each task's cmux sidebar with its pull request's review stage
|
|
229
|
+
// // (crew_stage), gating labels (crew_labels), and ticket id (crew_ticket)
|
|
230
|
+
// // once per watch tick. Opt-in and cmux-only; other workspaceKind backends
|
|
231
|
+
// // no-op. The two gating labels below are created on the PR's repo on
|
|
232
|
+
// // first use via `crew stage label-add`/`label-remove`; a PR sits at
|
|
233
|
+
// // `my_review` until `selfReviewed` is applied and `needs_testing` until
|
|
234
|
+
// // `tested` is applied, ahead of CI and review-decision state.
|
|
235
|
+
// cmux: {
|
|
236
|
+
// prStages: {
|
|
237
|
+
// enabled: true,
|
|
238
|
+
// labels: { selfReviewed: "self-reviewed", tested: "tested" },
|
|
239
|
+
// },
|
|
240
|
+
// },
|
|
227
241
|
} satisfies Config;
|
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":"AA6PA;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,SAAS,MAAM,EAAE,CAIvD;AAiDD,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCvD"}
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { orchestrate } from "./commands/orchestrator.js";
|
|
|
9
9
|
import { resumeWorkspaceCli } from "./commands/resumeWorkspace.js";
|
|
10
10
|
import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
|
|
11
11
|
import { sourceCli } from "./commands/source.js";
|
|
12
|
+
import { stageCli } from "./commands/stage.js";
|
|
12
13
|
import { statusCli } from "./commands/status.js";
|
|
13
14
|
import { taskCli } from "./commands/task.js";
|
|
14
15
|
import { createDefaultUpgradeCliOptions, upgradeCli } from "./commands/upgrade.js";
|
|
@@ -192,6 +193,11 @@ const SUBCOMMANDS = {
|
|
|
192
193
|
usage: "<bash|zsh|fish>",
|
|
193
194
|
invoke: completionsCli,
|
|
194
195
|
},
|
|
196
|
+
stage: {
|
|
197
|
+
summary: "Manage pr-stage-sync's gating labels, or run a sync pass now",
|
|
198
|
+
usage: "<label-add|label-remove|refresh> [...]",
|
|
199
|
+
invoke: stageCli,
|
|
200
|
+
},
|
|
195
201
|
};
|
|
196
202
|
function isVisibleCommand(command) {
|
|
197
203
|
return command.hidden !== true && command.deprecated !== true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/commands/completions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,+EAA+E;AAC/E,KAAK,aAAa,GACd;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEhC,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACpD,kEAAkE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,eAAO,MAAM,gBAAgB,YAAI,MAAM,EAAE,KAAK,EAAE,MAAM,CAAU,CAAC;AAEjE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AA2B/D;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,iBAAiB,
|
|
1
|
+
{"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/commands/completions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,+EAA+E;AAC/E,KAAK,aAAa,GACd;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEhC,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACpD,kEAAkE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,eAAO,MAAM,gBAAgB,YAAI,MAAM,EAAE,KAAK,EAAE,MAAM,CAAU,CAAC;AAEjE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AA2B/D;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,iBAAiB,EAsKvD,CAAC;AAqXF,2DAA2D;AAC3D,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEtE;AAQD,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAUlE"}
|
|
@@ -186,6 +186,23 @@ export const COMPLETION_SPEC = [
|
|
|
186
186
|
summary: "Print a shell completion script",
|
|
187
187
|
argValues: SUPPORTED_SHELLS,
|
|
188
188
|
},
|
|
189
|
+
{
|
|
190
|
+
name: "stage",
|
|
191
|
+
summary: "Manage pr-stage-sync's gating labels, or run a sync pass now",
|
|
192
|
+
subcommands: [
|
|
193
|
+
{
|
|
194
|
+
name: "label-add",
|
|
195
|
+
summary: "Add a configured gating label to a pull request",
|
|
196
|
+
options: [],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "label-remove",
|
|
200
|
+
summary: "Remove a configured gating label from a pull request",
|
|
201
|
+
options: [],
|
|
202
|
+
},
|
|
203
|
+
{ name: "refresh", summary: "Run one pr-stage-sync pass now" },
|
|
204
|
+
],
|
|
205
|
+
},
|
|
189
206
|
];
|
|
190
207
|
/** Global flags valid only as the first token. */
|
|
191
208
|
const TOP_LEVEL_GLOBALS = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAqEH,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,CAoE7E"}
|
|
@@ -14,6 +14,7 @@ import { errorMessage, log, sleep, writeOutput } from "../lib/util.js";
|
|
|
14
14
|
import { worktrees } from "../lib/worktrees.js";
|
|
15
15
|
import { createCleaner } from "./cleaner.js";
|
|
16
16
|
import { createDispatcher } from "./dispatcher.js";
|
|
17
|
+
import { createPrStageSync, createPrStageSyncDeps } from "./prStageSync.js";
|
|
17
18
|
import { createReviewer } from "./reviewer.js";
|
|
18
19
|
const RATE_LIMIT_DELAY_MS = 60_000;
|
|
19
20
|
const RETRY_BASE_DELAY_MS = 1000;
|
|
@@ -98,6 +99,7 @@ export async function orchestrate(options) {
|
|
|
98
99
|
findPullRequests: findPullRequestsForBranch,
|
|
99
100
|
});
|
|
100
101
|
const dispatcher = createDispatcher({ config, board });
|
|
102
|
+
const prStageSync = createPrStageSync(createPrStageSyncDeps(config));
|
|
101
103
|
// Folded into the dispatcher's idle log lines in watch mode so each idle
|
|
102
104
|
// tick prints one combined line instead of "<reason>" + "Next poll in Xs".
|
|
103
105
|
const idleSuffix = options.watch
|
|
@@ -121,6 +123,7 @@ export async function orchestrate(options) {
|
|
|
121
123
|
});
|
|
122
124
|
await reviewer.runOnce(tickArguments);
|
|
123
125
|
await cleaner.runOnce(tickArguments);
|
|
126
|
+
await prStageSync.runOnce(tickArguments);
|
|
124
127
|
};
|
|
125
128
|
await (options.watch ? runWatchLoop(tick, config) : tick());
|
|
126
129
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-tick sync that paints each tracked task's cmux sidebar with its ticket
|
|
3
|
+
* id, and — when `cmux.prStages.enabled` — its pull request's review stage
|
|
4
|
+
* and gating labels too. Replaces the standalone `crew-pr-stages` bash
|
|
5
|
+
* poller: same stage/ticket derivation rules (`../lib/prStageRules.ts`),
|
|
6
|
+
* same status-key contract the installed sidebar reads, but PRs are
|
|
7
|
+
* resolved from the task's own worktree branch
|
|
8
|
+
* (`findPullRequestsForBranchOrThrow`) instead of cmux's own git-branch PR
|
|
9
|
+
* detection.
|
|
10
|
+
*
|
|
11
|
+
* `crew_ticket` is written for every matched cmux task workspace regardless
|
|
12
|
+
* of the `cmux.prStages.enabled` opt-in, since a custom sidebar may read it
|
|
13
|
+
* independent of PR-stage sync; the opt-in only gates the `gh`-backed stage
|
|
14
|
+
* and label writes (and the `crew_poller_heartbeat` that signals they're
|
|
15
|
+
* running). `cmuxAdapter.ts` also writes it once at workspace-creation time,
|
|
16
|
+
* using the same `deriveTicket` rule, so a brand-new workspace shows a
|
|
17
|
+
* ticket pill before the first tick without disagreeing with it — this
|
|
18
|
+
* module keeps the value current afterward.
|
|
19
|
+
*
|
|
20
|
+
* Runs after the other tick steps in `orchestrator.ts`. Every failure is
|
|
21
|
+
* caught and logged here — a flaky `gh` call or a single misbehaving
|
|
22
|
+
* workspace must never break the watch loop or starve the other steps.
|
|
23
|
+
*/
|
|
24
|
+
import { type CmuxWorkspaceSummary } from "../lib/cmuxAdapter.ts";
|
|
25
|
+
import { type CmuxStatusWrite } from "../lib/cmuxStatusFields.ts";
|
|
26
|
+
import type { ResolvedConfig } from "../lib/config.ts";
|
|
27
|
+
import { type PullRequestDetail } from "../lib/pullRequestDetails.ts";
|
|
28
|
+
import { type PullRequestSummary } from "../lib/pullRequests.ts";
|
|
29
|
+
import type { WorktreeEntry } from "../lib/worktrees.ts";
|
|
30
|
+
export type FindPullRequests = (arguments_: {
|
|
31
|
+
cwd: string;
|
|
32
|
+
branchName: string;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}) => Promise<readonly PullRequestSummary[]>;
|
|
35
|
+
export type FetchPullRequestDetails = (arguments_: {
|
|
36
|
+
urls: readonly string[];
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}) => Promise<Map<string, PullRequestDetail | undefined>>;
|
|
39
|
+
export type ListCmuxWorkspaces = (signal?: AbortSignal) => Promise<CmuxWorkspaceSummary[] | undefined>;
|
|
40
|
+
export type ReadCmuxStatus = (workspaceId: string, signal?: AbortSignal) => Promise<Map<string, string>>;
|
|
41
|
+
export type WriteCmuxStatus = (workspaceId: string, write: CmuxStatusWrite, signal?: AbortSignal) => Promise<void>;
|
|
42
|
+
export type IsCmuxAdapterActive = (signal?: AbortSignal) => Promise<boolean>;
|
|
43
|
+
export interface PrStageSyncDeps {
|
|
44
|
+
config: ResolvedConfig;
|
|
45
|
+
findPullRequests: FindPullRequests;
|
|
46
|
+
fetchPullRequestDetails: FetchPullRequestDetails;
|
|
47
|
+
listCmuxWorkspaces: ListCmuxWorkspaces;
|
|
48
|
+
readCmuxStatus: ReadCmuxStatus;
|
|
49
|
+
writeCmuxStatus: WriteCmuxStatus;
|
|
50
|
+
isCmuxAdapterActive: IsCmuxAdapterActive;
|
|
51
|
+
}
|
|
52
|
+
export interface PrStageSyncTickArgs {
|
|
53
|
+
worktreeEntries: readonly WorktreeEntry[];
|
|
54
|
+
/** Orchestrator `--dry-run`; only `runOnce` honors it — `syncOnce` (`crew stage refresh`) always writes. */
|
|
55
|
+
dryRun?: boolean;
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}
|
|
58
|
+
export interface PrStageSync {
|
|
59
|
+
/**
|
|
60
|
+
* The orchestrator tick step. A dry run skips everything. Otherwise
|
|
61
|
+
* `crew_ticket` is always written for matched cmux task workspaces; stage,
|
|
62
|
+
* labels, and the heartbeat are additionally synced only when
|
|
63
|
+
* `config.cmux.prStages.enabled` is true.
|
|
64
|
+
*/
|
|
65
|
+
runOnce: (arguments_: PrStageSyncTickArgs) => Promise<void>;
|
|
66
|
+
/** Ungated (still requires the cmux adapter): `crew stage refresh` and the label CLI's trailing sync. */
|
|
67
|
+
syncOnce: (arguments_: PrStageSyncTickArgs) => Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
/** Whether the resolved workspace backend for this config is cmux — every other adapter no-ops. */
|
|
70
|
+
export declare function isCmuxAdapterActive(config: ResolvedConfig, signal?: AbortSignal): Promise<boolean>;
|
|
71
|
+
export declare function createPrStageSync(deps: PrStageSyncDeps): PrStageSync;
|
|
72
|
+
/** Wires the real cmux/gh-backed implementations behind `PrStageSyncDeps`. */
|
|
73
|
+
export declare function createPrStageSyncDeps(config: ResolvedConfig): PrStageSyncDeps;
|
|
74
|
+
//# sourceMappingURL=prStageSync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prStageSync.d.ts","sourceRoot":"","sources":["../../src/commands/prStageSync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,KAAK,oBAAoB,EAA8B,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EACL,KAAK,eAAe,EAKrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AASnG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD,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,MAAM,MAAM,uBAAuB,GAAG,CAAC,UAAU,EAAE;IACjD,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG,CAC/B,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,oBAAoB,EAAE,GAAG,SAAS,CAAC,CAAC;AACjD,MAAM,MAAM,cAAc,GAAG,CAC3B,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAClC,MAAM,MAAM,eAAe,GAAG,CAC5B,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,EACtB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,IAAI,CAAC,CAAC;AACnB,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,uBAAuB,EAAE,uBAAuB,CAAC;IACjD,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,cAAc,EAAE,cAAc,CAAC;IAC/B,eAAe,EAAE,eAAe,CAAC;IACjC,mBAAmB,EAAE,mBAAmB,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,4GAA4G;IAC5G,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,OAAO,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,yGAAyG;IACzG,QAAQ,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AAED,mGAAmG;AACnG,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAUlB;AA2QD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CA0KpE;AAED,8EAA8E;AAC9E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,eAAe,CAU7E"}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-tick sync that paints each tracked task's cmux sidebar with its ticket
|
|
3
|
+
* id, and — when `cmux.prStages.enabled` — its pull request's review stage
|
|
4
|
+
* and gating labels too. Replaces the standalone `crew-pr-stages` bash
|
|
5
|
+
* poller: same stage/ticket derivation rules (`../lib/prStageRules.ts`),
|
|
6
|
+
* same status-key contract the installed sidebar reads, but PRs are
|
|
7
|
+
* resolved from the task's own worktree branch
|
|
8
|
+
* (`findPullRequestsForBranchOrThrow`) instead of cmux's own git-branch PR
|
|
9
|
+
* detection.
|
|
10
|
+
*
|
|
11
|
+
* `crew_ticket` is written for every matched cmux task workspace regardless
|
|
12
|
+
* of the `cmux.prStages.enabled` opt-in, since a custom sidebar may read it
|
|
13
|
+
* independent of PR-stage sync; the opt-in only gates the `gh`-backed stage
|
|
14
|
+
* and label writes (and the `crew_poller_heartbeat` that signals they're
|
|
15
|
+
* running). `cmuxAdapter.ts` also writes it once at workspace-creation time,
|
|
16
|
+
* using the same `deriveTicket` rule, so a brand-new workspace shows a
|
|
17
|
+
* ticket pill before the first tick without disagreeing with it — this
|
|
18
|
+
* module keeps the value current afterward.
|
|
19
|
+
*
|
|
20
|
+
* Runs after the other tick steps in `orchestrator.ts`. Every failure is
|
|
21
|
+
* caught and logged here — a flaky `gh` call or a single misbehaving
|
|
22
|
+
* workspace must never break the watch loop or starve the other steps.
|
|
23
|
+
*/
|
|
24
|
+
import { listCmuxWorkspaceSummaries } from "../lib/cmuxAdapter.js";
|
|
25
|
+
import { readCmuxStatus, TICKET_KEY, TICKET_PRIORITY, writeCmuxStatus, } from "../lib/cmuxStatusFields.js";
|
|
26
|
+
import { detectHostCapabilities } from "../lib/host.js";
|
|
27
|
+
import { fetchPullRequestDetails } from "../lib/pullRequestDetails.js";
|
|
28
|
+
import { findPullRequestsForBranchOrThrow } from "../lib/pullRequests.js";
|
|
29
|
+
import { deriveTicket, derivePrStage, managedLabelsField, } from "../lib/prStageRules.js";
|
|
30
|
+
import { debug, errorMessage, logEvent, readEnvironmentVariable } from "../lib/util.js";
|
|
31
|
+
import { resolveWorkspaceKind } from "../lib/workspaces.js";
|
|
32
|
+
import { effectiveBranchName } from "../lib/worktreeRunState.js";
|
|
33
|
+
const FLOW = "pr-stage-sync";
|
|
34
|
+
const STAGE_KEY = "crew_stage";
|
|
35
|
+
const STAGE_PRIORITY = -10;
|
|
36
|
+
const HEARTBEAT_KEY = "crew_poller_heartbeat";
|
|
37
|
+
const HEARTBEAT_PRIORITY = -12;
|
|
38
|
+
const LABELS_KEY = "crew_labels";
|
|
39
|
+
const LABELS_PRIORITY = -13;
|
|
40
|
+
const CMUX_WORKSPACE_ID_ENV = "CMUX_WORKSPACE_ID";
|
|
41
|
+
/** Whether the resolved workspace backend for this config is cmux — every other adapter no-ops. */
|
|
42
|
+
export async function isCmuxAdapterActive(config, signal) {
|
|
43
|
+
try {
|
|
44
|
+
const { resolved } = resolveWorkspaceKind({
|
|
45
|
+
config,
|
|
46
|
+
host: await detectHostCapabilities(signal),
|
|
47
|
+
});
|
|
48
|
+
return resolved === "cmux";
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Only cmux workspaces groundcrew created for a task are in scope: resolving
|
|
56
|
+
* a PR "from the task's worktree branch" presupposes a task. A cmux pane the
|
|
57
|
+
* user opened by hand has neither — it is left exactly as untouched as
|
|
58
|
+
* before this feature existed, rather than guessed at. The workspace's
|
|
59
|
+
* reported cwd is the precise match (handles multi-repo tasks); the task-id
|
|
60
|
+
* marker is the fallback for a workspace cmux hasn't reported a cwd for yet.
|
|
61
|
+
*/
|
|
62
|
+
function matchWorkspacesToTasks(workspaces, worktreeEntries) {
|
|
63
|
+
const matches = [];
|
|
64
|
+
for (const workspace of workspaces) {
|
|
65
|
+
const entry = worktreeEntries.find((candidate) => candidate.dir === workspace.currentDirectory) ??
|
|
66
|
+
worktreeEntries.find((candidate) => candidate.task === workspace.taskId);
|
|
67
|
+
if (entry !== undefined) {
|
|
68
|
+
matches.push({ workspace, entry });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return matches;
|
|
72
|
+
}
|
|
73
|
+
/** One PR to track per workspace: prefer a live one, then whichever terminal state, else whatever gh returned first. */
|
|
74
|
+
function selectTrackedPullRequest(pullRequests) {
|
|
75
|
+
return (pullRequests.find((pr) => pr.state === "open") ??
|
|
76
|
+
pullRequests.find((pr) => pr.state === "merged") ??
|
|
77
|
+
pullRequests.find((pr) => pr.state === "closed") ??
|
|
78
|
+
pullRequests[0]);
|
|
79
|
+
}
|
|
80
|
+
function newCounters() {
|
|
81
|
+
return { written: 0, cleared: 0, skipped: 0 };
|
|
82
|
+
}
|
|
83
|
+
async function applyField(arguments_) {
|
|
84
|
+
const { workspaceId, key, priority, current, desired, signal, writeStatus, counters } = arguments_;
|
|
85
|
+
if (desired === current) {
|
|
86
|
+
counters.skipped += 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
await writeStatus(workspaceId, { key, priority, value: desired }, signal);
|
|
90
|
+
if (desired.length === 0) {
|
|
91
|
+
counters.cleared += 1;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
counters.written += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The ticket field is derived from the workspace's own title/cwd, so it
|
|
99
|
+
* needs no PR lookup and is written for every matched cmux task workspace —
|
|
100
|
+
* including when `cmux.prStages.enabled` is false, so the sidebar's
|
|
101
|
+
* `crew_ticket` read works for everyone regardless of the PR-stage opt-in.
|
|
102
|
+
*/
|
|
103
|
+
async function writeTicketField(arguments_) {
|
|
104
|
+
const { workspace, current, deps, signal, counters } = arguments_;
|
|
105
|
+
await applyField({
|
|
106
|
+
workspaceId: workspace.id,
|
|
107
|
+
key: TICKET_KEY,
|
|
108
|
+
priority: TICKET_PRIORITY,
|
|
109
|
+
current: current.get(TICKET_KEY) ?? "",
|
|
110
|
+
desired: deriveTicket({
|
|
111
|
+
title: workspace.title,
|
|
112
|
+
...(workspace.currentDirectory === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: { currentDirectory: workspace.currentDirectory }),
|
|
115
|
+
}) ?? "",
|
|
116
|
+
signal,
|
|
117
|
+
writeStatus: deps.writeCmuxStatus,
|
|
118
|
+
counters,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Stage and labels are cleared only when the branch is confirmed to have no
|
|
123
|
+
* PR; when discovery itself failed (network/auth error) or a PR exists but
|
|
124
|
+
* its detail couldn't be (re)confirmed this tick (a failed or partial
|
|
125
|
+
* GraphQL batch), they are left exactly as they were — never guessed, never
|
|
126
|
+
* cleared.
|
|
127
|
+
*/
|
|
128
|
+
async function syncWorkspace(arguments_) {
|
|
129
|
+
const { match, labelNames, pullRequest, pullRequestLookupFailed, detail, deps, signal, counters, } = arguments_;
|
|
130
|
+
const { workspace } = match;
|
|
131
|
+
const current = await deps.readCmuxStatus(workspace.id, signal);
|
|
132
|
+
await writeTicketField({ workspace, current, deps, signal, counters });
|
|
133
|
+
if (pullRequestLookupFailed) {
|
|
134
|
+
counters.skipped += 2;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (pullRequest === undefined) {
|
|
138
|
+
await applyField({
|
|
139
|
+
workspaceId: workspace.id,
|
|
140
|
+
key: STAGE_KEY,
|
|
141
|
+
priority: STAGE_PRIORITY,
|
|
142
|
+
current: current.get(STAGE_KEY) ?? "",
|
|
143
|
+
desired: "",
|
|
144
|
+
signal,
|
|
145
|
+
writeStatus: deps.writeCmuxStatus,
|
|
146
|
+
counters,
|
|
147
|
+
});
|
|
148
|
+
await applyField({
|
|
149
|
+
workspaceId: workspace.id,
|
|
150
|
+
key: LABELS_KEY,
|
|
151
|
+
priority: LABELS_PRIORITY,
|
|
152
|
+
current: current.get(LABELS_KEY) ?? "",
|
|
153
|
+
desired: "",
|
|
154
|
+
signal,
|
|
155
|
+
writeStatus: deps.writeCmuxStatus,
|
|
156
|
+
counters,
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (detail === undefined) {
|
|
161
|
+
counters.skipped += 2;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await applyField({
|
|
165
|
+
workspaceId: workspace.id,
|
|
166
|
+
key: STAGE_KEY,
|
|
167
|
+
priority: STAGE_PRIORITY,
|
|
168
|
+
current: current.get(STAGE_KEY) ?? "",
|
|
169
|
+
desired: derivePrStage(detail, labelNames),
|
|
170
|
+
signal,
|
|
171
|
+
writeStatus: deps.writeCmuxStatus,
|
|
172
|
+
counters,
|
|
173
|
+
});
|
|
174
|
+
await applyField({
|
|
175
|
+
workspaceId: workspace.id,
|
|
176
|
+
key: LABELS_KEY,
|
|
177
|
+
priority: LABELS_PRIORITY,
|
|
178
|
+
current: current.get(LABELS_KEY) ?? "",
|
|
179
|
+
desired: managedLabelsField(detail.labels, labelNames),
|
|
180
|
+
signal,
|
|
181
|
+
writeStatus: deps.writeCmuxStatus,
|
|
182
|
+
counters,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async function resolvePullRequestsByWorkspace(arguments_) {
|
|
186
|
+
const { matches, config, findPullRequests, signal } = arguments_;
|
|
187
|
+
const byWorkspace = new Map();
|
|
188
|
+
const failedWorkspaceIds = new Set();
|
|
189
|
+
for (const match of matches) {
|
|
190
|
+
// oxlint-disable-next-line no-await-in-loop -- one gh lookup per matched workspace; mirrors reviewer.ts's sequential PR lookups
|
|
191
|
+
const branchName = await effectiveBranchName({ config, entry: match.entry });
|
|
192
|
+
try {
|
|
193
|
+
// oxlint-disable-next-line no-await-in-loop -- see above
|
|
194
|
+
const pullRequests = await findPullRequests({
|
|
195
|
+
cwd: match.entry.dir,
|
|
196
|
+
branchName,
|
|
197
|
+
...(signal === undefined ? {} : { signal }),
|
|
198
|
+
});
|
|
199
|
+
byWorkspace.set(match.workspace.id, selectTrackedPullRequest(pullRequests));
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
debug(`pr-stage-sync: PR lookup failed for ${match.entry.task}: ${errorMessage(error)}`);
|
|
203
|
+
failedWorkspaceIds.add(match.workspace.id);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { byWorkspace, failedWorkspaceIds };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Prefers the first matched task workspace, since the sidebar's staleness
|
|
210
|
+
* check (`heartbeatFresh`) scans every workspace for any recent heartbeat —
|
|
211
|
+
* it doesn't matter which task workspace carries it from pass to pass. This
|
|
212
|
+
* matters because `crew stage refresh`/`label-add`/`label-remove` run this
|
|
213
|
+
* same pass from a cmux Action's own ephemeral workspace, which cmux closes
|
|
214
|
+
* right after the command exits; a heartbeat written there would vanish with
|
|
215
|
+
* it, making a successful on-demand refresh look stale on the very next
|
|
216
|
+
* render. Falls back to the watch loop's own cmux workspace
|
|
217
|
+
* (`$CMUX_WORKSPACE_ID`) only when no task is currently tracked, so a
|
|
218
|
+
* fully-working sync with zero matched tasks still reports a heartbeat
|
|
219
|
+
* somewhere instead of silently writing none at all.
|
|
220
|
+
*/
|
|
221
|
+
async function writeHeartbeat(arguments_) {
|
|
222
|
+
const targetWorkspaceId = arguments_.matches[0]?.workspace.id ?? readEnvironmentVariable(CMUX_WORKSPACE_ID_ENV);
|
|
223
|
+
if (targetWorkspaceId === undefined) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const epochSeconds = String(Math.floor(Date.now() / 1000));
|
|
227
|
+
await arguments_.writeStatus(targetWorkspaceId, { key: HEARTBEAT_KEY, priority: HEARTBEAT_PRIORITY, value: epochSeconds }, arguments_.signal);
|
|
228
|
+
}
|
|
229
|
+
const TICKETS_ONLY_FLOW = "pr-stage-sync.tickets-only";
|
|
230
|
+
export function createPrStageSync(deps) {
|
|
231
|
+
/**
|
|
232
|
+
* Runs when `cmux.prStages.enabled` is false: writes only `crew_ticket` for
|
|
233
|
+
* each matched task workspace, with no `gh` lookups, no stage/label
|
|
234
|
+
* writes, and no `crew_poller_heartbeat` — the absence of any heartbeat is
|
|
235
|
+
* exactly what tells the sidebar the PR-stage feature is off, so it must
|
|
236
|
+
* never be written from this path.
|
|
237
|
+
*/
|
|
238
|
+
async function syncTicketsOnly(arguments_) {
|
|
239
|
+
const { worktreeEntries, signal } = arguments_;
|
|
240
|
+
if (!(await deps.isCmuxAdapterActive(signal))) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
debug("pr-stage-sync: starting (tickets-only, cmux.prStages disabled)");
|
|
244
|
+
const counters = newCounters();
|
|
245
|
+
let outcome = "updated";
|
|
246
|
+
let reason;
|
|
247
|
+
try {
|
|
248
|
+
const workspaces = await deps.listCmuxWorkspaces(signal);
|
|
249
|
+
if (workspaces === undefined) {
|
|
250
|
+
logEvent(TICKETS_ONLY_FLOW, { outcome: "error", reason: "workspace_list_failed" });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const matches = matchWorkspacesToTasks(workspaces, worktreeEntries);
|
|
254
|
+
for (const match of matches) {
|
|
255
|
+
try {
|
|
256
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential per workspace, mirrors syncOnce
|
|
257
|
+
const current = await deps.readCmuxStatus(match.workspace.id, signal);
|
|
258
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential per workspace, mirrors syncOnce
|
|
259
|
+
await writeTicketField({ workspace: match.workspace, current, deps, signal, counters });
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
outcome = "error";
|
|
263
|
+
reason = "status_write_failed";
|
|
264
|
+
debug(`pr-stage-sync: ticket write failed for ${match.entry.task}: ${errorMessage(error)}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
outcome = "error";
|
|
270
|
+
reason = errorMessage(error);
|
|
271
|
+
}
|
|
272
|
+
logEvent(TICKETS_ONLY_FLOW, {
|
|
273
|
+
outcome,
|
|
274
|
+
written: counters.written,
|
|
275
|
+
cleared: counters.cleared,
|
|
276
|
+
skipped: counters.skipped,
|
|
277
|
+
...(reason === undefined ? {} : { reason }),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async function syncOnce(arguments_) {
|
|
281
|
+
const { worktreeEntries, signal } = arguments_;
|
|
282
|
+
const { config } = deps;
|
|
283
|
+
if (!(await deps.isCmuxAdapterActive(signal))) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
debug("pr-stage-sync: starting");
|
|
287
|
+
const counters = newCounters();
|
|
288
|
+
let outcome = "updated";
|
|
289
|
+
let reason;
|
|
290
|
+
try {
|
|
291
|
+
const workspaces = await deps.listCmuxWorkspaces(signal);
|
|
292
|
+
if (workspaces === undefined) {
|
|
293
|
+
logEvent(FLOW, { outcome: "error", reason: "workspace_list_failed" });
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const matches = matchWorkspacesToTasks(workspaces, worktreeEntries);
|
|
297
|
+
const { byWorkspace: pullRequestByWorkspace, failedWorkspaceIds } = await resolvePullRequestsByWorkspace({
|
|
298
|
+
matches,
|
|
299
|
+
config,
|
|
300
|
+
findPullRequests: deps.findPullRequests,
|
|
301
|
+
signal,
|
|
302
|
+
});
|
|
303
|
+
if (failedWorkspaceIds.size > 0) {
|
|
304
|
+
outcome = "error";
|
|
305
|
+
reason = "pull_request_lookup_failed";
|
|
306
|
+
}
|
|
307
|
+
const urls = [
|
|
308
|
+
...new Set([...pullRequestByWorkspace.values()].flatMap((pr) => (pr === undefined ? [] : [pr.url]))),
|
|
309
|
+
];
|
|
310
|
+
let details = new Map();
|
|
311
|
+
if (urls.length > 0) {
|
|
312
|
+
try {
|
|
313
|
+
details = await deps.fetchPullRequestDetails({
|
|
314
|
+
urls,
|
|
315
|
+
...(signal === undefined ? {} : { signal }),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
outcome = "error";
|
|
320
|
+
reason = "pull_request_detail_lookup_failed";
|
|
321
|
+
debug(`pr-stage-sync: PR detail lookup failed: ${errorMessage(error)}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const match of matches) {
|
|
325
|
+
const pullRequest = pullRequestByWorkspace.get(match.workspace.id);
|
|
326
|
+
const detail = pullRequest === undefined ? undefined : details.get(pullRequest.url);
|
|
327
|
+
try {
|
|
328
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential per workspace so one workspace's status calls never interleave with another's
|
|
329
|
+
await syncWorkspace({
|
|
330
|
+
match,
|
|
331
|
+
labelNames: config.cmux.prStages.labels,
|
|
332
|
+
pullRequest,
|
|
333
|
+
pullRequestLookupFailed: failedWorkspaceIds.has(match.workspace.id),
|
|
334
|
+
detail,
|
|
335
|
+
deps,
|
|
336
|
+
signal,
|
|
337
|
+
counters,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
outcome = "error";
|
|
342
|
+
reason = "status_write_failed";
|
|
343
|
+
debug(`pr-stage-sync: sync failed for ${match.entry.task}: ${errorMessage(error)}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
await writeHeartbeat({ matches, writeStatus: deps.writeCmuxStatus, signal });
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
// A heartbeat failure is reported only when nothing else already
|
|
351
|
+
// flagged this pass as an error, so it never masks a more specific
|
|
352
|
+
// reason (like a per-workspace status_write_failed) from an earlier
|
|
353
|
+
// step in the same pass.
|
|
354
|
+
if (outcome !== "error") {
|
|
355
|
+
outcome = "error";
|
|
356
|
+
reason = "heartbeat_write_failed";
|
|
357
|
+
}
|
|
358
|
+
debug(`pr-stage-sync: heartbeat write failed: ${errorMessage(error)}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
outcome = "error";
|
|
363
|
+
reason = errorMessage(error);
|
|
364
|
+
}
|
|
365
|
+
logEvent(FLOW, {
|
|
366
|
+
outcome,
|
|
367
|
+
written: counters.written,
|
|
368
|
+
cleared: counters.cleared,
|
|
369
|
+
skipped: counters.skipped,
|
|
370
|
+
...(reason === undefined ? {} : { reason }),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
async function runOnce(arguments_) {
|
|
374
|
+
if (arguments_.dryRun === true) {
|
|
375
|
+
logEvent(FLOW, { outcome: "skipped", reason: "dry_run" });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (!deps.config.cmux.prStages.enabled) {
|
|
379
|
+
await syncTicketsOnly(arguments_);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
await syncOnce(arguments_);
|
|
383
|
+
}
|
|
384
|
+
return { runOnce, syncOnce };
|
|
385
|
+
}
|
|
386
|
+
/** Wires the real cmux/gh-backed implementations behind `PrStageSyncDeps`. */
|
|
387
|
+
export function createPrStageSyncDeps(config) {
|
|
388
|
+
return {
|
|
389
|
+
config,
|
|
390
|
+
findPullRequests: findPullRequestsForBranchOrThrow,
|
|
391
|
+
fetchPullRequestDetails,
|
|
392
|
+
listCmuxWorkspaces: listCmuxWorkspaceSummaries,
|
|
393
|
+
readCmuxStatus,
|
|
394
|
+
writeCmuxStatus,
|
|
395
|
+
isCmuxAdapterActive: async (signal) => await isCmuxAdapterActive(config, signal),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `crew stage` — operator commands for `pr-stage-sync`'s two gating labels
|
|
3
|
+
* (`label-add` / `label-remove`) and an on-demand sync pass (`refresh`).
|
|
4
|
+
* Labels are restricted to the two names configured under
|
|
5
|
+
* `cmux.prStages.labels`; each is created in the PR's repo on first use with
|
|
6
|
+
* a fixed color so the two gates stay visually distinct in GitHub's label UI.
|
|
7
|
+
*/
|
|
8
|
+
export declare function stageCli(argv: string[]): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=stage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../../src/commands/stage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuJH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAe5D"}
|