@clipboard-health/groundcrew 4.2.0 → 4.2.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/dist/commands/cleaner.d.ts +1 -1
- package/dist/commands/cleaner.d.ts.map +1 -1
- package/dist/commands/cleaner.js +4 -2
- package/dist/commands/dispatcher.d.ts +6 -6
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +43 -27
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +18 -22
- package/dist/commands/eligibility.d.ts +1 -1
- package/dist/commands/eligibility.d.ts.map +1 -1
- package/dist/commands/eligibility.js +7 -6
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +18 -14
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +3 -2
- package/dist/commands/setupWorkspace.d.ts +2 -4
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +27 -27
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +6 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/lib/adapters/linear/client.d.ts +22 -0
- package/dist/lib/adapters/linear/client.d.ts.map +1 -0
- package/dist/lib/adapters/linear/client.js +36 -0
- package/dist/lib/adapters/linear/factory.d.ts +24 -14
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.js +113 -46
- package/dist/lib/{boardSource.d.ts → adapters/linear/fetch.d.ts} +19 -71
- package/dist/lib/adapters/linear/fetch.d.ts.map +1 -0
- package/dist/lib/{boardSource.js → adapters/linear/fetch.js} +21 -133
- package/dist/lib/adapters/linear/index.d.ts +1 -0
- package/dist/lib/adapters/linear/index.d.ts.map +1 -1
- package/dist/lib/adapters/linear/parsing.d.ts +44 -0
- package/dist/lib/adapters/linear/parsing.d.ts.map +1 -0
- package/dist/lib/adapters/linear/parsing.js +144 -0
- package/dist/lib/{linearIssueStatus.d.ts → adapters/linear/writeback.d.ts} +1 -2
- package/dist/lib/adapters/linear/writeback.d.ts.map +1 -0
- package/dist/lib/{linearIssueStatus.js → adapters/linear/writeback.js} +16 -17
- package/dist/lib/adapters/shell/factory.d.ts +1 -1
- package/dist/lib/adapters/shell/factory.d.ts.map +1 -1
- package/dist/lib/adapters/shell/factory.js +8 -4
- package/dist/lib/adapters/shell/invoke.d.ts +4 -7
- package/dist/lib/adapters/shell/invoke.d.ts.map +1 -1
- package/dist/lib/adapters/shell/invoke.js +46 -75
- package/dist/lib/adapters/shell/schema.d.ts +10 -0
- package/dist/lib/adapters/shell/schema.d.ts.map +1 -1
- package/dist/lib/adapters/shell/schema.js +9 -5
- package/dist/lib/board.d.ts.map +1 -1
- package/dist/lib/board.js +43 -4
- package/dist/lib/buildSources.d.ts +11 -0
- package/dist/lib/buildSources.d.ts.map +1 -1
- package/dist/lib/buildSources.js +41 -0
- package/dist/lib/repositoryValidation.d.ts +13 -0
- package/dist/lib/repositoryValidation.d.ts.map +1 -0
- package/dist/lib/repositoryValidation.js +20 -0
- package/dist/lib/testing/canonicalFixtures.d.ts +19 -0
- package/dist/lib/testing/canonicalFixtures.d.ts.map +1 -0
- package/dist/lib/testing/canonicalFixtures.js +62 -0
- package/dist/lib/ticketSource.d.ts +71 -1
- package/dist/lib/ticketSource.d.ts.map +1 -1
- package/dist/lib/ticketSource.js +31 -0
- package/dist/lib/util.d.ts +0 -20
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +0 -35
- package/package.json +1 -1
- package/dist/lib/boardSource.d.ts.map +0 -1
- package/dist/lib/linearIssueStatus.d.ts.map +0 -1
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Linear adapter —
|
|
3
|
-
* snapshot. Owns the GraphQL queries and shape parsing so callers consume a
|
|
4
|
-
* typed `BoardState` instead of raw nodes.
|
|
2
|
+
* Linear adapter — GraphQL fetch helpers for board/issue data.
|
|
5
3
|
*
|
|
6
|
-
* There is no project / view / status configuration: the only
|
|
7
|
-
* "assigned to the API key's viewer AND carries an `agent-*`
|
|
8
|
-
* State classification is driven by Linear's workflow `state.type`
|
|
4
|
+
* There is no project / view / status configuration: the only server-side
|
|
5
|
+
* filter is "assigned to the API key's viewer AND carries an `agent-*`
|
|
6
|
+
* label." State classification is driven by Linear's workflow `state.type`
|
|
9
7
|
* (`unstarted` | `started` | `completed` | `canceled` | `duplicate`) —
|
|
10
|
-
* never by status name — so workspaces with renamed columns (Todo
|
|
11
|
-
* Done
|
|
8
|
+
* never by status name — so workspaces with renamed columns (Todo -> To Do,
|
|
9
|
+
* Done -> Shipped, etc.) Just Work without per-team config.
|
|
12
10
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
export const AGENT_LABEL_PREFIX = "agent-";
|
|
11
|
+
import { RepositoryResolutionError } from "../../ticketSource.js";
|
|
12
|
+
import { log } from "../../util.js";
|
|
13
|
+
import { AGENT_LABEL_PREFIX, resolveModelFor, resolveRepositoryFor, } from "./parsing.js";
|
|
17
14
|
export const ISSUES_PAGE_SIZE = 250;
|
|
18
15
|
// `state.type` values surfaced by `fetch()`. `backlog` / `triage` are dropped
|
|
19
16
|
// at the GraphQL filter; everything else is post-classified by these names.
|
|
@@ -24,14 +21,6 @@ const ACTIONABLE_STATE_TYPES = [
|
|
|
24
21
|
"canceled",
|
|
25
22
|
"duplicate",
|
|
26
23
|
];
|
|
27
|
-
export function isGroundcrewIssue(issue) {
|
|
28
|
-
return issue.model !== undefined && issue.repository !== undefined;
|
|
29
|
-
}
|
|
30
|
-
// Canonical RepositoryResolutionError lives in ./ticketSource.ts (imported at
|
|
31
|
-
// the top of this file). Re-exported here so existing consumers of
|
|
32
|
-
// boardSource.ts keep compiling until a follow-up PR completes the consumer
|
|
33
|
-
// refactor and deletes this file.
|
|
34
|
-
export { RepositoryResolutionError };
|
|
35
24
|
export function createBoardSource(deps) {
|
|
36
25
|
const { config, client } = deps;
|
|
37
26
|
return {
|
|
@@ -75,18 +64,6 @@ export function isTerminalStatusForBlocker(blocker) {
|
|
|
75
64
|
async function fetchBoard(client, config) {
|
|
76
65
|
const nodes = [];
|
|
77
66
|
let after = null;
|
|
78
|
-
// Three server-side filters narrow the response to tickets the orchestrator
|
|
79
|
-
// can actually act on:
|
|
80
|
-
// 1. Assignee: the API key's own viewer. groundcrew is a single-user
|
|
81
|
-
// orchestrator — every ticket it dispatches is "this user's work."
|
|
82
|
-
// 2. Label: at least one `agent-*` label — i.e. the user opted the
|
|
83
|
-
// ticket in to groundcrew. Without this, every human-owned ticket
|
|
84
|
-
// would round-trip back just to be filtered out client-side.
|
|
85
|
-
// 3. State type: scoped to actionable values (`unstarted`, `started`,
|
|
86
|
-
// `completed`, `canceled`, `duplicate`) so backlog/triage tickets never
|
|
87
|
-
// make it into the page.
|
|
88
|
-
// The client-side `isGroundcrewIssue` guard in dispatcher.ts is
|
|
89
|
-
// belt-and-suspenders against query drift, not the load-bearing filter.
|
|
90
67
|
const stateTypes = [...ACTIONABLE_STATE_TYPES];
|
|
91
68
|
for (;;) {
|
|
92
69
|
// oxlint-disable-next-line no-await-in-loop -- pagination cursor depends on the previous response
|
|
@@ -163,14 +140,14 @@ export function modelForResolution(resolution) {
|
|
|
163
140
|
if (resolution.kind === "disabled-fallback") {
|
|
164
141
|
return resolution.fallbackModel;
|
|
165
142
|
}
|
|
166
|
-
return
|
|
143
|
+
return "any";
|
|
167
144
|
}
|
|
168
|
-
|
|
145
|
+
function resolveTodoAgentMetadata(arguments_) {
|
|
169
146
|
const { ticket, description, modelResolution, config, isTodo } = arguments_;
|
|
170
147
|
let repository;
|
|
171
148
|
let model;
|
|
172
149
|
if (modelResolution.kind !== "no-label" && isTodo) {
|
|
173
|
-
const resolution = resolveRepositoryFor({ description, config
|
|
150
|
+
const resolution = resolveRepositoryFor({ description, config });
|
|
174
151
|
if (resolution.kind === "ok") {
|
|
175
152
|
({ repository } = resolution);
|
|
176
153
|
model = modelForResolution(modelResolution);
|
|
@@ -186,6 +163,7 @@ function buildLinearIssue(input) {
|
|
|
186
163
|
id: input.identifier.toLowerCase(),
|
|
187
164
|
uuid: input.uuid,
|
|
188
165
|
title: input.title,
|
|
166
|
+
description: input.description,
|
|
189
167
|
status: input.status,
|
|
190
168
|
statusId: input.statusId,
|
|
191
169
|
stateType: input.stateType,
|
|
@@ -202,11 +180,6 @@ function buildLinearIssue(input) {
|
|
|
202
180
|
function issueFromNode(node, config) {
|
|
203
181
|
const modelResolution = resolveModelFor({ labels: node.labels.nodes, config });
|
|
204
182
|
warnIfDisabledFallback(node.identifier, modelResolution, config);
|
|
205
|
-
// Only the dispatcher reads `Issue.repository` / `Issue.model`, and only on
|
|
206
|
-
// tickets in the Todo column it's about to pick up. Resolving them for In
|
|
207
|
-
// Progress (already running) or Done (cleaner only needs the id) would just
|
|
208
|
-
// invite tick-spam warnings on already-finished tickets — e.g. when a
|
|
209
|
-
// description was edited or knownRepositories changed after dispatch.
|
|
210
183
|
const { repository, model } = resolveTodoAgentMetadata({
|
|
211
184
|
ticket: node.identifier,
|
|
212
185
|
/* v8 ignore next @preserve -- BoardIssues query selects description; the ?? guard normalises a null vs undefined edge */
|
|
@@ -219,6 +192,8 @@ function issueFromNode(node, config) {
|
|
|
219
192
|
identifier: node.identifier,
|
|
220
193
|
uuid: node.id,
|
|
221
194
|
title: node.title,
|
|
195
|
+
/* v8 ignore next @preserve -- BoardIssues query always selects description; this `?? ""` is a defensive null vs undefined edge */
|
|
196
|
+
description: node.description ?? "",
|
|
222
197
|
/* v8 ignore next @preserve -- BoardIssues query always returns state */
|
|
223
198
|
status: node.state?.name ?? "Unknown",
|
|
224
199
|
/* v8 ignore next @preserve -- BoardIssues query always returns state */
|
|
@@ -233,23 +208,6 @@ function issueFromNode(node, config) {
|
|
|
233
208
|
inverseRelations: node.inverseRelations,
|
|
234
209
|
});
|
|
235
210
|
}
|
|
236
|
-
function escapeRegex(value) {
|
|
237
|
-
return value.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw `\$&`);
|
|
238
|
-
}
|
|
239
|
-
// Sort by descending length so longer names match first — `api-admin`
|
|
240
|
-
// must beat `api` when both are configured. `\b` treats `-` as a word
|
|
241
|
-
// boundary, so without this ordering `api` would win on `api-admin`.
|
|
242
|
-
function buildRepositoryRegex(config) {
|
|
243
|
-
const candidates = config.workspace.knownRepositories.flatMap((repo) => {
|
|
244
|
-
const slashIndex = repo.indexOf("/");
|
|
245
|
-
return slashIndex === -1 ? [repo] : [repo, repo.slice(slashIndex + 1)];
|
|
246
|
-
});
|
|
247
|
-
const alternation = candidates
|
|
248
|
-
.toSorted((a, b) => b.length - a.length)
|
|
249
|
-
.map(escapeRegex)
|
|
250
|
-
.join("|");
|
|
251
|
-
return new RegExp(String.raw `\b(${alternation})\b`);
|
|
252
|
-
}
|
|
253
211
|
const ISSUE_LABEL_PAGE_SIZE = 50;
|
|
254
212
|
const ISSUE_RELATION_PAGE_SIZE = 50;
|
|
255
213
|
export async function fetchBlockersForTicket(arguments_) {
|
|
@@ -294,7 +252,7 @@ export async function fetchRawLinearIssue(arguments_) {
|
|
|
294
252
|
title
|
|
295
253
|
description
|
|
296
254
|
team { id }
|
|
297
|
-
state { name type }
|
|
255
|
+
state { id name type }
|
|
298
256
|
children { nodes { id } }
|
|
299
257
|
labels(first: ${ISSUE_LABEL_PAGE_SIZE}) {
|
|
300
258
|
nodes { name }
|
|
@@ -328,6 +286,8 @@ export async function fetchRawLinearIssue(arguments_) {
|
|
|
328
286
|
stateName: issue.state?.name ?? "",
|
|
329
287
|
/* v8 ignore next @preserve -- ResolveIssue query selects state; null only if Linear genuinely returns a stateless ticket */
|
|
330
288
|
stateType: issue.state?.type ?? "",
|
|
289
|
+
/* v8 ignore next @preserve -- ResolveIssue query selects state; null only if Linear genuinely returns a stateless ticket */
|
|
290
|
+
stateId: issue.state?.id ?? "",
|
|
331
291
|
blockers: blockersFromRelations(issue.inverseRelations?.nodes ?? []),
|
|
332
292
|
hasMoreBlockers: issue.inverseRelations?.pageInfo.hasNextPage ?? false,
|
|
333
293
|
hasChildren: (issue.children?.nodes.length ?? 0) > 0,
|
|
@@ -374,53 +334,6 @@ export async function fetchInProgressIssueCount(arguments_) {
|
|
|
374
334
|
after = page.pageInfo.endCursor;
|
|
375
335
|
}
|
|
376
336
|
}
|
|
377
|
-
export function resolveRepositoryFor(arguments_) {
|
|
378
|
-
const { description, config } = arguments_;
|
|
379
|
-
if (description === undefined || description.length === 0) {
|
|
380
|
-
return { kind: "missing" };
|
|
381
|
-
}
|
|
382
|
-
const match = buildRepositoryRegex(config).exec(description)?.[1];
|
|
383
|
-
if (match === undefined) {
|
|
384
|
-
return { kind: "missing" };
|
|
385
|
-
}
|
|
386
|
-
// `buildRepositoryRegex` matches both the full `owner/repo` entry and its bare
|
|
387
|
-
// suffix, so the captured value can be either form. Downstream code composes
|
|
388
|
-
// the resolved value with `workspace.projectDir` and needs the exact
|
|
389
|
-
// `knownRepositories` entry, so resolve back to that form here.
|
|
390
|
-
const candidates = config.workspace.knownRepositories.filter((entry) => entry === match || entry.endsWith(`/${match}`));
|
|
391
|
-
if (candidates.length !== 1) {
|
|
392
|
-
return { kind: "missing" };
|
|
393
|
-
}
|
|
394
|
-
const [canonical] = candidates;
|
|
395
|
-
/* v8 ignore next 3 @preserve -- candidates.length === 1 guarantees [0] is defined */
|
|
396
|
-
if (canonical === undefined) {
|
|
397
|
-
return { kind: "missing" };
|
|
398
|
-
}
|
|
399
|
-
return { kind: "ok", repository: canonical };
|
|
400
|
-
}
|
|
401
|
-
export function resolveModelFor(arguments_) {
|
|
402
|
-
const { labels, config } = arguments_;
|
|
403
|
-
const parsed = parseAgentLabels(labels, config);
|
|
404
|
-
if (parsed === undefined) {
|
|
405
|
-
return { kind: "no-label" };
|
|
406
|
-
}
|
|
407
|
-
if (parsed.model === AGENT_ANY_MODEL) {
|
|
408
|
-
return { kind: "agent-any" };
|
|
409
|
-
}
|
|
410
|
-
if (parsed.disabledFallback !== undefined) {
|
|
411
|
-
return {
|
|
412
|
-
kind: "disabled-fallback",
|
|
413
|
-
requestedModel: parsed.disabledFallback,
|
|
414
|
-
fallbackModel: parsed.model,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
return { kind: "matched", model: parsed.model };
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* `agent-any` collapses to `models.default` here — manual setup doesn't run
|
|
421
|
-
* the usage-gated `any` resolver, so the caller gets a concrete model name
|
|
422
|
-
* instead of a sentinel that downstream code can't interpret.
|
|
423
|
-
*/
|
|
424
337
|
export async function fetchResolvedIssue(arguments_) {
|
|
425
338
|
const { client, config, ticket } = arguments_;
|
|
426
339
|
const upper = ticket.toUpperCase();
|
|
@@ -428,7 +341,6 @@ export async function fetchResolvedIssue(arguments_) {
|
|
|
428
341
|
const repositoryResolution = resolveRepositoryFor({
|
|
429
342
|
description: raw.description,
|
|
430
343
|
config,
|
|
431
|
-
ticket: upper,
|
|
432
344
|
});
|
|
433
345
|
if (repositoryResolution.kind === "missing") {
|
|
434
346
|
throw new RepositoryResolutionError({
|
|
@@ -452,35 +364,11 @@ export async function fetchResolvedIssue(arguments_) {
|
|
|
452
364
|
repository: repositoryResolution.repository,
|
|
453
365
|
model,
|
|
454
366
|
teamId: raw.teamId,
|
|
367
|
+
stateType: raw.stateType,
|
|
368
|
+
status: raw.stateName,
|
|
369
|
+
statusId: raw.stateId,
|
|
455
370
|
};
|
|
456
371
|
}
|
|
457
|
-
function parseAgentLabels(labels, config) {
|
|
458
|
-
const agentLabels = labels.filter((label) => label.name.startsWith(AGENT_LABEL_PREFIX));
|
|
459
|
-
if (agentLabels.length === 0) {
|
|
460
|
-
return undefined;
|
|
461
|
-
}
|
|
462
|
-
let disabledFallback;
|
|
463
|
-
for (const label of agentLabels) {
|
|
464
|
-
const name = label.name.slice(AGENT_LABEL_PREFIX.length);
|
|
465
|
-
if (name === AGENT_ANY_MODEL) {
|
|
466
|
-
return { model: AGENT_ANY_MODEL };
|
|
467
|
-
}
|
|
468
|
-
// Own-property check, not `in`: a label like `agent-toString` or
|
|
469
|
-
// `agent-__proto__` would otherwise resolve through the prototype chain
|
|
470
|
-
// instead of falling back to `models.default`.
|
|
471
|
-
if (Object.hasOwn(config.models.definitions, name)) {
|
|
472
|
-
return { model: name };
|
|
473
|
-
}
|
|
474
|
-
if (disabledFallback === undefined && isShippedDefaultDisabled(config, name)) {
|
|
475
|
-
disabledFallback = name;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
const fallback = { model: config.models.default };
|
|
479
|
-
if (disabledFallback !== undefined) {
|
|
480
|
-
fallback.disabledFallback = disabledFallback;
|
|
481
|
-
}
|
|
482
|
-
return fallback;
|
|
483
|
-
}
|
|
484
372
|
export function warnIfDisabledFallback(ticket, modelResolution, config) {
|
|
485
373
|
if (modelResolution.kind !== "disabled-fallback") {
|
|
486
374
|
return;
|
|
@@ -2,4 +2,5 @@ import type { AdapterDefinition } from "../../adapterDefinition.ts";
|
|
|
2
2
|
import { linearAdapterConfigSchema } from "./schema.ts";
|
|
3
3
|
declare const definition: AdapterDefinition<typeof linearAdapterConfigSchema>;
|
|
4
4
|
export default definition;
|
|
5
|
+
export type { LinearSourceRef } from "./factory.ts";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAExD,QAAA,MAAM,UAAU,EAAE,iBAAiB,CAAC,OAAO,yBAAyB,CAInE,CAAC;eAEa,UAAU"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAExD,QAAA,MAAM,UAAU,EAAE,iBAAiB,CAAC,OAAO,yBAAyB,CAInE,CAAC;eAEa,UAAU;AAEzB,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear adapter — parsing helpers for model/repository resolution from
|
|
3
|
+
* issue labels and descriptions. Extracted from boardSource.ts (Task 10).
|
|
4
|
+
*/
|
|
5
|
+
import { type ResolvedConfig } from "../../config.ts";
|
|
6
|
+
export declare const AGENT_LABEL_PREFIX = "agent-";
|
|
7
|
+
export type RepositoryResolution = {
|
|
8
|
+
kind: "ok";
|
|
9
|
+
repository: string;
|
|
10
|
+
} | {
|
|
11
|
+
kind: "missing";
|
|
12
|
+
};
|
|
13
|
+
export type ModelResolution = {
|
|
14
|
+
kind: "matched";
|
|
15
|
+
model: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "no-label";
|
|
18
|
+
} | {
|
|
19
|
+
kind: "agent-any";
|
|
20
|
+
} | {
|
|
21
|
+
kind: "disabled-fallback";
|
|
22
|
+
requestedModel: string;
|
|
23
|
+
fallbackModel: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function buildRepositoryRegex(config: ResolvedConfig): RegExp;
|
|
26
|
+
export declare function resolveRepositoryFor(arguments_: {
|
|
27
|
+
description: string | undefined;
|
|
28
|
+
config: ResolvedConfig;
|
|
29
|
+
}): RepositoryResolution;
|
|
30
|
+
interface ParseRepositoryArguments {
|
|
31
|
+
description: string | undefined;
|
|
32
|
+
config: ResolvedConfig;
|
|
33
|
+
repositoryRegex: RegExp;
|
|
34
|
+
ticket: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function parseRepository(arguments_: ParseRepositoryArguments): string;
|
|
37
|
+
export declare function resolveModelFor(arguments_: {
|
|
38
|
+
labels: {
|
|
39
|
+
name: string;
|
|
40
|
+
}[];
|
|
41
|
+
config: ResolvedConfig;
|
|
42
|
+
}): ModelResolution;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=parsing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsing.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/parsing.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAA6C,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjG,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAE3C,MAAM,MAAM,oBAAoB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAE5F,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AASjF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAUnE;AAiDD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE;IAC/C,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,EAAE,cAAc,CAAC;CACxB,GAAG,oBAAoB,CAuBvB;AAED,UAAU,wBAAwB;IAChC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,wBAAwB,GAAG,MAAM,CA2B5E;AAkDD,wBAAgB,eAAe,CAAC,UAAU,EAAE;IAC1C,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;CACxB,GAAG,eAAe,CAiBlB"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear adapter — parsing helpers for model/repository resolution from
|
|
3
|
+
* issue labels and descriptions. Extracted from boardSource.ts (Task 10).
|
|
4
|
+
*/
|
|
5
|
+
import { AGENT_ANY_MODEL, isShippedDefaultDisabled } from "../../config.js";
|
|
6
|
+
import { RepositoryResolutionError } from "../../ticketSource.js";
|
|
7
|
+
export const AGENT_LABEL_PREFIX = "agent-";
|
|
8
|
+
function escapeRegex(value) {
|
|
9
|
+
return value.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw `\$&`);
|
|
10
|
+
}
|
|
11
|
+
// Sort by descending length so longer names match first — `api-admin`
|
|
12
|
+
// must beat `api` when both are configured. `\b` treats `-` as a word
|
|
13
|
+
// boundary, so without this ordering `api` would win on `api-admin`.
|
|
14
|
+
export function buildRepositoryRegex(config) {
|
|
15
|
+
const candidates = config.workspace.knownRepositories.flatMap((repo) => {
|
|
16
|
+
const slashIndex = repo.indexOf("/");
|
|
17
|
+
return slashIndex === -1 ? [repo] : [repo, repo.slice(slashIndex + 1)];
|
|
18
|
+
});
|
|
19
|
+
const alternation = candidates
|
|
20
|
+
.toSorted((a, b) => b.length - a.length)
|
|
21
|
+
.map(escapeRegex)
|
|
22
|
+
.join("|");
|
|
23
|
+
return new RegExp(String.raw `\b(${alternation})\b`);
|
|
24
|
+
}
|
|
25
|
+
function canonicalizeRepositoryMatch(description, config, repositoryRegex) {
|
|
26
|
+
if (description === undefined || description.length === 0) {
|
|
27
|
+
return { kind: "missing" };
|
|
28
|
+
}
|
|
29
|
+
// Guard against an empty knownRepositories config: buildRepositoryRegex
|
|
30
|
+
// would produce /\b()\b/, which matches the empty string at any word
|
|
31
|
+
// boundary and returns a bogus "" match. Treat that as "no repo could
|
|
32
|
+
// be resolved" so neither the dispatch path nor the doctor path emits
|
|
33
|
+
// a spurious empty-string repository.
|
|
34
|
+
if (config.workspace.knownRepositories.length === 0) {
|
|
35
|
+
return { kind: "missing" };
|
|
36
|
+
}
|
|
37
|
+
const matched = repositoryRegex.exec(description)?.[1];
|
|
38
|
+
if (matched === undefined) {
|
|
39
|
+
return { kind: "missing" };
|
|
40
|
+
}
|
|
41
|
+
const candidates = config.workspace.knownRepositories.filter((r) => r === matched || r.endsWith(`/${matched}`));
|
|
42
|
+
if (candidates.length > 1) {
|
|
43
|
+
return { kind: "ambiguous" };
|
|
44
|
+
}
|
|
45
|
+
if (candidates.length === 1) {
|
|
46
|
+
/* v8 ignore next @preserve -- length-1 guarantees [0] defined */
|
|
47
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- length-1 guarantees [0] is defined
|
|
48
|
+
return { kind: "canonical", repository: candidates[0] };
|
|
49
|
+
}
|
|
50
|
+
return { kind: "unknown", repository: matched };
|
|
51
|
+
}
|
|
52
|
+
export function resolveRepositoryFor(arguments_) {
|
|
53
|
+
const { description, config } = arguments_;
|
|
54
|
+
const match = canonicalizeRepositoryMatch(description, config, buildRepositoryRegex(config));
|
|
55
|
+
switch (match.kind) {
|
|
56
|
+
case "missing":
|
|
57
|
+
case "ambiguous": {
|
|
58
|
+
// Ambiguous matches surface as "missing" so fetchResolvedIssue throws
|
|
59
|
+
// RepositoryResolutionError — same conflation parseRepository uses,
|
|
60
|
+
// and the right call for single-ticket flows: the launcher can't
|
|
61
|
+
// disambiguate "matched N known repos" any more than the dispatcher can.
|
|
62
|
+
return { kind: "missing" };
|
|
63
|
+
}
|
|
64
|
+
case "canonical":
|
|
65
|
+
case "unknown": {
|
|
66
|
+
return { kind: "ok", repository: match.repository };
|
|
67
|
+
}
|
|
68
|
+
/* v8 ignore next 5 @preserve -- exhaustive over CanonicalizedRepositoryMatch.kind */
|
|
69
|
+
default: {
|
|
70
|
+
throw new Error(`resolveRepositoryFor: unexpected match kind ${match.kind}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function parseRepository(arguments_) {
|
|
75
|
+
const { description, config, repositoryRegex, ticket } = arguments_;
|
|
76
|
+
const match = canonicalizeRepositoryMatch(description, config, repositoryRegex);
|
|
77
|
+
switch (match.kind) {
|
|
78
|
+
case "missing":
|
|
79
|
+
case "ambiguous": {
|
|
80
|
+
throw new RepositoryResolutionError({
|
|
81
|
+
ticket,
|
|
82
|
+
repositories: config.workspace.knownRepositories,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
case "canonical": {
|
|
86
|
+
return match.repository;
|
|
87
|
+
}
|
|
88
|
+
case "unknown": {
|
|
89
|
+
// No match in knownRepositories — return the asserted name as-is. The
|
|
90
|
+
// dispatcher's dispatchableRepository helper WARN-logs and skips at
|
|
91
|
+
// the host layer, uniformly across all sources.
|
|
92
|
+
return match.repository;
|
|
93
|
+
}
|
|
94
|
+
/* v8 ignore next 5 @preserve -- exhaustive over CanonicalizedRepositoryMatch.kind */
|
|
95
|
+
default: {
|
|
96
|
+
throw new Error(`parseRepository: unexpected match kind ${match.kind}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function parseAgentLabels(labels, config) {
|
|
101
|
+
const agentLabels = labels.filter((label) => label.name.startsWith(AGENT_LABEL_PREFIX));
|
|
102
|
+
if (agentLabels.length === 0) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
let disabledFallback;
|
|
106
|
+
for (const label of agentLabels) {
|
|
107
|
+
const name = label.name.slice(AGENT_LABEL_PREFIX.length);
|
|
108
|
+
if (name === AGENT_ANY_MODEL) {
|
|
109
|
+
return { model: AGENT_ANY_MODEL };
|
|
110
|
+
}
|
|
111
|
+
// Own-property check, not `in`: a label like `agent-toString` or
|
|
112
|
+
// `agent-__proto__` would otherwise resolve through the prototype chain
|
|
113
|
+
// instead of falling back to `models.default`.
|
|
114
|
+
if (Object.hasOwn(config.models.definitions, name)) {
|
|
115
|
+
return { model: name };
|
|
116
|
+
}
|
|
117
|
+
if (disabledFallback === undefined && isShippedDefaultDisabled(config, name)) {
|
|
118
|
+
disabledFallback = name;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const fallback = { model: config.models.default };
|
|
122
|
+
if (disabledFallback !== undefined) {
|
|
123
|
+
fallback.disabledFallback = disabledFallback;
|
|
124
|
+
}
|
|
125
|
+
return fallback;
|
|
126
|
+
}
|
|
127
|
+
export function resolveModelFor(arguments_) {
|
|
128
|
+
const { labels, config } = arguments_;
|
|
129
|
+
const parsed = parseAgentLabels(labels, config);
|
|
130
|
+
if (parsed === undefined) {
|
|
131
|
+
return { kind: "no-label" };
|
|
132
|
+
}
|
|
133
|
+
if (parsed.model === AGENT_ANY_MODEL) {
|
|
134
|
+
return { kind: "agent-any" };
|
|
135
|
+
}
|
|
136
|
+
if (parsed.disabledFallback !== undefined) {
|
|
137
|
+
return {
|
|
138
|
+
kind: "disabled-fallback",
|
|
139
|
+
requestedModel: parsed.disabledFallback,
|
|
140
|
+
fallbackModel: parsed.model,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { kind: "matched", model: parsed.model };
|
|
144
|
+
}
|
|
@@ -6,10 +6,9 @@ interface LinearIssueReference {
|
|
|
6
6
|
}
|
|
7
7
|
interface LinearIssueStatusUpdater {
|
|
8
8
|
markInProgress(issue: LinearIssueReference): Promise<void>;
|
|
9
|
-
resetMissingInProgressCache(): void;
|
|
10
9
|
}
|
|
11
10
|
export declare function createLinearIssueStatusUpdater(arguments_: {
|
|
12
11
|
client: LinearClient;
|
|
13
12
|
}): LinearIssueStatusUpdater;
|
|
14
13
|
export {};
|
|
15
|
-
//# sourceMappingURL=
|
|
14
|
+
//# sourceMappingURL=writeback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/writeback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,wBAAwB;IAChC,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,wBAAwB,CAgD3B"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { log } from "
|
|
1
|
+
import { log } from "../../util.js";
|
|
2
2
|
export function createLinearIssueStatusUpdater(arguments_) {
|
|
3
3
|
const { client } = arguments_;
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Positive cache only. Keyed by teamId because the workflow `state.type ===
|
|
5
|
+
// "started"` lookup yields a single stateId per team — independent of which
|
|
6
|
+
// project the ticket belongs to. State ids don't change for misconfig
|
|
7
|
+
// reasons, so caching successful resolutions is safe across the process.
|
|
8
|
+
//
|
|
9
|
+
// No negative cache: a missing "started" workflow state is a Linear-side
|
|
10
|
+
// config issue the operator can correct mid-session, and a negative cache
|
|
11
|
+
// would mask that recovery until process restart. Slot count caps
|
|
12
|
+
// markInProgress calls per tick at 1-5, so re-fetching team states on
|
|
13
|
+
// every failing attempt costs at most a handful of extra Linear API calls
|
|
14
|
+
// per tick.
|
|
8
15
|
const inProgressStateByTeam = new Map();
|
|
9
|
-
let teamsMissingInProgress = new Set();
|
|
10
16
|
async function getInProgressStateId(teamId) {
|
|
11
17
|
if (teamId.length === 0) {
|
|
12
18
|
return undefined;
|
|
@@ -15,17 +21,13 @@ export function createLinearIssueStatusUpdater(arguments_) {
|
|
|
15
21
|
if (cached !== undefined) {
|
|
16
22
|
return cached;
|
|
17
23
|
}
|
|
18
|
-
if (teamsMissingInProgress.has(teamId)) {
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
24
|
const team = await client.team(teamId);
|
|
22
25
|
const states = await team.states();
|
|
23
|
-
// Use the workflow state's
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
+
// Use the workflow state's `type` — Linear standardises on `started` for
|
|
27
|
+
// in-progress columns regardless of how the user renames them, so this
|
|
28
|
+
// works without any per-team status-name configuration.
|
|
26
29
|
const inProgress = states.nodes.find((state) => state.type === "started");
|
|
27
30
|
if (inProgress?.id === undefined) {
|
|
28
|
-
teamsMissingInProgress.add(teamId);
|
|
29
31
|
return undefined;
|
|
30
32
|
}
|
|
31
33
|
inProgressStateByTeam.set(teamId, inProgress.id);
|
|
@@ -39,8 +41,5 @@ export function createLinearIssueStatusUpdater(arguments_) {
|
|
|
39
41
|
await client.updateIssue(issue.uuid, { stateId });
|
|
40
42
|
log(`Marked ${issue.id} as in progress`);
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
teamsMissingInProgress = new Set();
|
|
44
|
-
}
|
|
45
|
-
return { markInProgress, resetMissingInProgressCache };
|
|
44
|
+
return { markInProgress };
|
|
46
45
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - `fetch` is required by the Zod schema.
|
|
15
15
|
*/
|
|
16
16
|
import type { AdapterContext } from "../../adapterDefinition.ts";
|
|
17
|
-
import type
|
|
17
|
+
import { type Issue as CanonicalIssue, type TicketSource } from "../../ticketSource.ts";
|
|
18
18
|
import { type ShellAdapterConfig, type ShellIssue } from "./schema.ts";
|
|
19
19
|
export declare function toCanonicalIssue(shellIssue: ShellIssue, sourceName: string): CanonicalIssue;
|
|
20
20
|
export declare function createShellTicketSource(config: ShellAdapterConfig, _context: AdapterContext): TicketSource;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAGL,KAAK,KAAK,IAAI,cAAc,EAC5B,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAyBrB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAsB3F;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,YAAY,CAgFd"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - `markInProgress` absent → silent no-op.
|
|
14
14
|
* - `fetch` is required by the Zod schema.
|
|
15
15
|
*/
|
|
16
|
+
import { toCanonicalId, } from "../../ticketSource.js";
|
|
16
17
|
import { invokeShellCommand } from "./invoke.js";
|
|
17
18
|
import { shellFetchOutputSchema, shellIssueSchema, } from "./schema.js";
|
|
18
19
|
const DEFAULT_TIMEOUTS = {
|
|
@@ -31,12 +32,14 @@ function mergeTimeouts(overrides) {
|
|
|
31
32
|
}
|
|
32
33
|
export function toCanonicalIssue(shellIssue, sourceName) {
|
|
33
34
|
const blockers = shellIssue.blockers.map((b) => ({
|
|
34
|
-
id:
|
|
35
|
+
id: toCanonicalId(sourceName, b.id),
|
|
35
36
|
title: b.title,
|
|
36
37
|
status: b.status,
|
|
38
|
+
...(b.statusReason !== undefined && { statusReason: b.statusReason }),
|
|
39
|
+
...(b.nativeStatus !== undefined && { nativeStatus: b.nativeStatus }),
|
|
37
40
|
}));
|
|
38
41
|
return {
|
|
39
|
-
id:
|
|
42
|
+
id: toCanonicalId(sourceName, shellIssue.id),
|
|
40
43
|
source: sourceName,
|
|
41
44
|
title: shellIssue.title,
|
|
42
45
|
description: shellIssue.description,
|
|
@@ -81,10 +84,11 @@ export function createShellTicketSource(config, _context) {
|
|
|
81
84
|
},
|
|
82
85
|
fetch: runFetch,
|
|
83
86
|
async resolveOne(naturalId) {
|
|
87
|
+
const canonicalId = toCanonicalId(sourceName, naturalId);
|
|
84
88
|
const resolveCommand = config.commands.resolveOne;
|
|
85
89
|
if (resolveCommand === undefined) {
|
|
86
90
|
const all = await runFetch();
|
|
87
|
-
return all.find((i) => i.id ===
|
|
91
|
+
return all.find((i) => i.id === canonicalId);
|
|
88
92
|
}
|
|
89
93
|
const result = await invokeShellCommand({
|
|
90
94
|
command: resolveCommand,
|
|
@@ -93,7 +97,7 @@ export function createShellTicketSource(config, _context) {
|
|
|
93
97
|
env: config.env,
|
|
94
98
|
substitutions: {
|
|
95
99
|
id: naturalId,
|
|
96
|
-
canonicalId
|
|
100
|
+
canonicalId,
|
|
97
101
|
name: sourceName,
|
|
98
102
|
},
|
|
99
103
|
sourceName,
|
|
@@ -11,19 +11,12 @@
|
|
|
11
11
|
* Exit code 0 = success; exit code 3 = "not found" (caller decides how to
|
|
12
12
|
* interpret); any other nonzero exit throws.
|
|
13
13
|
*/
|
|
14
|
-
export declare const SHELL_COMMAND_MAX_BUFFER_BYTES: number;
|
|
15
14
|
export declare class ShellAdapterTimeoutError extends Error {
|
|
16
15
|
constructor(arguments_: {
|
|
17
16
|
command: string;
|
|
18
17
|
timeoutMs: number;
|
|
19
18
|
});
|
|
20
19
|
}
|
|
21
|
-
export declare class ShellAdapterOutputLimitError extends Error {
|
|
22
|
-
constructor(arguments_: {
|
|
23
|
-
command: string;
|
|
24
|
-
maxBytes: number;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
20
|
interface InvokeArgs {
|
|
28
21
|
command: string;
|
|
29
22
|
timeoutMs: number;
|
|
@@ -33,11 +26,15 @@ interface InvokeArgs {
|
|
|
33
26
|
substitutions?: Record<string, string> | undefined;
|
|
34
27
|
/** Source name for log prefixing. */
|
|
35
28
|
sourceName: string;
|
|
29
|
+
/** Override the default per-stream stdout/stderr cap (10 MB). Used by tests. */
|
|
30
|
+
maxOutputBytes?: number;
|
|
36
31
|
}
|
|
37
32
|
interface InvokeResult {
|
|
38
33
|
stdout: string;
|
|
39
34
|
stderr: string;
|
|
40
35
|
exitCode: number;
|
|
36
|
+
/** True if either stream hit the byte cap and the rest was discarded. */
|
|
37
|
+
truncated: boolean;
|
|
41
38
|
}
|
|
42
39
|
export declare function applySubstitutions(command: string, subs: Record<string, string>): string;
|
|
43
40
|
export declare function invokeShellCommand(args: InvokeArgs): Promise<InvokeResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/invoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/invoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,YAAmB,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAGpE;CACF;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACnD,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAC;CACpB;AAMD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAMxF;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAkGhF"}
|