@clipboard-health/groundcrew 4.2.0 → 4.2.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 +15 -25
- 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 +7 -6
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +56 -28
- 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} +22 -74
- package/dist/lib/adapters/linear/fetch.d.ts.map +1 -0
- package/dist/lib/{boardSource.js → adapters/linear/fetch.js} +28 -136
- 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 +73 -3
- 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
11
|
import type { LinearClient } from "@linear/sdk";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
export declare const AGENT_LABEL_PREFIX = "agent-";
|
|
12
|
+
import type { ResolvedConfig } from "../../config.ts";
|
|
13
|
+
import { type ModelResolution } from "./parsing.ts";
|
|
17
14
|
export declare const ISSUES_PAGE_SIZE = 250;
|
|
18
15
|
export interface Blocker {
|
|
19
16
|
id: string;
|
|
@@ -30,6 +27,7 @@ export interface Issue {
|
|
|
30
27
|
id: string;
|
|
31
28
|
uuid: string;
|
|
32
29
|
title: string;
|
|
30
|
+
description: string;
|
|
33
31
|
status: string;
|
|
34
32
|
statusId: string;
|
|
35
33
|
/** Linear workflow `state.type` — the source of truth for canonical classification. */
|
|
@@ -39,33 +37,28 @@ export interface Issue {
|
|
|
39
37
|
/**
|
|
40
38
|
* `undefined` unless the ticket is in Todo with a parseable `agent-*` label
|
|
41
39
|
* and a known-repo reference in its description — i.e. the dispatcher would
|
|
42
|
-
* actually pick it up.
|
|
43
|
-
* tick-spam warnings on already-finished work.
|
|
40
|
+
* actually pick it up. Non-Todo tickets do not resolve repositories because
|
|
41
|
+
* that would invite tick-spam warnings on already-finished work.
|
|
44
42
|
*/
|
|
45
43
|
repository: string | undefined;
|
|
46
|
-
/**
|
|
44
|
+
/** Parsed from the `agent-*` label when present, including non-Todo tickets for slot logs. */
|
|
47
45
|
model: string | undefined;
|
|
48
46
|
teamId: string;
|
|
49
47
|
blockers: Blocker[];
|
|
50
48
|
hasMoreBlockers: boolean;
|
|
51
49
|
}
|
|
52
50
|
/**
|
|
53
|
-
* `Issue` narrowed to "this ticket is for groundcrew"
|
|
54
|
-
*
|
|
55
|
-
*
|
|
51
|
+
* `Issue` narrowed to "this ticket is for groundcrew". Consumers operate on
|
|
52
|
+
* the canonical `GroundcrewIssue` from `ticketSource.ts`; this internal
|
|
53
|
+
* variant just shapes the adapter's local Linear type.
|
|
56
54
|
*/
|
|
57
55
|
export type GroundcrewIssue = Issue & {
|
|
58
56
|
model: string;
|
|
59
57
|
repository: string;
|
|
60
58
|
};
|
|
61
|
-
export declare function isGroundcrewIssue(issue: Issue): issue is GroundcrewIssue;
|
|
62
59
|
/**
|
|
63
60
|
* Linear ticket that was silently dropped from `issues` because it has at
|
|
64
61
|
* least one sub-issue and groundcrew works sub-issues rather than parents.
|
|
65
|
-
* The dispatcher logs each one per tick so operators see WHY a Todo ticket
|
|
66
|
-
* isn't being picked up instead of just "No Todo tickets to pick up." Only
|
|
67
|
-
* Todo+agent-labelled parents qualify — non-actionable parents (e.g. Done
|
|
68
|
-
* epics) would be noise.
|
|
69
62
|
*/
|
|
70
63
|
export interface ParentSkip {
|
|
71
64
|
id: string;
|
|
@@ -77,14 +70,8 @@ export interface BoardState {
|
|
|
77
70
|
issues: Issue[];
|
|
78
71
|
parentSkips: ParentSkip[];
|
|
79
72
|
}
|
|
80
|
-
export { RepositoryResolutionError };
|
|
81
73
|
export interface BoardSource {
|
|
82
|
-
/**
|
|
83
|
-
* Verify the Linear API key resolves to a viewer. Run once at startup so
|
|
84
|
-
* misconfiguration surfaces before the first tick.
|
|
85
|
-
*/
|
|
86
74
|
verify(): Promise<void>;
|
|
87
|
-
/** Fetch the current board snapshot. Paginates internally. */
|
|
88
75
|
fetch(): Promise<BoardState>;
|
|
89
76
|
}
|
|
90
77
|
interface BoardSourceDeps {
|
|
@@ -116,16 +103,6 @@ export interface IssueRelationNode {
|
|
|
116
103
|
export declare function modelForResolution(resolution: Exclude<ModelResolution, {
|
|
117
104
|
kind: "no-label";
|
|
118
105
|
}>): string;
|
|
119
|
-
export declare function resolveTodoAgentMetadata(arguments_: {
|
|
120
|
-
ticket: string;
|
|
121
|
-
description: string | undefined;
|
|
122
|
-
modelResolution: ModelResolution;
|
|
123
|
-
config: ResolvedConfig;
|
|
124
|
-
isTodo: boolean;
|
|
125
|
-
}): {
|
|
126
|
-
repository: string | undefined;
|
|
127
|
-
model: string | undefined;
|
|
128
|
-
};
|
|
129
106
|
interface ResolvedIssue {
|
|
130
107
|
uuid: string;
|
|
131
108
|
title: string;
|
|
@@ -133,6 +110,9 @@ interface ResolvedIssue {
|
|
|
133
110
|
repository: string;
|
|
134
111
|
model: string;
|
|
135
112
|
teamId: string;
|
|
113
|
+
stateType: string;
|
|
114
|
+
status: string;
|
|
115
|
+
statusId: string;
|
|
136
116
|
}
|
|
137
117
|
export interface RawLinearIssue {
|
|
138
118
|
uuid: string;
|
|
@@ -144,7 +124,8 @@ export interface RawLinearIssue {
|
|
|
144
124
|
}[];
|
|
145
125
|
/** Linear workflow state name, e.g. "Todo", "In Review". May be "" if state was null. */
|
|
146
126
|
stateName: string;
|
|
147
|
-
stateType
|
|
127
|
+
stateType: string;
|
|
128
|
+
stateId: string;
|
|
148
129
|
blockers: Blocker[];
|
|
149
130
|
hasMoreBlockers: boolean;
|
|
150
131
|
/**
|
|
@@ -167,40 +148,6 @@ export declare function fetchRawLinearIssue(arguments_: {
|
|
|
167
148
|
export declare function fetchInProgressIssueCount(arguments_: {
|
|
168
149
|
client: LinearClient;
|
|
169
150
|
}): Promise<number>;
|
|
170
|
-
export type RepositoryResolution = {
|
|
171
|
-
kind: "ok";
|
|
172
|
-
repository: string;
|
|
173
|
-
} | {
|
|
174
|
-
kind: "missing";
|
|
175
|
-
};
|
|
176
|
-
export declare function resolveRepositoryFor(arguments_: {
|
|
177
|
-
description: string | undefined;
|
|
178
|
-
config: ResolvedConfig;
|
|
179
|
-
ticket: string;
|
|
180
|
-
}): RepositoryResolution;
|
|
181
|
-
export type ModelResolution = {
|
|
182
|
-
kind: "matched";
|
|
183
|
-
model: string;
|
|
184
|
-
} | {
|
|
185
|
-
kind: "no-label";
|
|
186
|
-
} | {
|
|
187
|
-
kind: "agent-any";
|
|
188
|
-
} | {
|
|
189
|
-
kind: "disabled-fallback";
|
|
190
|
-
requestedModel: string;
|
|
191
|
-
fallbackModel: string;
|
|
192
|
-
};
|
|
193
|
-
export declare function resolveModelFor(arguments_: {
|
|
194
|
-
labels: {
|
|
195
|
-
name: string;
|
|
196
|
-
}[];
|
|
197
|
-
config: ResolvedConfig;
|
|
198
|
-
}): ModelResolution;
|
|
199
|
-
/**
|
|
200
|
-
* `agent-any` collapses to `models.default` here — manual setup doesn't run
|
|
201
|
-
* the usage-gated `any` resolver, so the caller gets a concrete model name
|
|
202
|
-
* instead of a sentinel that downstream code can't interpret.
|
|
203
|
-
*/
|
|
204
151
|
export declare function fetchResolvedIssue(arguments_: {
|
|
205
152
|
client: LinearClient;
|
|
206
153
|
config: ResolvedConfig;
|
|
@@ -208,4 +155,5 @@ export declare function fetchResolvedIssue(arguments_: {
|
|
|
208
155
|
}): Promise<ResolvedIssue>;
|
|
209
156
|
export declare function warnIfDisabledFallback(ticket: string, modelResolution: ModelResolution, config: ResolvedConfig): void;
|
|
210
157
|
export declare function blockersFromRelations(relations: IssueRelationNode[]): Blocker[];
|
|
211
|
-
|
|
158
|
+
export {};
|
|
159
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AAEtB,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAYpC,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B;;;;OAIG;IACH,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,8FAA8F;IAC9F,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAUpE;AAkBD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG,OAAO,CAE1E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG,OAAO,CAEpE;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE1E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG,OAAO,CAEjF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAEpE;AAwBD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;KAChD,GAAG,IAAI,CAAC;CACV;AAmFD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,OAAO,CAAC,eAAe,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,GACzD,MAAM,CAQR;AAiGD,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3B,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAsB,sBAAsB,CAAC,UAAU,EAAE;IACvD,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CA8C9B;AAED,wBAAsB,mBAAmB,CAAC,UAAU,EAAE;IACpD,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,cAAc,CAAC,CAiE1B;AAUD,wBAAsB,yBAAyB,CAAC,UAAU,EAAE;IAC1D,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2ClB;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE;IACnD,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,aAAa,CAAC,CAiCzB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,cAAc,GACrB,IAAI,CAON;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,iBAAiB,EAAE,GAAG,OAAO,EAAE,CAS/E"}
|
|
@@ -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,19 +140,23 @@ 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 resolveAgentMetadata(arguments_) {
|
|
169
146
|
const { ticket, description, modelResolution, config, isTodo } = arguments_;
|
|
170
147
|
let repository;
|
|
171
148
|
let model;
|
|
172
|
-
if (modelResolution.kind
|
|
173
|
-
|
|
149
|
+
if (modelResolution.kind === "no-label") {
|
|
150
|
+
return { repository, model };
|
|
151
|
+
}
|
|
152
|
+
model = modelForResolution(modelResolution);
|
|
153
|
+
if (isTodo) {
|
|
154
|
+
const resolution = resolveRepositoryFor({ description, config });
|
|
174
155
|
if (resolution.kind === "ok") {
|
|
175
156
|
({ repository } = resolution);
|
|
176
|
-
model = modelForResolution(modelResolution);
|
|
177
157
|
}
|
|
178
158
|
else {
|
|
159
|
+
model = undefined;
|
|
179
160
|
log(`WARNING: ${ticket} has an ${AGENT_LABEL_PREFIX}* label but no known repository in its description; skipping dispatch. Add one of workspace.knownRepositories to the description, or remove the ${AGENT_LABEL_PREFIX}* label: ${config.workspace.knownRepositories.join(", ")}`);
|
|
180
161
|
}
|
|
181
162
|
}
|
|
@@ -186,6 +167,7 @@ function buildLinearIssue(input) {
|
|
|
186
167
|
id: input.identifier.toLowerCase(),
|
|
187
168
|
uuid: input.uuid,
|
|
188
169
|
title: input.title,
|
|
170
|
+
description: input.description,
|
|
189
171
|
status: input.status,
|
|
190
172
|
statusId: input.statusId,
|
|
191
173
|
stateType: input.stateType,
|
|
@@ -202,12 +184,7 @@ function buildLinearIssue(input) {
|
|
|
202
184
|
function issueFromNode(node, config) {
|
|
203
185
|
const modelResolution = resolveModelFor({ labels: node.labels.nodes, config });
|
|
204
186
|
warnIfDisabledFallback(node.identifier, modelResolution, config);
|
|
205
|
-
|
|
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
|
-
const { repository, model } = resolveTodoAgentMetadata({
|
|
187
|
+
const { repository, model } = resolveAgentMetadata({
|
|
211
188
|
ticket: node.identifier,
|
|
212
189
|
/* v8 ignore next @preserve -- BoardIssues query selects description; the ?? guard normalises a null vs undefined edge */
|
|
213
190
|
description: node.description ?? undefined,
|
|
@@ -219,6 +196,8 @@ function issueFromNode(node, config) {
|
|
|
219
196
|
identifier: node.identifier,
|
|
220
197
|
uuid: node.id,
|
|
221
198
|
title: node.title,
|
|
199
|
+
/* v8 ignore next @preserve -- BoardIssues query always selects description; this `?? ""` is a defensive null vs undefined edge */
|
|
200
|
+
description: node.description ?? "",
|
|
222
201
|
/* v8 ignore next @preserve -- BoardIssues query always returns state */
|
|
223
202
|
status: node.state?.name ?? "Unknown",
|
|
224
203
|
/* v8 ignore next @preserve -- BoardIssues query always returns state */
|
|
@@ -233,23 +212,6 @@ function issueFromNode(node, config) {
|
|
|
233
212
|
inverseRelations: node.inverseRelations,
|
|
234
213
|
});
|
|
235
214
|
}
|
|
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
215
|
const ISSUE_LABEL_PAGE_SIZE = 50;
|
|
254
216
|
const ISSUE_RELATION_PAGE_SIZE = 50;
|
|
255
217
|
export async function fetchBlockersForTicket(arguments_) {
|
|
@@ -294,7 +256,7 @@ export async function fetchRawLinearIssue(arguments_) {
|
|
|
294
256
|
title
|
|
295
257
|
description
|
|
296
258
|
team { id }
|
|
297
|
-
state { name type }
|
|
259
|
+
state { id name type }
|
|
298
260
|
children { nodes { id } }
|
|
299
261
|
labels(first: ${ISSUE_LABEL_PAGE_SIZE}) {
|
|
300
262
|
nodes { name }
|
|
@@ -328,6 +290,8 @@ export async function fetchRawLinearIssue(arguments_) {
|
|
|
328
290
|
stateName: issue.state?.name ?? "",
|
|
329
291
|
/* v8 ignore next @preserve -- ResolveIssue query selects state; null only if Linear genuinely returns a stateless ticket */
|
|
330
292
|
stateType: issue.state?.type ?? "",
|
|
293
|
+
/* v8 ignore next @preserve -- ResolveIssue query selects state; null only if Linear genuinely returns a stateless ticket */
|
|
294
|
+
stateId: issue.state?.id ?? "",
|
|
331
295
|
blockers: blockersFromRelations(issue.inverseRelations?.nodes ?? []),
|
|
332
296
|
hasMoreBlockers: issue.inverseRelations?.pageInfo.hasNextPage ?? false,
|
|
333
297
|
hasChildren: (issue.children?.nodes.length ?? 0) > 0,
|
|
@@ -374,53 +338,6 @@ export async function fetchInProgressIssueCount(arguments_) {
|
|
|
374
338
|
after = page.pageInfo.endCursor;
|
|
375
339
|
}
|
|
376
340
|
}
|
|
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
341
|
export async function fetchResolvedIssue(arguments_) {
|
|
425
342
|
const { client, config, ticket } = arguments_;
|
|
426
343
|
const upper = ticket.toUpperCase();
|
|
@@ -428,7 +345,6 @@ export async function fetchResolvedIssue(arguments_) {
|
|
|
428
345
|
const repositoryResolution = resolveRepositoryFor({
|
|
429
346
|
description: raw.description,
|
|
430
347
|
config,
|
|
431
|
-
ticket: upper,
|
|
432
348
|
});
|
|
433
349
|
if (repositoryResolution.kind === "missing") {
|
|
434
350
|
throw new RepositoryResolutionError({
|
|
@@ -452,35 +368,11 @@ export async function fetchResolvedIssue(arguments_) {
|
|
|
452
368
|
repository: repositoryResolution.repository,
|
|
453
369
|
model,
|
|
454
370
|
teamId: raw.teamId,
|
|
371
|
+
stateType: raw.stateType,
|
|
372
|
+
status: raw.stateName,
|
|
373
|
+
statusId: raw.stateId,
|
|
455
374
|
};
|
|
456
375
|
}
|
|
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
376
|
export function warnIfDisabledFallback(ticket, modelResolution, config) {
|
|
485
377
|
if (modelResolution.kind !== "disabled-fallback") {
|
|
486
378
|
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"}
|