@clipboard-health/groundcrew 3.0.0 → 3.0.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 +73 -18
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -14
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +9 -4
- package/dist/commands/ticketCheck.d.ts +22 -0
- package/dist/commands/ticketCheck.d.ts.map +1 -0
- package/dist/commands/ticketCheck.js +23 -0
- package/dist/commands/ticketDoctor.d.ts +168 -20
- package/dist/commands/ticketDoctor.d.ts.map +1 -1
- package/dist/commands/ticketDoctor.js +766 -133
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/util.d.ts +18 -0
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +30 -0
- package/dist/lib/worktrees.d.ts +16 -0
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,6 +105,25 @@ Installs the `crew` binary. `@clipboard-health/clearance` is pulled in transitiv
|
|
|
105
105
|
crew run --watch # poll forever
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
## Secrets
|
|
109
|
+
|
|
110
|
+
Groundcrew forwards a small allowlist of build-time secrets from your shell into the setup phase (so `npm install` can authenticate against private registries) and then strips them before the agent runs. The agent process never inherits these values in its environment.
|
|
111
|
+
|
|
112
|
+
**Recognized names.** Defined in [`BUILD_SECRET_NAMES`](./src/lib/buildSecrets.ts):
|
|
113
|
+
|
|
114
|
+
- `NPM_TOKEN`
|
|
115
|
+
- `BUF_TOKEN`
|
|
116
|
+
|
|
117
|
+
Set them in the shell you run `crew` from. Anything not in this list is ignored by the secret-shuttling path.
|
|
118
|
+
|
|
119
|
+
**Flow.** For each ticket:
|
|
120
|
+
|
|
121
|
+
1. If any recognized var is set and non-empty, groundcrew writes `secrets.env` (mode `0600`) into the ticket's temp prompt dir as `KEY='value'` lines — see `stageBuildSecrets` in [`src/commands/setupWorkspace.ts`](./src/commands/setupWorkspace.ts).
|
|
122
|
+
2. The launch script sources `secrets.env` with `set -a` so the values are exported into the setup phase only (and under `sdx`, forwarded into the sandbox via `-e KEY` flags).
|
|
123
|
+
3. After setup completes, the script `unset`s every name in `BUILD_SECRET_NAMES` and then `rm -rf`s the entire prompt dir (including `secrets.env`) before `exec`'ing the agent. See `sourceSecretsLine` / `unsetSecretsLine` and the `rm -rf` / `exec` lines in [`src/lib/launchCommand.ts`](./src/lib/launchCommand.ts). The rollback path on setup failure ([`src/commands/setupWorkspace.ts`](./src/commands/setupWorkspace.ts)) wipes the prompt dir too.
|
|
124
|
+
|
|
125
|
+
Net effect: by the time the agent process exists, the values are gone from the environment and the file is gone from disk.
|
|
126
|
+
|
|
108
127
|
## Runners
|
|
109
128
|
|
|
110
129
|
`local.runner` picks the local isolation backend. `auto` resolves per platform.
|
|
@@ -270,39 +289,75 @@ To have a coding agent (Claude Code, Cursor, etc.) scaffold `.groundcrew/setup.s
|
|
|
270
289
|
## Commands
|
|
271
290
|
|
|
272
291
|
```bash
|
|
273
|
-
crew doctor
|
|
274
|
-
crew doctor --ticket <TICKET>
|
|
275
|
-
crew run
|
|
276
|
-
crew run --watch
|
|
277
|
-
crew run --ticket <TICKET>
|
|
292
|
+
crew doctor # full setup check
|
|
293
|
+
crew doctor --ticket <TICKET> [--no-linear] [--no-fetch] # full ticket lifecycle (dispatch + recovery)
|
|
294
|
+
crew run # one-shot dispatch
|
|
295
|
+
crew run --watch # poll forever
|
|
296
|
+
crew run --ticket <TICKET> # provision one ticket and exit
|
|
278
297
|
crew setup repos [--dry-run] [<repo>...]
|
|
279
|
-
crew cleanup <TICKET>
|
|
298
|
+
crew cleanup <TICKET> # tear down every worktree carrying this ticket
|
|
280
299
|
```
|
|
281
300
|
|
|
282
|
-
`--watch` and `--ticket` are mutually exclusive. To inspect codexbar session windows directly, run `codexbar usage`.
|
|
301
|
+
`crew doctor --ticket <TICKET>` covers the full per-ticket lifecycle: pre-dispatch eligibility (Todo status, `agent-*` label, model resolution, repository mention, local clone, blockers, model session usage, in-progress capacity) **and** post-dispatch local-state recovery (host worktree, workspace pane, local branch, remote branch, open PR). Verdict precedence runs from post-dispatch outcomes down: `pr-open` > `pr-merged` > `in-flight` > `recoverable` > `unresolvable` > `ineligible` > `would-dispatch` > `lost`. Exits 0 on `would-dispatch`, `pr-open`, or `pr-merged`; any other verdict exits 1. `--watch` and `--ticket` are mutually exclusive. To inspect codexbar session windows directly, run `codexbar usage`.
|
|
283
302
|
|
|
284
303
|
### `crew doctor --ticket <ticket>`
|
|
285
304
|
|
|
286
|
-
Diagnose
|
|
305
|
+
Diagnose where a ticket is in its lifecycle and what to do next. Runs the same resolution and eligibility chain as the dispatcher, plus probes the host worktree, workspace pane, local branch, remote branch, and PR; prints a single verdict with a copy-pasteable recovery step when one applies.
|
|
306
|
+
|
|
307
|
+
Flags:
|
|
308
|
+
|
|
309
|
+
- `--no-linear` — skip the Linear GraphQL call. Resolution and Eligibility sections are skipped; verdicts that need only local state (`in-flight`, `recoverable`, `pr-open`, `pr-merged`, `lost`) still fire.
|
|
310
|
+
- `--no-fetch` — skip the upfront `git fetch origin <branch>` before checking remote presence.
|
|
311
|
+
|
|
312
|
+
The Workspace section appends an attach hint to the pane name when the workspace backend exposes one (e.g. `tmux attach -t <session>:<pane>` or `cmux attach <name>`), so the verdict line is immediately actionable. The hero above shows a passing pre-dispatch run; here's the same command on a ticket that's already past dispatch:
|
|
287
313
|
|
|
288
314
|
```text
|
|
289
|
-
groundcrew doctor --ticket HRD-
|
|
290
|
-
|
|
315
|
+
groundcrew doctor --ticket HRD-442 (Multi-event extractor: year inference can produce date_start > date_end)
|
|
316
|
+
────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
291
317
|
|
|
292
318
|
Resolution
|
|
293
|
-
[ok] Ticket exists in Linear ("
|
|
294
|
-
[
|
|
295
|
-
|
|
296
|
-
[ok] Model resolves from agent-* label (model "claude")
|
|
297
|
-
[ok] Description mentions known repo (owner/repo)
|
|
298
|
-
[ok] Resolved repo is cloned locally (/dev/workspaces/owner/repo)
|
|
319
|
+
[ok] Ticket exists in Linear ("Multi-event extractor: year inference can produce date_start > date_end")
|
|
320
|
+
[ok] Status is Todo
|
|
321
|
+
(skipped — post-dispatch — pre-dispatch checks are irrelevant)
|
|
299
322
|
|
|
300
323
|
Eligibility
|
|
301
|
-
(skipped —
|
|
324
|
+
(skipped — post-dispatch — pre-dispatch checks are irrelevant)
|
|
325
|
+
|
|
326
|
+
Worktree
|
|
327
|
+
[ok] Host worktree exists (/Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442)
|
|
328
|
+
[--] Working tree clean (0 modified, 1 untracked)
|
|
329
|
+
[ok] Branch checked out (paul-hrd-442)
|
|
330
|
+
|
|
331
|
+
Workspace
|
|
332
|
+
[ok] Workspace pane open (hrd-442 — attach: `tmux attach -t groundcrew:hrd-442`)
|
|
302
333
|
|
|
303
|
-
|
|
334
|
+
Local branch
|
|
335
|
+
[ok] Local branch exists (paul-hrd-442, 2 ahead / 0 behind origin/main)
|
|
336
|
+
|
|
337
|
+
Remote branch
|
|
338
|
+
[ok] Branch present on origin
|
|
339
|
+
|
|
340
|
+
Pull request
|
|
341
|
+
[ok] Open PR for this branch (#224 https://github.com/herds-social/herds/pull/224)
|
|
342
|
+
|
|
343
|
+
→ pr-open: https://github.com/herds-social/herds/pull/224 (#224)
|
|
304
344
|
```
|
|
305
345
|
|
|
346
|
+
#### Recovering a stranded ticket
|
|
347
|
+
|
|
348
|
+
The verdict on the last line maps to a recovery action:
|
|
349
|
+
|
|
350
|
+
| Verdict | What to do |
|
|
351
|
+
| ---------------- | --------------------------------------------------------------------------------------------- |
|
|
352
|
+
| `pr-open` | Nothing — the PR is the source of truth. |
|
|
353
|
+
| `pr-merged` | Done. |
|
|
354
|
+
| `in-flight` | The ticket is still being worked on; the verdict line names the workspace pane to attach to. |
|
|
355
|
+
| `recoverable` | Run the printed `nextStep` exactly. |
|
|
356
|
+
| `would-dispatch` | Pre-dispatch checks pass; the orchestrator will pick the ticket up on its next tick. |
|
|
357
|
+
| `ineligible` | A resolution or eligibility check failed; the reason after the colon names the failing check. |
|
|
358
|
+
| `unresolvable` | The Linear ticket couldn't be fetched; the reason after the colon names the error. |
|
|
359
|
+
| `lost` | No trace exists. Re-dispatch via `crew run --ticket <ticket>`. |
|
|
360
|
+
|
|
306
361
|
## Troubleshooting
|
|
307
362
|
|
|
308
363
|
First stop for "labeled but not on the board": `crew doctor --ticket <ticket>` lists every check the dispatcher runs and flags the failing one.
|
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":"AAiJA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BvD"}
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { doctor } from "./commands/doctor.js";
|
|
|
4
4
|
import { orchestrate } from "./commands/orchestrator.js";
|
|
5
5
|
import { setupReposCli } from "./commands/setupRepos.js";
|
|
6
6
|
import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
|
|
7
|
-
import { errorMessage, writeError, writeOutput } from "./lib/util.js";
|
|
7
|
+
import { errorMessage, readTicketArgument, writeError, writeOutput } from "./lib/util.js";
|
|
8
8
|
const requireFromCli = createRequire(import.meta.url);
|
|
9
9
|
function setupUsage() {
|
|
10
10
|
return "Usage: crew setup repos [--dry-run] [<repo>...]";
|
|
@@ -32,11 +32,7 @@ async function runCli(argv) {
|
|
|
32
32
|
continue;
|
|
33
33
|
}
|
|
34
34
|
if (argument === "--ticket") {
|
|
35
|
-
|
|
36
|
-
if (value === undefined || value.length === 0 || value.startsWith("-")) {
|
|
37
|
-
throw new Error("crew run --ticket: ticket id is required");
|
|
38
|
-
}
|
|
39
|
-
ticket = value;
|
|
35
|
+
ticket = readTicketArgument(argv, index, "run");
|
|
40
36
|
index += 1;
|
|
41
37
|
continue;
|
|
42
38
|
}
|
|
@@ -53,20 +49,29 @@ async function runCli(argv) {
|
|
|
53
49
|
}
|
|
54
50
|
async function doctorCli(argv) {
|
|
55
51
|
let ticket;
|
|
52
|
+
const remainingArgs = [];
|
|
56
53
|
for (let index = 0; index < argv.length; index += 1) {
|
|
57
54
|
const argument = argv[index];
|
|
58
55
|
if (argument === "--ticket") {
|
|
59
|
-
|
|
60
|
-
if (value === undefined || value.length === 0 || value.startsWith("-")) {
|
|
61
|
-
throw new Error("crew doctor --ticket: ticket id is required");
|
|
62
|
-
}
|
|
63
|
-
ticket = value;
|
|
56
|
+
ticket = readTicketArgument(argv, index, "doctor");
|
|
64
57
|
index += 1;
|
|
65
58
|
continue;
|
|
66
59
|
}
|
|
60
|
+
if (argument === "--no-linear" || argument === "--no-fetch") {
|
|
61
|
+
remainingArgs.push(argument);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
67
64
|
throw new Error(`crew doctor: unknown argument: ${argument}`);
|
|
68
65
|
}
|
|
69
|
-
|
|
66
|
+
if (ticket === undefined) {
|
|
67
|
+
if (remainingArgs.length > 0) {
|
|
68
|
+
throw new Error(`crew doctor: ${remainingArgs[0]} requires --ticket (host doctor mode has no flags)`);
|
|
69
|
+
}
|
|
70
|
+
const ok = await doctor();
|
|
71
|
+
process.exitCode = ok ? process.exitCode : 1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const ok = await doctor({ ticket, ticketArgv: remainingArgs });
|
|
70
75
|
process.exitCode = ok ? process.exitCode : 1;
|
|
71
76
|
}
|
|
72
77
|
const SUBCOMMANDS = {
|
|
@@ -76,8 +81,8 @@ const SUBCOMMANDS = {
|
|
|
76
81
|
invoke: runCli,
|
|
77
82
|
},
|
|
78
83
|
doctor: {
|
|
79
|
-
summary: "Verify prereqs, or diagnose one ticket with --ticket",
|
|
80
|
-
usage: "[--ticket <ticket>]",
|
|
84
|
+
summary: "Verify prereqs, or diagnose one ticket with --ticket (full lifecycle: dispatch eligibility + local-state recovery)",
|
|
85
|
+
usage: "[--ticket <ticket> [--no-linear] [--no-fetch]]",
|
|
81
86
|
invoke: doctorCli,
|
|
82
87
|
},
|
|
83
88
|
cleanup: {
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export interface DoctorOptions {
|
|
6
6
|
ticket?: string;
|
|
7
|
+
/** Extra flags after `--ticket <id>`; currently `--no-linear` and `--no-fetch`. */
|
|
8
|
+
ticketArgv?: string[];
|
|
7
9
|
}
|
|
8
10
|
export declare function doctor(options?: DoctorOptions): Promise<boolean>;
|
|
9
11
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2BH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2BH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAsHD,wBAAsB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAK1E"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -8,7 +8,7 @@ import { detectHostCapabilities, which } from "../lib/host.js";
|
|
|
8
8
|
import { resolveLocalRunner } from "../lib/localRunner.js";
|
|
9
9
|
import { errorMessage, resolveLinearApiKey, writeOutput } from "../lib/util.js";
|
|
10
10
|
import { resolveWorkspaceKind } from "../lib/workspaces.js";
|
|
11
|
-
import { runTicketDoctor } from "./ticketDoctor.js";
|
|
11
|
+
import { parseTicketDoctorFlags, runTicketDoctor } from "./ticketDoctor.js";
|
|
12
12
|
// Tokenization stops after this many non-flag tokens. Two is enough to
|
|
13
13
|
// catch wrapper + wrapped CLI commands like `safehouse claude --foo`.
|
|
14
14
|
const MAX_TOKENS_PER_CMD = 2;
|
|
@@ -124,13 +124,18 @@ function format(check) {
|
|
|
124
124
|
}
|
|
125
125
|
export async function doctor(options = {}) {
|
|
126
126
|
if (options.ticket !== undefined) {
|
|
127
|
-
return await doctorTicket(options.ticket);
|
|
127
|
+
return await doctorTicket(options.ticket, options.ticketArgv ?? []);
|
|
128
128
|
}
|
|
129
129
|
return await doctorHost();
|
|
130
130
|
}
|
|
131
|
-
async function doctorTicket(ticket) {
|
|
131
|
+
async function doctorTicket(ticket, ticketArgv) {
|
|
132
132
|
try {
|
|
133
|
-
|
|
133
|
+
const flags = parseTicketDoctorFlags(ticketArgv);
|
|
134
|
+
return await runTicketDoctor({
|
|
135
|
+
ticket,
|
|
136
|
+
doLinear: flags.doLinear,
|
|
137
|
+
doFetch: flags.doFetch,
|
|
138
|
+
});
|
|
134
139
|
}
|
|
135
140
|
catch (error) {
|
|
136
141
|
const displayTicket = ticket.toUpperCase();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface TicketCheck {
|
|
2
|
+
name: string;
|
|
3
|
+
status: "ok" | "fail" | "skipped";
|
|
4
|
+
detail?: string;
|
|
5
|
+
failureSummary?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Section {
|
|
8
|
+
name: string;
|
|
9
|
+
checks: TicketCheck[];
|
|
10
|
+
/** When present and `checks` is empty, the section renders as `(skipped — <skipReason>)`. */
|
|
11
|
+
skipReason?: string;
|
|
12
|
+
}
|
|
13
|
+
interface RenderInput {
|
|
14
|
+
command: string;
|
|
15
|
+
argument: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
sections: Section[];
|
|
18
|
+
verdict: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function renderTicketCheckResult(input: RenderInput): string[];
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=ticketCheck.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticketCheck.d.ts","sourceRoot":"","sources":["../../src/commands/ticketCheck.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,6FAA6F;IAC7F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqBD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,EAAE,CAMpE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const STATUS_TAG = {
|
|
2
|
+
ok: "[ok]",
|
|
3
|
+
fail: "[--]",
|
|
4
|
+
skipped: "[? ]",
|
|
5
|
+
};
|
|
6
|
+
function formatCheck(check) {
|
|
7
|
+
const tag = STATUS_TAG[check.status];
|
|
8
|
+
const detail = check.detail === undefined ? "" : ` (${check.detail})`;
|
|
9
|
+
return ` ${tag} ${check.name}${detail}`;
|
|
10
|
+
}
|
|
11
|
+
function sectionLines(section) {
|
|
12
|
+
if (section.checks.length === 0 && section.skipReason !== undefined) {
|
|
13
|
+
return [section.name, ` (skipped — ${section.skipReason})`];
|
|
14
|
+
}
|
|
15
|
+
return [section.name, ...section.checks.map(formatCheck)];
|
|
16
|
+
}
|
|
17
|
+
export function renderTicketCheckResult(input) {
|
|
18
|
+
const titlePart = input.title === undefined ? "" : ` (${input.title})`;
|
|
19
|
+
const header = `groundcrew ${input.command} ${input.argument}${titlePart}`;
|
|
20
|
+
const bar = "─".repeat(header.length);
|
|
21
|
+
const body = input.sections.flatMap((section) => ["", ...sectionLines(section)]);
|
|
22
|
+
return [header, bar, ...body, "", input.verdict];
|
|
23
|
+
}
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { type Blocker, type RawLinearIssue } from "../lib/boardSource.ts";
|
|
2
2
|
import { type ResolvedConfig } from "../lib/config.ts";
|
|
3
3
|
import { type UsageByModel } from "../lib/usage.ts";
|
|
4
|
+
import { type WorkspaceAccessHint, type WorkspaceProbe } from "../lib/workspaces.ts";
|
|
5
|
+
import { type WorktreeDirtiness, type WorktreeEntry } from "../lib/worktrees.ts";
|
|
6
|
+
import { type TicketCheck } from "./ticketCheck.ts";
|
|
4
7
|
export type TicketDoctorVerdict = {
|
|
8
|
+
kind: "pr-open";
|
|
9
|
+
number: number;
|
|
10
|
+
url: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "pr-merged";
|
|
13
|
+
number: number;
|
|
14
|
+
url: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "in-flight";
|
|
17
|
+
reason: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "recoverable";
|
|
20
|
+
reason: string;
|
|
21
|
+
nextStep: string;
|
|
22
|
+
} | {
|
|
5
23
|
kind: "would-dispatch";
|
|
6
24
|
} | {
|
|
7
25
|
kind: "ineligible";
|
|
@@ -9,40 +27,170 @@ export type TicketDoctorVerdict = {
|
|
|
9
27
|
} | {
|
|
10
28
|
kind: "unresolvable";
|
|
11
29
|
reason: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: "lost";
|
|
32
|
+
reason: string;
|
|
12
33
|
};
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
export type LinearStatusProbe = {
|
|
35
|
+
kind: "terminal";
|
|
36
|
+
stateName: string;
|
|
37
|
+
} | {
|
|
38
|
+
kind: "non-terminal";
|
|
39
|
+
stateName: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "skipped";
|
|
42
|
+
} | {
|
|
43
|
+
kind: "unresolvable";
|
|
44
|
+
reason: string;
|
|
45
|
+
};
|
|
46
|
+
export type WorktreeProbe = {
|
|
47
|
+
kind: "present-clean";
|
|
48
|
+
} | {
|
|
49
|
+
kind: "present-dirty";
|
|
50
|
+
modified: number;
|
|
51
|
+
untracked: number;
|
|
52
|
+
} | {
|
|
53
|
+
kind: "present-unknown-dirtiness";
|
|
54
|
+
reason: string;
|
|
55
|
+
} | {
|
|
56
|
+
kind: "absent";
|
|
57
|
+
};
|
|
58
|
+
export type LocalBranchProbe = {
|
|
59
|
+
kind: "present";
|
|
60
|
+
ahead: number;
|
|
61
|
+
behind: number;
|
|
62
|
+
defaultBranch?: string;
|
|
63
|
+
} | {
|
|
64
|
+
kind: "absent";
|
|
65
|
+
} | {
|
|
66
|
+
kind: "unknown";
|
|
67
|
+
reason: string;
|
|
68
|
+
};
|
|
69
|
+
export type RemoteBranchProbe = {
|
|
70
|
+
kind: "present";
|
|
71
|
+
} | {
|
|
72
|
+
kind: "absent";
|
|
73
|
+
} | {
|
|
74
|
+
kind: "unknown";
|
|
75
|
+
reason: string;
|
|
76
|
+
};
|
|
77
|
+
export type PullRequestProbe = {
|
|
78
|
+
kind: "open";
|
|
79
|
+
number: number;
|
|
80
|
+
url: string;
|
|
81
|
+
} | {
|
|
82
|
+
kind: "merged";
|
|
83
|
+
number: number;
|
|
84
|
+
url: string;
|
|
85
|
+
} | {
|
|
86
|
+
kind: "absent";
|
|
87
|
+
} | {
|
|
88
|
+
kind: "gh-missing";
|
|
89
|
+
} | {
|
|
90
|
+
kind: "unknown";
|
|
91
|
+
reason: string;
|
|
92
|
+
};
|
|
93
|
+
export interface DecideVerdictInput {
|
|
94
|
+
linear: LinearStatusProbe;
|
|
95
|
+
worktree: WorktreeProbe;
|
|
96
|
+
localBranch: LocalBranchProbe;
|
|
97
|
+
remoteBranch: RemoteBranchProbe;
|
|
98
|
+
pullRequest: PullRequestProbe;
|
|
99
|
+
branch: string;
|
|
100
|
+
worktreeDir: string | undefined;
|
|
101
|
+
workspaceName: string | undefined;
|
|
25
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Returns a post-dispatch verdict if the probe bundle matches one of the
|
|
105
|
+
* "ticket has moved past dispatch" cases. Returns `undefined` otherwise,
|
|
106
|
+
* signalling that the caller should fall through to the pre-dispatch path.
|
|
107
|
+
*
|
|
108
|
+
* Precedence: PR > in-flight > recoverable. Inside `recoverable`, dirty
|
|
109
|
+
* worktree beats clean-with-un-pushed-local beats remote-only beats stranded
|
|
110
|
+
* local.
|
|
111
|
+
*/
|
|
112
|
+
export declare function decidePostDispatchVerdict(input: DecideVerdictInput): TicketDoctorVerdict | undefined;
|
|
26
113
|
export interface TicketDoctorDependencies {
|
|
27
114
|
config: ResolvedConfig;
|
|
28
115
|
ticket: string;
|
|
29
116
|
/**
|
|
30
|
-
* Injected to keep `ticketDoctor` pure and easy to unit-test.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
117
|
+
* Injected to keep `ticketDoctor` pure and easy to unit-test. `undefined`
|
|
118
|
+
* means the caller passed `--no-linear` — the Linear-backed pre-dispatch
|
|
119
|
+
* checks (status, label, repo, eligibility) are all skipped.
|
|
33
120
|
*/
|
|
34
|
-
fetchRawIssue: (input: {
|
|
121
|
+
fetchRawIssue: ((input: {
|
|
35
122
|
ticket: string;
|
|
36
|
-
}) => Promise<RawLinearIssue
|
|
123
|
+
}) => Promise<RawLinearIssue>) | undefined;
|
|
37
124
|
fetchBlockersFor: (input: {
|
|
38
125
|
ticket: string;
|
|
39
126
|
uuid: string;
|
|
40
127
|
}) => Promise<readonly Blocker[]>;
|
|
41
128
|
fetchUsage: () => Promise<UsageByModel>;
|
|
42
129
|
countInProgress: () => Promise<number>;
|
|
130
|
+
findWorktree: (ticket: string) => WorktreeEntry | undefined;
|
|
131
|
+
probeWorkspaces: () => Promise<WorkspaceProbe>;
|
|
132
|
+
workspaceAccessHint: (name: string) => Promise<WorkspaceAccessHint | undefined>;
|
|
133
|
+
probeWorkingTree: (input: {
|
|
134
|
+
worktreeDir: string;
|
|
135
|
+
}) => Promise<WorktreeDirtiness>;
|
|
136
|
+
probeLocalBranch: (input: {
|
|
137
|
+
repoDir: string;
|
|
138
|
+
branch: string;
|
|
139
|
+
defaultBranch: string;
|
|
140
|
+
}) => Promise<LocalBranchProbe>;
|
|
141
|
+
probeRemoteBranch: (input: {
|
|
142
|
+
repoDir: string;
|
|
143
|
+
branch: string;
|
|
144
|
+
doFetch: boolean;
|
|
145
|
+
}) => Promise<RemoteBranchProbe>;
|
|
146
|
+
probePullRequest: (input: {
|
|
147
|
+
repoDir: string;
|
|
148
|
+
branch: string;
|
|
149
|
+
}) => Promise<PullRequestProbe>;
|
|
150
|
+
doFetch: boolean;
|
|
43
151
|
}
|
|
44
|
-
export
|
|
152
|
+
export interface TicketDoctorResult {
|
|
153
|
+
ticket: string;
|
|
154
|
+
title?: string;
|
|
155
|
+
resolution: TicketCheck[];
|
|
156
|
+
eligibility: TicketCheck[];
|
|
157
|
+
worktree: TicketCheck[];
|
|
158
|
+
workspace: TicketCheck[];
|
|
159
|
+
localBranch: TicketCheck[];
|
|
160
|
+
remoteBranch: TicketCheck[];
|
|
161
|
+
pullRequest: TicketCheck[];
|
|
162
|
+
skipReasons: {
|
|
163
|
+
resolution: string;
|
|
164
|
+
eligibility: string;
|
|
165
|
+
worktree: string;
|
|
166
|
+
workspace: string;
|
|
167
|
+
localBranch: string;
|
|
168
|
+
remoteBranch: string;
|
|
169
|
+
pullRequest: string;
|
|
170
|
+
};
|
|
171
|
+
verdict: TicketDoctorVerdict;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Pure-with-async orchestrator that gathers all sections plus the verdict.
|
|
175
|
+
* All I/O happens via injected probes — the function itself does no
|
|
176
|
+
* filesystem, network, or stdout work.
|
|
177
|
+
*/
|
|
45
178
|
export declare function ticketDoctor(dependencies: TicketDoctorDependencies): Promise<TicketDoctorResult>;
|
|
46
|
-
|
|
47
|
-
|
|
179
|
+
interface TicketDoctorArguments {
|
|
180
|
+
ticket: string;
|
|
181
|
+
doLinear: boolean;
|
|
182
|
+
doFetch: boolean;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Parses optional `--no-linear` and `--no-fetch` flags that follow
|
|
186
|
+
* `crew doctor --ticket <id>`. The ticket id is consumed by `cli.ts` before
|
|
187
|
+
* this point.
|
|
188
|
+
*/
|
|
189
|
+
export declare function parseTicketDoctorFlags(argv: string[]): {
|
|
190
|
+
doLinear: boolean;
|
|
191
|
+
doFetch: boolean;
|
|
192
|
+
};
|
|
193
|
+
export declare function renderTicketDoctorResult(result: TicketDoctorResult): string[];
|
|
194
|
+
export declare function runTicketDoctor(parsed: TicketDoctorArguments): Promise<boolean>;
|
|
195
|
+
export {};
|
|
48
196
|
//# sourceMappingURL=ticketDoctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticketDoctor.d.ts","sourceRoot":"","sources":["../../src/commands/ticketDoctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ticketDoctor.d.ts","sourceRoot":"","sources":["../../src/commands/ticketDoctor.ts"],"names":[],"mappings":"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"}
|