@clipboard-health/groundcrew 4.0.1 → 4.0.2
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 +51 -72
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -25
- package/dist/commands/doctor.d.ts +1 -6
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +24 -37
- package/dist/commands/interruptWorkspace.js +1 -1
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +178 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/lib/util.d.ts +3 -2
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +18 -2
- package/package.json +1 -1
- package/dist/commands/ticketCheck.d.ts +0 -22
- package/dist/commands/ticketCheck.d.ts.map +0 -1
- package/dist/commands/ticketCheck.js +0 -23
- package/dist/commands/ticketDoctor.d.ts +0 -223
- package/dist/commands/ticketDoctor.d.ts.map +0 -1
- package/dist/commands/ticketDoctor.js +0 -1134
package/README.md
CHANGED
|
@@ -17,24 +17,31 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
|
-
$ crew
|
|
21
|
-
groundcrew
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
$ crew status HRD-446
|
|
21
|
+
groundcrew status HRD-446
|
|
22
|
+
========================
|
|
23
|
+
ticket: hrd-446
|
|
24
|
+
|
|
25
|
+
Config snapshot
|
|
26
|
+
---------------
|
|
27
|
+
projectDir: /dev/workspaces
|
|
28
|
+
repositories: owner/repo
|
|
29
|
+
git: remote=origin; defaultBranch=main
|
|
30
|
+
workspaceKind: auto
|
|
31
|
+
|
|
32
|
+
Worktree state
|
|
33
|
+
--------------
|
|
34
|
+
- owner/repo host
|
|
35
|
+
branch: rocky-hrd-446
|
|
36
|
+
git: dirty (2 modified, 1 untracked)
|
|
37
|
+
|
|
38
|
+
Workspace probe
|
|
39
|
+
---------------
|
|
40
|
+
live: yes
|
|
41
|
+
|
|
42
|
+
Last Linear status
|
|
43
|
+
------------------
|
|
44
|
+
In Progress (state.type=started) — Add retry logic to the sync job
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
## Why
|
|
@@ -75,7 +82,7 @@ In Linear, assign tickets to yourself and add an `agent-*` label (`agent-claude`
|
|
|
75
82
|
```bash
|
|
76
83
|
crew init [--global | --local] [--force] [--dry-run] # create a crew.config.ts
|
|
77
84
|
crew doctor # check setup
|
|
78
|
-
crew
|
|
85
|
+
crew status [<TICKET>] # inspect current state or one ticket
|
|
79
86
|
crew run # one-shot dispatch
|
|
80
87
|
crew run --watch # poll forever
|
|
81
88
|
crew run --ticket <TICKET> # dispatch one ticket
|
|
@@ -185,72 +192,44 @@ Groundcrew auto-creates sandboxes when missing but never deletes them — they p
|
|
|
185
192
|
|
|
186
193
|
</details>
|
|
187
194
|
|
|
188
|
-
##
|
|
195
|
+
## Inspecting status
|
|
189
196
|
|
|
190
|
-
`crew
|
|
197
|
+
`crew status <TICKET>` prints a read-only snapshot for one ticket: resolved config, matching worktrees, workspace probe result, recorded run state, recent log lines for that ticket, and the latest Linear status. It does not fetch, recover, tear down, resume, or mutate any local/remote state.
|
|
191
198
|
|
|
192
|
-
|
|
199
|
+
`crew status` with no ticket prints the current inventory: known worktrees with workspace/run-state presence plus live workspaces reported by the configured backend.
|
|
193
200
|
|
|
194
|
-
|
|
201
|
+
Use `crew cleanup <TICKET>` to tear down stale worktrees and `crew resume <TICKET>` to reopen preserved work. Status is intentionally informational only.
|
|
195
202
|
|
|
196
|
-
|
|
197
|
-
- `--no-fetch` — skip the upfront `git fetch origin <branch>` before checking remote presence.
|
|
203
|
+
## Doctor
|
|
198
204
|
|
|
199
|
-
|
|
200
|
-
| ---------------- | --------------------------------------------------------------------------------------------- |
|
|
201
|
-
| `pr-open` | Nothing — the PR is the source of truth. |
|
|
202
|
-
| `pr-merged` | Done. |
|
|
203
|
-
| `in-flight` | The ticket is still being worked on; the verdict line names the workspace pane to attach to. |
|
|
204
|
-
| `recoverable` | Run the printed `nextStep` exactly. |
|
|
205
|
-
| `interrupted` | Resume the preserved worktree with `crew resume <ticket>` or inspect it by hand. |
|
|
206
|
-
| `failed-launch` | Fix the launch failure, then run `crew resume <ticket>` or `crew cleanup <ticket>`. |
|
|
207
|
-
| `would-dispatch` | Pre-dispatch checks pass; the orchestrator will pick the ticket up on its next tick. |
|
|
208
|
-
| `ineligible` | A resolution or eligibility check failed; the reason after the colon names the failing check. |
|
|
209
|
-
| `unresolvable` | The Linear ticket couldn't be fetched; the reason after the colon names the error. |
|
|
210
|
-
| `lost` | No trace exists. Re-dispatch via `crew run --ticket <ticket>`. |
|
|
205
|
+
`crew doctor` checks host prerequisites only: config validity, Linear reachability, required binaries on PATH, workspace backend availability, workspace.projectDir, local runner capability, and enabled model commands.
|
|
211
206
|
|
|
212
207
|
<details>
|
|
213
|
-
<summary>Sample output
|
|
214
|
-
|
|
215
|
-
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.
|
|
208
|
+
<summary>Sample ticket status output</summary>
|
|
216
209
|
|
|
217
210
|
```text
|
|
218
|
-
groundcrew
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
Resolution
|
|
222
|
-
[ok] Ticket exists in Linear ("Multi-event extractor: year inference can produce date_start > date_end")
|
|
223
|
-
[ok] Status is Todo
|
|
224
|
-
(skipped — post-dispatch — pre-dispatch checks are irrelevant)
|
|
225
|
-
|
|
226
|
-
Eligibility
|
|
227
|
-
(skipped — post-dispatch — pre-dispatch checks are irrelevant)
|
|
211
|
+
groundcrew status HRD-442
|
|
212
|
+
=========================
|
|
213
|
+
ticket: hrd-442
|
|
228
214
|
|
|
229
215
|
Run state
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
[ok] Recorded worktree (/Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442)
|
|
233
|
-
[ok] Recorded branch (paul-hrd-442)
|
|
234
|
-
[ok] Resume count (0)
|
|
216
|
+
---------
|
|
217
|
+
running; model=claude; updated=2026-05-26T00:01:00.000Z; resumes=0
|
|
235
218
|
|
|
236
219
|
Worktree
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
220
|
+
--------
|
|
221
|
+
- herds-social host
|
|
222
|
+
branch: paul-hrd-442
|
|
223
|
+
dir: /Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442
|
|
224
|
+
git: dirty (0 modified, 1 untracked)
|
|
240
225
|
|
|
241
226
|
Workspace
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
Local branch
|
|
245
|
-
[ok] Local branch exists (paul-hrd-442, 2 ahead / 0 behind origin/main)
|
|
246
|
-
|
|
247
|
-
Remote branch
|
|
248
|
-
[ok] Branch present on origin
|
|
249
|
-
|
|
250
|
-
Pull request
|
|
251
|
-
[ok] Open PR for this branch (#224 https://github.com/herds-social/herds/pull/224)
|
|
227
|
+
---------
|
|
228
|
+
live: yes
|
|
252
229
|
|
|
253
|
-
|
|
230
|
+
Last Linear status
|
|
231
|
+
------------------
|
|
232
|
+
In Progress (state.type=started) — Multi-event extractor: year inference can produce date_start > date_end
|
|
254
233
|
```
|
|
255
234
|
|
|
256
235
|
</details>
|
|
@@ -261,11 +240,11 @@ Stops a live workspace pane while preserving the ticket worktree and branch. The
|
|
|
261
240
|
|
|
262
241
|
```bash
|
|
263
242
|
crew interrupt HRD-442 --reason "wrong implementation direction"
|
|
264
|
-
crew
|
|
243
|
+
crew status HRD-442
|
|
265
244
|
crew resume HRD-442
|
|
266
245
|
```
|
|
267
246
|
|
|
268
|
-
The command closes the cmux/tmux workspace if present, records local run state, and never tears down the worktree. If the workspace was already gone but the worktree is still present, interrupt records that fact so
|
|
247
|
+
The command closes the cmux/tmux workspace if present, records local run state, and never tears down the worktree. If the workspace was already gone but the worktree is still present, interrupt records that fact so status can show the preserved branch.
|
|
269
248
|
|
|
270
249
|
### `crew resume <TICKET>`
|
|
271
250
|
|
|
@@ -452,7 +431,7 @@ op run --env-file .env.1password -- crew doctor
|
|
|
452
431
|
|
|
453
432
|
## Troubleshooting
|
|
454
433
|
|
|
455
|
-
First stop for "
|
|
434
|
+
First stop for "what exists locally right now": `crew status <ticket>` shows the ticket's worktrees, workspace presence, run state, logs, and latest Linear status. Use `crew doctor` when you need to verify host setup.
|
|
456
435
|
|
|
457
436
|
<details>
|
|
458
437
|
<summary>Safehouse-already-wrapped commands are not re-wrapped</summary>
|
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":"AA6MA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCvD"}
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { resumeWorkspaceCli } from "./commands/resumeWorkspace.js";
|
|
|
8
8
|
import { sandboxCli } from "./commands/sandbox/index.js";
|
|
9
9
|
import { setupReposCli } from "./commands/setupRepos.js";
|
|
10
10
|
import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
|
|
11
|
+
import { statusCli } from "./commands/status.js";
|
|
11
12
|
import { createDefaultUpgradeCliOptions, upgradeCli } from "./commands/upgrade.js";
|
|
12
13
|
import { computeUpgradeNudge, defaultUpgradeCheckCachePath, fetchLatestVersion, } from "./lib/upgrade.js";
|
|
13
14
|
import { errorMessage, readEnvironmentVariable, readTicketArgument, writeError, writeOutput, } from "./lib/util.js";
|
|
@@ -80,30 +81,10 @@ async function maybeRunUpgradeNudge(metadata) {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
async function doctorCli(argv) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
86
|
-
const argument = argv[index];
|
|
87
|
-
if (argument === "--ticket") {
|
|
88
|
-
ticket = readTicketArgument(argv, index, "doctor");
|
|
89
|
-
index += 1;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (argument === "--no-linear" || argument === "--no-fetch") {
|
|
93
|
-
remainingArgs.push(argument);
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
throw new Error(`crew doctor: unknown argument: ${argument}`);
|
|
84
|
+
if (argv.length > 0) {
|
|
85
|
+
throw new Error("Usage: crew doctor");
|
|
97
86
|
}
|
|
98
|
-
|
|
99
|
-
if (remainingArgs.length > 0) {
|
|
100
|
-
throw new Error(`crew doctor: ${remainingArgs[0]} requires --ticket (host doctor mode has no flags)`);
|
|
101
|
-
}
|
|
102
|
-
const ok = await doctor();
|
|
103
|
-
process.exitCode = ok ? process.exitCode : 1;
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const ok = await doctor({ ticket, ticketArgv: remainingArgs });
|
|
87
|
+
const ok = await doctor();
|
|
107
88
|
process.exitCode = ok ? process.exitCode : 1;
|
|
108
89
|
}
|
|
109
90
|
const SUBCOMMANDS = {
|
|
@@ -118,10 +99,15 @@ const SUBCOMMANDS = {
|
|
|
118
99
|
invoke: runCli,
|
|
119
100
|
},
|
|
120
101
|
doctor: {
|
|
121
|
-
summary: "Verify
|
|
122
|
-
usage: "
|
|
102
|
+
summary: "Verify host prerequisites (PATH tools, config validity, Linear reachability)",
|
|
103
|
+
usage: "",
|
|
123
104
|
invoke: doctorCli,
|
|
124
105
|
},
|
|
106
|
+
status: {
|
|
107
|
+
summary: "Print read-only groundcrew state, or one ticket's local/Linear status",
|
|
108
|
+
usage: "[<ticket>]",
|
|
109
|
+
invoke: statusCli,
|
|
110
|
+
},
|
|
125
111
|
cleanup: {
|
|
126
112
|
summary: "Tear down a worktree",
|
|
127
113
|
usage: "[--force] <ticket>",
|
|
@@ -2,10 +2,5 @@
|
|
|
2
2
|
* doctor — verify groundcrew prerequisites against the resolved config.
|
|
3
3
|
* Returns true if every required check passes; false otherwise.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
ticket?: string;
|
|
7
|
-
/** Extra flags after `--ticket <id>`; currently `--no-linear` and `--no-fetch`. */
|
|
8
|
-
ticketArgv?: string[];
|
|
9
|
-
}
|
|
10
|
-
export declare function doctor(options?: DoctorOptions): Promise<boolean>;
|
|
5
|
+
export declare function doctor(): Promise<boolean>;
|
|
11
6
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoJH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CA8E/C"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, statSync } from "node:fs";
|
|
6
6
|
import { loadConfig, } from "../lib/config.js";
|
|
7
|
+
import { createBoardSource } from "../lib/boardSource.js";
|
|
7
8
|
import { detectHostCapabilities, which } from "../lib/host.js";
|
|
8
9
|
import { resolveLocalRunner } from "../lib/localRunner.js";
|
|
9
10
|
import { gatedModels } from "../lib/usage.js";
|
|
10
|
-
import { errorMessage, resolveLinearApiKey, writeOutput } from "../lib/util.js";
|
|
11
|
+
import { errorMessage, getLinearClient, resolveLinearApiKey, writeOutput } from "../lib/util.js";
|
|
11
12
|
import { resolveWorkspaceKind } from "../lib/workspaces.js";
|
|
12
|
-
import { parseTicketDoctorFlags, runTicketDoctor } from "./ticketDoctor.js";
|
|
13
13
|
// Tokenization stops after this many non-flag tokens. Two is enough to
|
|
14
14
|
// catch wrapper + wrapped CLI commands like `safehouse claude --foo`.
|
|
15
15
|
const MAX_TOKENS_PER_CMD = 2;
|
|
@@ -26,22 +26,33 @@ async function checkCmd(cmd, required, hint) {
|
|
|
26
26
|
}
|
|
27
27
|
return result;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
async function checkLinearReachability(config) {
|
|
30
30
|
const resolved = resolveLinearApiKey();
|
|
31
|
-
if (resolved
|
|
31
|
+
if (resolved === undefined) {
|
|
32
32
|
return {
|
|
33
|
-
name: "linear
|
|
33
|
+
name: "linear reachability",
|
|
34
|
+
ok: false,
|
|
35
|
+
required: true,
|
|
36
|
+
hint: "export $GROUNDCREW_LINEAR_API_KEY or $LINEAR_API_KEY",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await createBoardSource({ config, client: getLinearClient() }).verify();
|
|
41
|
+
return {
|
|
42
|
+
name: "linear reachability",
|
|
34
43
|
ok: true,
|
|
35
44
|
required: true,
|
|
36
45
|
hint: `set via $${resolved.source}`,
|
|
37
46
|
};
|
|
38
47
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
catch (error) {
|
|
49
|
+
return {
|
|
50
|
+
name: "linear reachability",
|
|
51
|
+
ok: false,
|
|
52
|
+
required: true,
|
|
53
|
+
hint: errorMessage(error),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
45
56
|
}
|
|
46
57
|
function checkDir(path, label) {
|
|
47
58
|
// statSync can throw on permission errors or path races; surface those
|
|
@@ -120,31 +131,7 @@ function format(check) {
|
|
|
120
131
|
const hint = check.hint !== undefined && check.hint.length > 0 ? ` — ${check.hint}` : "";
|
|
121
132
|
return `${tag}${check.name}${hint}`;
|
|
122
133
|
}
|
|
123
|
-
export async function doctor(
|
|
124
|
-
if (options.ticket !== undefined) {
|
|
125
|
-
return await doctorTicket(options.ticket, options.ticketArgv ?? []);
|
|
126
|
-
}
|
|
127
|
-
return await doctorHost();
|
|
128
|
-
}
|
|
129
|
-
async function doctorTicket(ticket, ticketArgv) {
|
|
130
|
-
try {
|
|
131
|
-
const flags = parseTicketDoctorFlags(ticketArgv);
|
|
132
|
-
return await runTicketDoctor({
|
|
133
|
-
ticket,
|
|
134
|
-
doLinear: flags.doLinear,
|
|
135
|
-
doFetch: flags.doFetch,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
const displayTicket = ticket.toUpperCase();
|
|
140
|
-
const header = `groundcrew doctor --ticket ${displayTicket}`;
|
|
141
|
-
writeOutput(header);
|
|
142
|
-
writeOutput("=".repeat(header.length));
|
|
143
|
-
writeOutput(`[--] config: ${errorMessage(error)}`);
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async function doctorHost() {
|
|
134
|
+
export async function doctor() {
|
|
148
135
|
writeOutput("groundcrew doctor");
|
|
149
136
|
writeOutput("=================");
|
|
150
137
|
let config;
|
|
@@ -174,7 +161,7 @@ async function doctorHost() {
|
|
|
174
161
|
const workspaceOutcome = resolveWorkspaceOutcome(config, host);
|
|
175
162
|
reportWorkspaceKind(config, workspaceOutcome);
|
|
176
163
|
const checks = [
|
|
177
|
-
|
|
164
|
+
await checkLinearReachability(config),
|
|
178
165
|
await checkCmd("git", true, "https://git-scm.com/"),
|
|
179
166
|
...(await workspaceChecks(workspaceOutcome)),
|
|
180
167
|
checkDir(config.workspace.projectDir, "workspace.projectDir"),
|
|
@@ -100,7 +100,7 @@ export async function interruptWorkspace(config, options) {
|
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
102
|
log(`Interrupted ${ticket}; worktree preserved at ${source.worktreeDir}`);
|
|
103
|
-
log(`Next: crew
|
|
103
|
+
log(`Next: crew status ${ticket}`);
|
|
104
104
|
}
|
|
105
105
|
export async function interruptWorkspaceCli(argv) {
|
|
106
106
|
const config = await loadConfig();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ResolvedConfig } from "../lib/config.ts";
|
|
2
|
+
export interface StatusOptions {
|
|
3
|
+
ticket?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function status(config: ResolvedConfig, options?: StatusOptions): Promise<void>;
|
|
6
|
+
export declare function statusCli(argv: string[]): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAWnE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuLD,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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fetchRawLinearIssue } from "../lib/boardSource.js";
|
|
3
|
+
import { loadConfig } from "../lib/config.js";
|
|
4
|
+
import { readRunState } from "../lib/runState.js";
|
|
5
|
+
import { errorMessage, getLinearClient, withLogOutputSuppressed, writeOutput, } from "../lib/util.js";
|
|
6
|
+
import { workspaces } from "../lib/workspaces.js";
|
|
7
|
+
import { worktrees } from "../lib/worktrees.js";
|
|
8
|
+
const RECENT_LOG_LINE_COUNT = 10;
|
|
9
|
+
function escapeRegExp(value) {
|
|
10
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
11
|
+
}
|
|
12
|
+
function ticketLinePattern(ticket) {
|
|
13
|
+
return new RegExp(`(^|[^a-z0-9])${escapeRegExp(ticket)}([^a-z0-9]|$)`, "i");
|
|
14
|
+
}
|
|
15
|
+
function parseArguments(argv) {
|
|
16
|
+
const [ticket, ...extras] = argv;
|
|
17
|
+
if (extras.length > 0 || ticket?.length === 0 || ticket?.startsWith("-") === true) {
|
|
18
|
+
throw new Error("Usage: crew status [<ticket>]");
|
|
19
|
+
}
|
|
20
|
+
return ticket === undefined ? {} : { ticket: ticket.toLowerCase() };
|
|
21
|
+
}
|
|
22
|
+
function writeSection(title) {
|
|
23
|
+
writeOutput();
|
|
24
|
+
writeOutput(title);
|
|
25
|
+
writeOutput("-".repeat(title.length));
|
|
26
|
+
}
|
|
27
|
+
function writeConfigSnapshot(config) {
|
|
28
|
+
writeSection("Config snapshot");
|
|
29
|
+
writeOutput(`projectDir: ${config.workspace.projectDir}`);
|
|
30
|
+
writeOutput(`repositories: ${config.workspace.knownRepositories.join(", ")}`);
|
|
31
|
+
writeOutput(`git: remote=${config.git.remote}; defaultBranch=${config.git.defaultBranch}`);
|
|
32
|
+
writeOutput(`workspaceKind: ${config.workspaceKind}`);
|
|
33
|
+
writeOutput(`local.runner: ${config.local.runner}`);
|
|
34
|
+
writeOutput(`models: default=${config.models.default}; enabled=${Object.keys(config.models.definitions).join(", ")}`);
|
|
35
|
+
writeOutput(`logFile: ${config.logging.file}`);
|
|
36
|
+
}
|
|
37
|
+
function formatDirtiness(dirtiness) {
|
|
38
|
+
if (dirtiness.kind === "dirty") {
|
|
39
|
+
return `dirty (${dirtiness.modified} modified, ${dirtiness.untracked} untracked)`;
|
|
40
|
+
}
|
|
41
|
+
return dirtiness.kind;
|
|
42
|
+
}
|
|
43
|
+
async function writeTicketWorktrees(config, ticket) {
|
|
44
|
+
writeSection("Worktree state");
|
|
45
|
+
const entries = worktrees.findByTicket(config, ticket);
|
|
46
|
+
if (entries.length === 0) {
|
|
47
|
+
writeOutput("(none)");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
// oxlint-disable-next-line no-await-in-loop -- status output is easier to read in worktree order.
|
|
52
|
+
const dirtiness = await worktrees.probeWorkingTree({ worktreeDir: entry.dir });
|
|
53
|
+
writeOutput(`- ${entry.repository} ${entry.kind}`);
|
|
54
|
+
writeOutput(` ticket: ${entry.ticket}`);
|
|
55
|
+
writeOutput(` branch: ${entry.branchName}`);
|
|
56
|
+
writeOutput(` dir: ${entry.dir}`);
|
|
57
|
+
writeOutput(` git: ${formatDirtiness(dirtiness)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function workspaceProbeUnavailableLine(probe) {
|
|
61
|
+
return probe.error === undefined
|
|
62
|
+
? "Workspace probe unavailable"
|
|
63
|
+
: `Workspace probe unavailable: ${errorMessage(probe.error)}`;
|
|
64
|
+
}
|
|
65
|
+
function writeTicketWorkspace(probe, ticket) {
|
|
66
|
+
writeSection("Workspace probe");
|
|
67
|
+
if (probe.kind === "unavailable") {
|
|
68
|
+
writeOutput(workspaceProbeUnavailableLine(probe));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
writeOutput(`live: ${probe.names.has(ticket) ? "yes" : "no"}`);
|
|
72
|
+
}
|
|
73
|
+
function formatRunState(state) {
|
|
74
|
+
if (state === undefined) {
|
|
75
|
+
return "(none)";
|
|
76
|
+
}
|
|
77
|
+
const summary = `${state.state}; model=${state.model}; updated=${state.updatedAt}; resumes=${state.resumeCount}`;
|
|
78
|
+
const detail = state.reason ?? state.detail;
|
|
79
|
+
return detail === undefined ? summary : `${summary}; ${detail}`;
|
|
80
|
+
}
|
|
81
|
+
function recentTicketLogLines(config, ticket) {
|
|
82
|
+
let raw;
|
|
83
|
+
try {
|
|
84
|
+
raw = readFileSync(config.logging.file, "utf8");
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const pattern = ticketLinePattern(ticket);
|
|
90
|
+
return raw
|
|
91
|
+
.split("\n")
|
|
92
|
+
.filter((line) => pattern.test(line))
|
|
93
|
+
.slice(-RECENT_LOG_LINE_COUNT);
|
|
94
|
+
}
|
|
95
|
+
async function linearStatus(ticket) {
|
|
96
|
+
try {
|
|
97
|
+
const issue = await fetchRawLinearIssue({ client: getLinearClient(), ticket });
|
|
98
|
+
return `${issue.stateName} (state.type=${issue.stateType ?? "unknown"}) — ${issue.title}`;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return `unavailable: ${errorMessage(error)}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function writeTicketStatus(config, rawTicket) {
|
|
105
|
+
const ticket = rawTicket.toLowerCase();
|
|
106
|
+
const displayTicket = ticket.toUpperCase();
|
|
107
|
+
writeOutput(`groundcrew status ${displayTicket}`);
|
|
108
|
+
writeOutput("=".repeat(`groundcrew status ${displayTicket}`.length));
|
|
109
|
+
writeOutput(`ticket: ${ticket}`);
|
|
110
|
+
writeConfigSnapshot(config);
|
|
111
|
+
await writeTicketWorktrees(config, ticket);
|
|
112
|
+
const workspaceProbe = await withLogOutputSuppressed(async () => await workspaces.probe(config));
|
|
113
|
+
writeTicketWorkspace(workspaceProbe, ticket);
|
|
114
|
+
writeSection("Run state");
|
|
115
|
+
writeOutput(formatRunState(readRunState(config, ticket)));
|
|
116
|
+
writeSection("Recent logs");
|
|
117
|
+
const logLines = recentTicketLogLines(config, ticket);
|
|
118
|
+
writeOutput(logLines.length === 0 ? "(none)" : logLines.join("\n"));
|
|
119
|
+
writeSection("Last Linear status");
|
|
120
|
+
writeOutput(await linearStatus(ticket));
|
|
121
|
+
}
|
|
122
|
+
function workspacePresence(probe, ticket) {
|
|
123
|
+
if (probe.kind === "unavailable") {
|
|
124
|
+
return "unknown";
|
|
125
|
+
}
|
|
126
|
+
return probe.names.has(ticket) ? "yes" : "no";
|
|
127
|
+
}
|
|
128
|
+
function writeInventoryWorktrees(config, probe) {
|
|
129
|
+
writeSection("Worktrees");
|
|
130
|
+
const entries = worktrees
|
|
131
|
+
.list(config)
|
|
132
|
+
.toSorted((left, right) => left.ticket.localeCompare(right.ticket));
|
|
133
|
+
if (entries.length === 0) {
|
|
134
|
+
writeOutput("(none)");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const runStates = new Map();
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
if (!runStates.has(entry.ticket)) {
|
|
140
|
+
runStates.set(entry.ticket, readRunState(config, entry.ticket));
|
|
141
|
+
}
|
|
142
|
+
const runState = runStates.get(entry.ticket);
|
|
143
|
+
writeOutput(`${entry.ticket} ${entry.repository} ${entry.kind} workspace=${workspacePresence(probe, entry.ticket)} run=${runState?.state ?? "none"}`);
|
|
144
|
+
writeOutput(` ${entry.branchName} ${entry.dir}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function writeInventoryWorkspaces(probe) {
|
|
148
|
+
writeSection("Live workspaces");
|
|
149
|
+
if (probe.kind === "unavailable") {
|
|
150
|
+
writeOutput(workspaceProbeUnavailableLine(probe));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const names = [...probe.names].toSorted();
|
|
154
|
+
writeOutput(names.length === 0 ? "(none)" : names.join("\n"));
|
|
155
|
+
}
|
|
156
|
+
async function writeInventoryStatus(config) {
|
|
157
|
+
writeOutput("groundcrew status");
|
|
158
|
+
writeOutput("=================");
|
|
159
|
+
const probe = await withLogOutputSuppressed(async () => await workspaces.probe(config));
|
|
160
|
+
writeInventoryWorktrees(config, probe);
|
|
161
|
+
writeInventoryWorkspaces(probe);
|
|
162
|
+
}
|
|
163
|
+
export async function status(config, options = {}) {
|
|
164
|
+
const ticket = options.ticket?.trim();
|
|
165
|
+
if (ticket === undefined) {
|
|
166
|
+
await writeInventoryStatus(config);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (ticket.length === 0 || ticket.startsWith("-")) {
|
|
170
|
+
throw new Error("ticket must be a non-empty value");
|
|
171
|
+
}
|
|
172
|
+
await writeTicketStatus(config, ticket);
|
|
173
|
+
}
|
|
174
|
+
export async function statusCli(argv) {
|
|
175
|
+
const options = parseArguments(argv);
|
|
176
|
+
const config = await loadConfig();
|
|
177
|
+
await status(config, options);
|
|
178
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { interruptWorkspace, type InterruptWorkspaceOptions, } from "./commands/
|
|
|
5
5
|
export { orchestrate, type OrchestratorOptions } from "./commands/orchestrator.ts";
|
|
6
6
|
export { resumeWorkspace, type ResumeWorkspaceOptions } from "./commands/resumeWorkspace.ts";
|
|
7
7
|
export { setupWorkspace, type SetupWorkspaceOptions } from "./commands/setupWorkspace.ts";
|
|
8
|
+
export { status, type StatusOptions } from "./commands/status.ts";
|
|
8
9
|
export type { Config, ModelDefinition, ResolvedConfig, SourceConfig } from "./lib/config.ts";
|
|
9
10
|
export { loadConfig } from "./lib/config.ts";
|
|
10
11
|
export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, type RunLifecycleState, type RunState, } from "./lib/runState.ts";
|
|
@@ -15,6 +16,4 @@ export { buildSources, buildSourcesWith } from "./lib/buildSources.ts";
|
|
|
15
16
|
export type { AdapterContext, AdapterDefinition } from "./lib/adapterDefinition.ts";
|
|
16
17
|
export { adapterRegistry, type AdapterLoader, buildRegistry, buildSourceConfigSchema, listAdapterDirectories, } from "./lib/adapters/registry.ts";
|
|
17
18
|
export { AmbiguousTicketError, type Blocker as CanonicalBlocker, type BoardState as CanonicalBoardState, type CanonicalStatus, type GroundcrewIssue as CanonicalGroundcrewIssue, type Issue as CanonicalIssue, isGroundcrewIssue as isCanonicalGroundcrewIssue, type TicketSource, } from "./lib/ticketSource.ts";
|
|
18
|
-
export type { TicketCheck } from "./commands/ticketCheck.ts";
|
|
19
|
-
export { ticketDoctor, type TicketDoctorDependencies, type TicketDoctorResult, type TicketDoctorVerdict, } from "./commands/ticketDoctor.ts";
|
|
20
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,eAAe,EACf,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,KAAK,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,EACL,eAAe,EACf,KAAK,aAAa,EAClB,aAAa,EACb,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,oBAAoB,EACpB,KAAK,OAAO,IAAI,gBAAgB,EAChC,KAAK,UAAU,IAAI,mBAAmB,EACtC,KAAK,eAAe,EACpB,KAAK,eAAe,IAAI,wBAAwB,EAChD,KAAK,KAAK,IAAI,cAAc,EAC5B,iBAAiB,IAAI,0BAA0B,EAC/C,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAClE,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,eAAe,EACf,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,KAAK,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,EACL,eAAe,EACf,KAAK,aAAa,EAClB,aAAa,EACb,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,oBAAoB,EACpB,KAAK,OAAO,IAAI,gBAAgB,EAChC,KAAK,UAAU,IAAI,mBAAmB,EACtC,KAAK,eAAe,EACpB,KAAK,eAAe,IAAI,wBAAwB,EAChD,KAAK,KAAK,IAAI,cAAc,EAC5B,iBAAiB,IAAI,0BAA0B,EAC/C,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { interruptWorkspace, } from "./commands/interruptWorkspace.js";
|
|
|
5
5
|
export { orchestrate } from "./commands/orchestrator.js";
|
|
6
6
|
export { resumeWorkspace } from "./commands/resumeWorkspace.js";
|
|
7
7
|
export { setupWorkspace } from "./commands/setupWorkspace.js";
|
|
8
|
+
export { status } from "./commands/status.js";
|
|
8
9
|
export { loadConfig } from "./lib/config.js";
|
|
9
10
|
export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, } from "./lib/runState.js";
|
|
10
11
|
export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isIssueInProgress, isIssueTodo, isTerminalStateType, isTerminalStatusForBlocker, isTerminalStatusForIssue, resolveModelFor, resolveRepositoryFor, } from "./lib/boardSource.js";
|
|
@@ -13,4 +14,4 @@ export { createBoard } from "./lib/board.js";
|
|
|
13
14
|
export { buildSources, buildSourcesWith } from "./lib/buildSources.js";
|
|
14
15
|
export { adapterRegistry, buildRegistry, buildSourceConfigSchema, listAdapterDirectories, } from "./lib/adapters/registry.js";
|
|
15
16
|
export { AmbiguousTicketError, isGroundcrewIssue as isCanonicalGroundcrewIssue, } from "./lib/ticketSource.js";
|
|
16
|
-
|
|
17
|
+
// RepositoryResolutionError is exported via boardSource.ts above (single canonical location).
|
package/dist/lib/util.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
|
3
3
|
export declare function writeOutput(message?: string): void;
|
|
4
4
|
export declare function writeError(message: string): void;
|
|
5
5
|
export declare function setLogFile(path: string | undefined): void;
|
|
6
|
+
export declare function withLogOutputSuppressed<T>(operation: () => Promise<T>): Promise<T>;
|
|
6
7
|
export declare function log(message: string): void;
|
|
7
8
|
type LogEventFieldValue = boolean | number | string | readonly string[] | undefined;
|
|
8
9
|
export declare function logEvent(event: string, fields: Record<string, LogEventFieldValue>): void;
|
|
@@ -18,8 +19,8 @@ export declare function getLinearClient(): LinearClient;
|
|
|
18
19
|
/**
|
|
19
20
|
* Returns a zero-arg getter that lazily constructs (and caches) a Linear
|
|
20
21
|
* client on first call. Used by CLI entry points that may not need the
|
|
21
|
-
* client at all
|
|
22
|
-
*
|
|
22
|
+
* client at all, so we avoid blowing up on a missing API key when no Linear
|
|
23
|
+
* call is actually made. The factory is taken as a
|
|
23
24
|
* parameter (rather than calling `getLinearClient` directly) so callers can
|
|
24
25
|
* pass their own module-level import of `getLinearClient` — that binding
|
|
25
26
|
* respects `vi.mock` intercepts, whereas an intra-module reference would
|
package/dist/lib/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAkBD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQzC;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAcxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,QAAA,MAAM,sBAAsB,YAAI,2BAA2B,EAAE,gBAAgB,CAAU,CAAC;AAExF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,IAAI,oBAAoB,GAAG,SAAS,CAQtE;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,MAAM,YAAY,CAMhF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
|