@clipboard-health/groundcrew 4.15.0 → 4.16.0

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.
@@ -4,10 +4,11 @@ import type { Config } from "@clipboard-health/groundcrew";
4
4
  export default {
5
5
  // Groundcrew's built-in Linear adapter is implicit and needs no config:
6
6
  // it picks up every Linear issue assigned to your API key's viewer that
7
- // carries an `agent-*` label. There is no project / view / status
8
- // block Linear's workflow `state.type` (`unstarted` todo,
9
- // `started` in progress, `completed`/`canceled`/`duplicate`
10
- // terminal) is the single source of truth, so renamed columns Just Work.
7
+ // carries an `agent-*` label. There is no project / view block. The default
8
+ // Linear status names `In Progress` and `In Review` disambiguate Linear's
9
+ // `started` workflow states; other statuses fall back to workflow
10
+ // `state.type` (`unstarted` todo, `started` in progress,
11
+ // `completed`/`canceled`/`duplicate` → terminal).
11
12
  //
12
13
  // Opt a ticket in: assign it to yourself and add an `agent-<model>`
13
14
  // label (e.g. `agent-claude`, `agent-any`).
@@ -53,6 +54,15 @@ export default {
53
54
  // // See the shell adapter's ShellIssue schema for the JSON contract
54
55
  // // `fetch` / `resolveOne` must emit.
55
56
  // sources: [
57
+ // // Optional: explicitly declare Linear only when you need custom status
58
+ // // names. Omitted fields keep their defaults.
59
+ // {
60
+ // kind: "linear",
61
+ // statuses: {
62
+ // inProgress: ["Doing"],
63
+ // inReview: ["Code Review"],
64
+ // },
65
+ // },
56
66
  // {
57
67
  // kind: "shell",
58
68
  // name: "jira",
@@ -5,9 +5,9 @@
5
5
  * ./client.ts) and converts Linear-specific shapes into the canonical
6
6
  * Issue/Blocker types consumers (via Board) speak.
7
7
  *
8
- * State classification is driven by Linear's workflow `state.type` never
9
- * by status name so workspaces with renamed columns Just Work without
10
- * per-team config.
8
+ * Status names disambiguate Linear's multiple `started` states ("In Progress"
9
+ * vs "In Review"). Unmatched statuses fall back to workflow `state.type` so
10
+ * renamed or custom columns still preserve the broad lifecycle behavior.
11
11
  *
12
12
  * Description is populated on both `fetch()` Issues and `resolveOne()` Issues.
13
13
  */
@@ -15,6 +15,7 @@ import type { AdapterContext } from "../../adapterDefinition.ts";
15
15
  import { type Issue as CanonicalIssue, type TicketSource } from "../../ticketSource.ts";
16
16
  import type { LinearAdapterConfig } from "./schema.ts";
17
17
  import { type Issue as LinearIssue } from "./fetch.ts";
18
+ import { type LinearStatusNames } from "./statusNames.ts";
18
19
  /**
19
20
  * Adapter-private payload threaded through `Issue.sourceRef`. Consumers
20
21
  * MUST NOT inspect; only the Linear adapter reads it.
@@ -28,6 +29,6 @@ export interface LinearSourceRef {
28
29
  /** Human-readable native status name, e.g. "In Progress", "Shipped". Diagnostic display only. */
29
30
  nativeStatus: string;
30
31
  }
31
- export declare function toCanonicalIssue(linearIssue: LinearIssue, sourceName: string): CanonicalIssue;
32
+ export declare function toCanonicalIssue(linearIssue: LinearIssue, sourceName: string, statusNames?: LinearStatusNames): CanonicalIssue;
32
33
  export declare function createLinearTicketSource(config: LinearAdapterConfig, context: AdapterContext): TicketSource;
33
34
  //# sourceMappingURL=factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAKL,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,YAAY,CAAC;AAGpB;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,YAAY,EAAE,MAAM,CAAC;CACtB;AAkFD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB7F;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,YAAY,CAyFd"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAIL,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,YAAY,CAAC;AACpB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AAG1B;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,YAAY,EAAE,MAAM,CAAC;CACtB;AAmDD,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,iBAA+C,GAC3D,cAAc,CA2BhB;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,YAAY,CAiGd"}
@@ -5,73 +5,37 @@
5
5
  * ./client.ts) and converts Linear-specific shapes into the canonical
6
6
  * Issue/Blocker types consumers (via Board) speak.
7
7
  *
8
- * State classification is driven by Linear's workflow `state.type` never
9
- * by status name so workspaces with renamed columns Just Work without
10
- * per-team config.
8
+ * Status names disambiguate Linear's multiple `started` states ("In Progress"
9
+ * vs "In Review"). Unmatched statuses fall back to workflow `state.type` so
10
+ * renamed or custom columns still preserve the broad lifecycle behavior.
11
11
  *
12
12
  * Description is populated on both `fetch()` Issues and `resolveOne()` Issues.
13
13
  */
14
14
  import { toCanonicalId, } from "../../ticketSource.js";
15
15
  import { getLinearClient, lazyLinearClient } from "./client.js";
16
- import { createBoardSource, fetchResolvedIssue, isTerminalStateType, } from "./fetch.js";
16
+ import { createBoardSource, fetchResolvedIssue, } from "./fetch.js";
17
+ import { canonicalStatusFromLinearState, DEFAULT_LINEAR_STATUS_NAMES, resolveLinearStatusNames, } from "./statusNames.js";
17
18
  import { createLinearIssueStatusUpdater } from "./writeback.js";
18
- function canonicalStatusFromStateType(stateType) {
19
- /* v8 ignore next 3 @preserve -- LinearIssue.stateType is non-optional; this guard is defensive for the resolveOne path */
20
- if (stateType === undefined) {
21
- return "other";
22
- }
23
- switch (stateType) {
24
- case "unstarted": {
25
- return "todo";
26
- }
27
- case "started": {
28
- return "in-progress";
29
- }
30
- case "completed":
31
- case "canceled":
32
- case "duplicate": {
33
- return "done";
34
- }
35
- default: {
36
- return "other";
37
- }
38
- }
39
- }
40
- function canonicalBlockerStatus(blocker) {
41
- if (blocker.stateType === undefined) {
42
- return {
43
- status: "other",
44
- statusReason: "missing",
45
- ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
46
- };
47
- }
48
- if (isTerminalStateType(blocker.stateType)) {
49
- return {
50
- status: "done",
51
- ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
52
- };
53
- }
54
- if (blocker.stateType === "started") {
55
- return {
56
- status: "in-progress",
57
- ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
58
- };
59
- }
60
- if (blocker.stateType === "unstarted") {
19
+ function canonicalBlockerStatus(blocker, statusNames) {
20
+ const status = canonicalStatusFromLinearState({
21
+ nativeStatus: blocker.status,
22
+ stateType: blocker.stateType,
23
+ statusNames,
24
+ });
25
+ if (status !== "other") {
61
26
  return {
62
- status: "todo",
27
+ status,
63
28
  ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
64
29
  };
65
30
  }
66
- // backlog / triage / anything else falls through as "other"
67
31
  return {
68
- status: "other",
69
- statusReason: "unmapped",
32
+ status,
33
+ statusReason: blocker.stateType === undefined ? "missing" : "unmapped",
70
34
  ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
71
35
  };
72
36
  }
73
- function toCanonicalBlocker(blocker, sourceName) {
74
- const { status, statusReason, nativeStatus } = canonicalBlockerStatus(blocker);
37
+ function toCanonicalBlocker(blocker, sourceName, statusNames) {
38
+ const { status, statusReason, nativeStatus } = canonicalBlockerStatus(blocker, statusNames);
75
39
  return {
76
40
  id: toCanonicalId(sourceName, blocker.id),
77
41
  title: blocker.title,
@@ -87,7 +51,7 @@ function toCanonicalParentSkip(skip, sourceName) {
87
51
  childCount: skip.childCount,
88
52
  };
89
53
  }
90
- export function toCanonicalIssue(linearIssue, sourceName) {
54
+ export function toCanonicalIssue(linearIssue, sourceName, statusNames = DEFAULT_LINEAR_STATUS_NAMES) {
91
55
  const sourceRef = {
92
56
  uuid: linearIssue.uuid,
93
57
  statusId: linearIssue.statusId,
@@ -100,12 +64,16 @@ export function toCanonicalIssue(linearIssue, sourceName) {
100
64
  source: sourceName,
101
65
  title: linearIssue.title,
102
66
  description: linearIssue.description,
103
- status: canonicalStatusFromStateType(linearIssue.stateType),
67
+ status: canonicalStatusFromLinearState({
68
+ nativeStatus: linearIssue.status,
69
+ stateType: linearIssue.stateType,
70
+ statusNames,
71
+ }),
104
72
  repository: linearIssue.repository,
105
73
  model: linearIssue.model,
106
74
  assignee: linearIssue.assignee,
107
75
  updatedAt: linearIssue.updatedAt,
108
- blockers: linearIssue.blockers.map((b) => toCanonicalBlocker(b, sourceName)),
76
+ blockers: linearIssue.blockers.map((b) => toCanonicalBlocker(b, sourceName, statusNames)),
109
77
  hasMoreBlockers: linearIssue.hasMoreBlockers,
110
78
  url: linearIssue.url,
111
79
  sourceRef,
@@ -113,6 +81,7 @@ export function toCanonicalIssue(linearIssue, sourceName) {
113
81
  }
114
82
  export function createLinearTicketSource(config, context) {
115
83
  const sourceName = config.name ?? "linear";
84
+ const statusNames = resolveLinearStatusNames(config.statuses);
116
85
  const { globalConfig } = context;
117
86
  // Lazy: deferring `getLinearClient()` (and the sub-modules that depend on
118
87
  // it) until first method use means `createLinearTicketSource` can be
@@ -131,6 +100,7 @@ export function createLinearTicketSource(config, context) {
131
100
  function getIssueStatusUpdater() {
132
101
  cachedIssueStatusUpdater ??= createLinearIssueStatusUpdater({
133
102
  client: getClient(),
103
+ statusNames,
134
104
  });
135
105
  return cachedIssueStatusUpdater;
136
106
  }
@@ -143,7 +113,7 @@ export function createLinearTicketSource(config, context) {
143
113
  async fetch() {
144
114
  const state = await getBoardSource().fetch();
145
115
  lastParentSkips = state.parentSkips.map((skip) => toCanonicalParentSkip(skip, sourceName));
146
- return state.issues.map((linearIssue) => toCanonicalIssue(linearIssue, sourceName));
116
+ return state.issues.map((linearIssue) => toCanonicalIssue(linearIssue, sourceName, statusNames));
147
117
  },
148
118
  async fetchParentSkips() {
149
119
  return lastParentSkips;
@@ -166,7 +136,11 @@ export function createLinearTicketSource(config, context) {
166
136
  source: sourceName,
167
137
  title: resolved.title,
168
138
  description: resolved.description,
169
- status: canonicalStatusFromStateType(resolved.stateType),
139
+ status: canonicalStatusFromLinearState({
140
+ nativeStatus: resolved.status,
141
+ stateType: resolved.stateType,
142
+ statusNames,
143
+ }),
170
144
  repository: resolved.repository,
171
145
  model: resolved.model,
172
146
  assignee: "Unassigned",
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Linear adapter — GraphQL fetch helpers for board/issue data.
3
3
  *
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`
7
- * (`unstarted` | `started` | `completed` | `canceled` | `duplicate`) —
8
- * never by status name — so workspaces with renamed columns (Todo -> To Do,
9
- * Done -> Shipped, etc.) Just Work without per-team config.
4
+ * There is no project or view configuration: the only server-side filter is
5
+ * "assigned to the API key's viewer AND carries an `agent-*` label." This
6
+ * module returns Linear's native status name plus workflow `state.type`; the
7
+ * ticket-source factory applies status-name disambiguation and state-type
8
+ * fallback when building canonical issues.
10
9
  */
11
10
  import type { LinearClient } from "@linear/sdk";
12
11
  import type { ResolvedConfig } from "../../config.ts";
@@ -18,8 +17,8 @@ export interface Blocker {
18
17
  status: string | undefined;
19
18
  /**
20
19
  * Linear workflow `state.type` for the blocker (`unstarted` | `started` |
21
- * `completed` | `canceled` | `duplicate` | `backlog` | `triage`). All
22
- * canonical classification todo / in-progress / terminal keys off this.
20
+ * `completed` | `canceled` | `duplicate` | `backlog` | `triage`). Canonical
21
+ * classification uses this as a fallback after configured status names.
23
22
  */
24
23
  stateType: string | undefined;
25
24
  }
@@ -30,7 +29,7 @@ export interface Issue {
30
29
  description: string;
31
30
  status: string;
32
31
  statusId: string;
33
- /** Linear workflow `state.type` — the source of truth for canonical classification. */
32
+ /** Linear workflow `state.type` — canonical classification fallback after status-name matching. */
34
33
  stateType: string;
35
34
  assignee: string;
36
35
  updatedAt: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/fetch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/fetch.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Linear adapter — GraphQL fetch helpers for board/issue data.
3
3
  *
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`
7
- * (`unstarted` | `started` | `completed` | `canceled` | `duplicate`) —
8
- * never by status name — so workspaces with renamed columns (Todo -> To Do,
9
- * Done -> Shipped, etc.) Just Work without per-team config.
4
+ * There is no project or view configuration: the only server-side filter is
5
+ * "assigned to the API key's viewer AND carries an `agent-*` label." This
6
+ * module returns Linear's native status name plus workflow `state.type`; the
7
+ * ticket-source factory applies status-name disambiguation and state-type
8
+ * fallback when building canonical issues.
10
9
  */
11
10
  import { RepositoryResolutionError } from "../../ticketSource.js";
12
11
  import { log, styleWarning } from "../../util.js";
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * Zod schema for the Linear adapter's per-source config block. The built-in
3
3
  * Linear adapter is implicit and derives scope from the API key's viewer plus
4
- * `agent-*` labels, so the source config only needs an optional display name.
4
+ * `agent-*` labels. Source config is only needed to override display name or
5
+ * Linear status names that disambiguate multiple `started` workflow states.
5
6
  */
6
7
  import { z } from "zod";
7
8
  export declare const linearAdapterConfigSchema: z.ZodObject<{
8
9
  kind: z.ZodLiteral<"linear">;
9
10
  name: z.ZodOptional<z.ZodString>;
11
+ statuses: z.ZodOptional<z.ZodObject<{
12
+ inProgress: z.ZodOptional<z.ZodArray<z.ZodString>>;
13
+ inReview: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
+ }, z.core.$strip>>;
10
15
  }, z.core.$strip>;
11
16
  export type LinearAdapterConfig = z.infer<typeof linearAdapterConfigSchema>;
12
17
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,yBAAyB;;;iBAMpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,yBAAyB;;;;;;;iBAYpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
@@ -1,13 +1,21 @@
1
1
  /**
2
2
  * Zod schema for the Linear adapter's per-source config block. The built-in
3
3
  * Linear adapter is implicit and derives scope from the API key's viewer plus
4
- * `agent-*` labels, so the source config only needs an optional display name.
4
+ * `agent-*` labels. Source config is only needed to override display name or
5
+ * Linear status names that disambiguate multiple `started` workflow states.
5
6
  */
6
7
  import { z } from "zod";
8
+ const statusNamesSchema = z.array(z.string().trim().min(1)).min(1);
7
9
  export const linearAdapterConfigSchema = z.object({
8
10
  kind: z.literal("linear"),
9
11
  name: z
10
12
  .string()
11
13
  .regex(/^[a-z][a-z0-9-]*$/, "name must be kebab-case (lowercase letters, digits, hyphens)")
12
14
  .optional(),
15
+ statuses: z
16
+ .object({
17
+ inProgress: statusNamesSchema.optional(),
18
+ inReview: statusNamesSchema.optional(),
19
+ })
20
+ .optional(),
13
21
  });
@@ -0,0 +1,25 @@
1
+ import type { CanonicalStatus } from "../../ticketSource.ts";
2
+ import type { LinearAdapterConfig } from "./schema.ts";
3
+ export interface LinearStatusNames {
4
+ inProgress: readonly string[];
5
+ inReview: readonly string[];
6
+ }
7
+ export interface LinearWorkflowState {
8
+ id: string;
9
+ name: string;
10
+ type: string;
11
+ position: number;
12
+ }
13
+ export declare const DEFAULT_LINEAR_STATUS_NAMES: {
14
+ readonly inProgress: readonly ["In Progress"];
15
+ readonly inReview: readonly ["In Review"];
16
+ };
17
+ export declare function resolveLinearStatusNames(config: LinearAdapterConfig["statuses"] | undefined): LinearStatusNames;
18
+ export declare function canonicalStatusFromLinearState(arguments_: {
19
+ nativeStatus: string | undefined;
20
+ stateType: string | undefined;
21
+ statusNames: LinearStatusNames;
22
+ }): CanonicalStatus;
23
+ export declare function findLinearWorkflowStateByName(states: readonly LinearWorkflowState[], names: readonly string[]): LinearWorkflowState | undefined;
24
+ export declare function formatLinearStatusNames(names: readonly string[]): string;
25
+ //# sourceMappingURL=statusNames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusNames.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/statusNames.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,2BAA2B;;;CAGF,CAAC;AAEvC,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAAG,SAAS,GAClD,iBAAiB,CAKnB;AAED,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,WAAW,EAAE,iBAAiB,CAAC;CAChC,GAAG,eAAe,CAWlB;AAED,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,SAAS,mBAAmB,EAAE,EACtC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,mBAAmB,GAAG,SAAS,CAEjC;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAExE"}
@@ -0,0 +1,60 @@
1
+ export const DEFAULT_LINEAR_STATUS_NAMES = {
2
+ inProgress: ["In Progress"],
3
+ inReview: ["In Review"],
4
+ };
5
+ export function resolveLinearStatusNames(config) {
6
+ return {
7
+ inProgress: config?.inProgress ?? DEFAULT_LINEAR_STATUS_NAMES.inProgress,
8
+ inReview: config?.inReview ?? DEFAULT_LINEAR_STATUS_NAMES.inReview,
9
+ };
10
+ }
11
+ export function canonicalStatusFromLinearState(arguments_) {
12
+ const { nativeStatus, stateType, statusNames } = arguments_;
13
+ if (stateType === "started") {
14
+ if (matchesLinearStatusName(nativeStatus, statusNames.inReview)) {
15
+ return "in-review";
16
+ }
17
+ if (matchesLinearStatusName(nativeStatus, statusNames.inProgress)) {
18
+ return "in-progress";
19
+ }
20
+ }
21
+ return canonicalStatusFromStateType(stateType);
22
+ }
23
+ export function findLinearWorkflowStateByName(states, names) {
24
+ return states.find((state) => matchesLinearStatusName(state.name, names));
25
+ }
26
+ export function formatLinearStatusNames(names) {
27
+ return names.map((name) => `"${name}"`).join(" or ");
28
+ }
29
+ function canonicalStatusFromStateType(stateType) {
30
+ /* v8 ignore next 3 @preserve -- LinearIssue.stateType is non-optional; this guard is defensive for the resolveOne path */
31
+ if (stateType === undefined) {
32
+ return "other";
33
+ }
34
+ switch (stateType) {
35
+ case "unstarted": {
36
+ return "todo";
37
+ }
38
+ case "started": {
39
+ return "in-progress";
40
+ }
41
+ case "completed":
42
+ case "canceled":
43
+ case "duplicate": {
44
+ return "done";
45
+ }
46
+ default: {
47
+ return "other";
48
+ }
49
+ }
50
+ }
51
+ function matchesLinearStatusName(nativeStatus, configuredNames) {
52
+ if (nativeStatus === undefined) {
53
+ return false;
54
+ }
55
+ const normalizedStatus = normalizeStatusName(nativeStatus);
56
+ return configuredNames.some((name) => normalizeStatusName(name) === normalizedStatus);
57
+ }
58
+ function normalizeStatusName(name) {
59
+ return name.trim().toLowerCase();
60
+ }
@@ -1,5 +1,6 @@
1
1
  import type { LinearClient } from "@linear/sdk";
2
2
  import type { MarkInReviewResult } from "../../ticketSource.ts";
3
+ import { type LinearStatusNames } from "./statusNames.ts";
3
4
  interface LinearIssueReference {
4
5
  id: string;
5
6
  uuid: string;
@@ -11,6 +12,7 @@ interface LinearIssueStatusUpdater {
11
12
  }
12
13
  export declare function createLinearIssueStatusUpdater(arguments_: {
13
14
  client: LinearClient;
15
+ statusNames?: LinearStatusNames;
14
16
  }): LinearIssueStatusUpdater;
15
17
  export {};
16
18
  //# sourceMappingURL=writeback.d.ts.map
@@ -1 +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;AAEhD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,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;IAC3D,YAAY,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACxE;AAaD,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,wBAAwB,CAuD3B"}
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;AAEhD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,EAIL,KAAK,iBAAiB,EAEvB,MAAM,kBAAkB,CAAC;AAE1B,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;IAC3D,YAAY,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACxE;AAED,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC,GAAG,wBAAwB,CA0F3B"}
@@ -1,16 +1,7 @@
1
1
  import { debug } from "../../util.js";
2
- // Linear maps every `started` workflow state to canonical in-progress today,
3
- // so it cannot prove a successful in-review transition. Report unsupported
4
- // until read/write sides can distinguish "In Review" from generic started.
5
- async function markInReviewUnsupported(issue) {
6
- debug(`markInReview is unsupported for ${issue.id} (Linear in-review not yet implemented)`);
7
- return {
8
- outcome: "unsupported",
9
- reason: "Linear in-review writeback is not implemented",
10
- };
11
- }
2
+ import { DEFAULT_LINEAR_STATUS_NAMES, findLinearWorkflowStateByName, formatLinearStatusNames, } from "./statusNames.js";
12
3
  export function createLinearIssueStatusUpdater(arguments_) {
13
- const { client } = arguments_;
4
+ const { client, statusNames = DEFAULT_LINEAR_STATUS_NAMES } = arguments_;
14
5
  // Positive cache only. Keyed by teamId because the in-progress-state
15
6
  // resolution yields a single stateId per team — independent of which
16
7
  // project the ticket belongs to. State ids don't change for misconfig
@@ -23,6 +14,12 @@ export function createLinearIssueStatusUpdater(arguments_) {
23
14
  // every failing attempt costs at most a handful of extra Linear API calls
24
15
  // per tick.
25
16
  const inProgressStateByTeam = new Map();
17
+ const inReviewStateByTeam = new Map();
18
+ async function fetchWorkflowStates(teamId) {
19
+ const team = await client.team(teamId);
20
+ const states = await team.states();
21
+ return states.nodes;
22
+ }
26
23
  async function getInProgressStateId(teamId) {
27
24
  if (teamId.length === 0) {
28
25
  return undefined;
@@ -31,17 +28,14 @@ export function createLinearIssueStatusUpdater(arguments_) {
31
28
  if (cached !== undefined) {
32
29
  return cached;
33
30
  }
34
- const team = await client.team(teamId);
35
- const states = await team.states();
36
31
  // Linear's default workflow has MULTIPLE `started`-type states — both
37
32
  // "In Progress" and "In Review" are `started`. `team.states()` orders by
38
33
  // updatedAt (the connection has no position ordering), so array order
39
- // can't disambiguate them. Prefer the state literally named "In Progress";
40
- // otherwise fall back to the lowest-position (leftmost) `started` column,
41
- // which by Linear convention is the in-progress one. This survives teams
42
- // that rename the column ("Doing", "WIP", ...).
43
- const startedStates = states.nodes.filter((state) => state.type === "started");
44
- const inProgress = startedStates.find((state) => state.name.trim().toLowerCase() === "in progress") ??
34
+ // can't disambiguate them. Prefer configured/default in-progress names;
35
+ // otherwise fall back to the lowest-position (leftmost) `started` column.
36
+ const states = await fetchWorkflowStates(teamId);
37
+ const startedStates = states.filter((state) => state.type === "started");
38
+ const inProgress = findLinearWorkflowStateByName(startedStates, statusNames.inProgress) ??
45
39
  startedStates.toSorted((a, b) => a.position - b.position).at(0);
46
40
  if (inProgress?.id === undefined) {
47
41
  return undefined;
@@ -49,6 +43,23 @@ export function createLinearIssueStatusUpdater(arguments_) {
49
43
  inProgressStateByTeam.set(teamId, inProgress.id);
50
44
  return inProgress.id;
51
45
  }
46
+ async function getInReviewStateId(teamId) {
47
+ if (teamId.length === 0) {
48
+ return undefined;
49
+ }
50
+ const cached = inReviewStateByTeam.get(teamId);
51
+ if (cached !== undefined) {
52
+ return cached;
53
+ }
54
+ const states = await fetchWorkflowStates(teamId);
55
+ const startedStates = states.filter((state) => state.type === "started");
56
+ const inReview = findLinearWorkflowStateByName(startedStates, statusNames.inReview);
57
+ if (inReview?.id === undefined) {
58
+ return undefined;
59
+ }
60
+ inReviewStateByTeam.set(teamId, inReview.id);
61
+ return inReview.id;
62
+ }
52
63
  async function markInProgress(issue) {
53
64
  const stateId = await getInProgressStateId(issue.teamId);
54
65
  if (stateId === undefined) {
@@ -57,5 +68,17 @@ export function createLinearIssueStatusUpdater(arguments_) {
57
68
  await client.updateIssue(issue.uuid, { stateId });
58
69
  debug(`Marked ${issue.id} as in progress`);
59
70
  }
60
- return { markInProgress, markInReview: markInReviewUnsupported };
71
+ async function markInReview(issue) {
72
+ const stateId = await getInReviewStateId(issue.teamId);
73
+ if (stateId === undefined) {
74
+ return {
75
+ outcome: "unsupported",
76
+ reason: `Could not find a Linear workflow state named ${formatLinearStatusNames(statusNames.inReview)} for team ${issue.teamId.length > 0 ? issue.teamId : "?"}`,
77
+ };
78
+ }
79
+ await client.updateIssue(issue.uuid, { stateId });
80
+ debug(`Marked ${issue.id} as in review`);
81
+ return { outcome: "applied" };
82
+ }
83
+ return { markInProgress, markInReview };
61
84
  }
@@ -123,9 +123,9 @@ type UserModelDefinition = EnabledUserModelDefinition;
123
123
  *
124
124
  * Groundcrew's built-in Linear adapter is implicit and needs no config:
125
125
  * it picks up every Linear issue assigned to the API key's viewer that
126
- * carries an `agent-*` label. There is no project / view / status
127
- * configuration — Linear's workflow `state.type` is the source of truth
128
- * for todo / in-progress / terminal classification.
126
+ * carries an `agent-*` label. There is no project or view configuration.
127
+ * Linear's default "In Progress" / "In Review" status names disambiguate
128
+ * `started` workflow states; unmatched statuses fall back to `state.type`.
129
129
  */
130
130
  export interface Config {
131
131
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAMrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAIzD,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,MAAM,CAAC;AAEtD,eAAO,MAAM,qBAAqB,EAAE,SAAS,kBAAkB,EAMrD,CAAC;AAEX;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,QAAQ,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACjD,CAAC;IACF;;;;OAIG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,KAAK,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC;AAC/D,KAAK,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,GAAG;IAC1E,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AACF,KAAK,mBAAmB,GAAG,0BAA0B,CAAC;AAEtD;;;;;;;;;GASG;AACH,MAAM,WAAW,MAAM;IACrB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,GAAG,CAAC,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB;;;;WAIG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,iBAAiB,EAAE,MAAM,EAAE,CAAC;KAC7B,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,YAAY,CAAC;KACtB,CAAC;IACF,YAAY,CAAC,EAAE;QACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,sBAAsB,CAAC,EAAE,MAAM,CAAC;KACjC,CAAC;IACF,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB;;;;;WAKG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;KACnD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF;;;;OAIG;IACH,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC;;;;OAIG;IACH,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,kBAAkB,CAAC;KAC7B,CAAC;IACF,OAAO,CAAC,EAAE;QACR;;;;;WAKG;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,GAAG,EAAE;QACH,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,iBAAiB,EAAE,MAAM,EAAE,CAAC;KAC7B,CAAC;IACF,QAAQ,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;KACrB,CAAC;IACF,YAAY,EAAE;QACZ,iBAAiB,EAAE,MAAM,CAAC;QAC1B,wBAAwB,EAAE,MAAM,CAAC;QACjC,sBAAsB,EAAE,MAAM,CAAC;KAChC,CAAC;IACF,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;KAC9C,CAAC;IACF,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF;;;OAGG;IACH,aAAa,EAAE,oBAAoB,CAAC;IACpC;;;;OAIG;IACH,KAAK,EAAE;QACL,MAAM,EAAE,kBAAkB,CAAC;KAC5B,CAAC;IACF,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;AAEzD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IACjC,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;CAChC;AAsND;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,GAAG,OAAO,CAE1F;AA6FD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,EACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAKT;AA4bD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CA2B5E;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAGpE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -372,7 +372,8 @@ function failOnLegacyLinearShape(user) {
372
372
  fail([
373
373
  "The `linear` config block is no longer supported.",
374
374
  "Groundcrew now picks up every Linear issue assigned to your API key's viewer that carries an `agent-*` label —",
375
- "no project / view / status configuration needed. Remove the `linear: { ... }` block from your config.",
375
+ "remove the `linear: { ... }` block from your config.",
376
+ 'To customize Linear status names, declare `sources: [{ kind: "linear", statuses: { ... } }]` instead.',
376
377
  "If you only want a subset of your Linear tickets to be picked up, leave the unwanted tickets unassigned or remove their `agent-*` label.",
377
378
  ].join("\n"));
378
379
  }
@@ -11,12 +11,12 @@
11
11
  * Source-neutral status enum every adapter normalises its native vocabulary
12
12
  * to. Consumers branch on these values, never on a source's native names.
13
13
  *
14
- * - `todo` / `in-progress` / `done`: the only canonical states the built-in
15
- * Linear adapter produces today (mapped from Linear's workflow `state.type`).
16
- * - `in-review`: produced only by adapters whose schema declares an in-review
17
- * mapping. The shell adapter's JSON contract accepts it; the built-in Linear
18
- * adapter does not yet map any native state to it. Reserved here so
19
- * consumers' branch logic doesn't change when that follow-up lands.
14
+ * - `todo` / `in-progress` / `done`: broad lifecycle states mapped from each
15
+ * source's native vocabulary.
16
+ * - `in-review`: review-stage work that should no longer consume a dispatch
17
+ * slot but should not be cleaned up as terminal. The built-in Linear adapter
18
+ * maps default/configured review status names here; the shell adapter's JSON
19
+ * contract accepts it directly.
20
20
  * - `other`: anything an adapter sees but can't classify (Linear tickets in
21
21
  * `backlog`/`triage`, blockers with no resolvable state).
22
22
  */
@@ -35,8 +35,8 @@ export interface Blocker {
35
35
  * (e.g., Linear had no state on the blocker; shell script omitted
36
36
  * the field).
37
37
  * - `"unmapped"`: the source returned a status that isn't in the
38
- * source's known mapping (e.g., a Linear column not in
39
- * `linear.projects[*].statuses`, or an unrecognized shell value).
38
+ * source's known mapping (e.g., a Linear column not covered by
39
+ * `sources[*].statuses`, or an unrecognized shell value).
40
40
  *
41
41
  * MUST be undefined when `status !== "other"`.
42
42
  */
@@ -8,7 +8,7 @@ Workspace settings and at least one enabled model are required; everything else
8
8
  | `workspace.knownRepositories` | Repos searched for in ticket descriptions to infer where work belongs. |
9
9
  | `models.definitions` | Enabled model set. Built-in presets can be enabled with `{}`. |
10
10
 
11
- The branch prefix (`<prefix>-<TICKET>`) defaults to `os.userInfo().username`; override it with `git.branchPrefix` (see the full reference below). Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. There is no `linear` config block. Groundcrew picks up every issue assigned to your API key's viewer that carries an `agent-*` label across every visible team and project, governed by a single `orchestrator.maximumInProgress` budget.
11
+ The branch prefix (`<prefix>-<TICKET>`) defaults to `os.userInfo().username`; override it with `git.branchPrefix` (see the full reference below). Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Groundcrew picks up every issue assigned to your API key's viewer that carries an `agent-*` label across every visible team and project, governed by a single `orchestrator.maximumInProgress` budget.
12
12
 
13
13
  ## Repository Layout
14
14
 
@@ -48,7 +48,25 @@ The "Loaded config from ..." line at startup tells you which config won.
48
48
  - No `agent-*` label is ignored by `crew run`. Dispatch on demand with `crew start <TICKET>`, which falls back to `models.default`.
49
49
  - Todo tickets blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
50
50
 
51
- Status classification uses Linear's workflow `state.type` (`unstarted`, `started`, `completed`, `canceled`, `duplicate`), so renamed status columns work without configuration. Parent issues with children are ignored; sub-issues are the work items.
51
+ Status classification uses Linear's default status names `In Progress` and `In Review` to disambiguate multiple `started` workflow states. Statuses that do not match those names fall back to Linear's workflow `state.type` (`unstarted`, `started`, `completed`, `canceled`, `duplicate`), so broad lifecycle classification still works without configuration. Parent issues with children are ignored; sub-issues are the work items.
52
+
53
+ If your Linear workflow uses different names, explicitly declare the built-in Linear source and override only the names you need:
54
+
55
+ ```ts
56
+ export default {
57
+ sources: [
58
+ {
59
+ kind: "linear",
60
+ statuses: {
61
+ inProgress: ["Doing"],
62
+ inReview: ["Code Review"],
63
+ },
64
+ },
65
+ ],
66
+ };
67
+ ```
68
+
69
+ Configured names replace the default for that status; omitted fields keep their defaults. Matching is case-insensitive and trims surrounding whitespace.
52
70
 
53
71
  ## Enabling Model Presets
54
72
 
@@ -130,7 +148,7 @@ and hook contract.
130
148
 
131
149
  | Key | Default | What it does |
132
150
  | ---------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
133
- | `sources` | `[]` | Additional pluggable ticket sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. |
151
+ | `sources` | `[]` | Additional pluggable ticket sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. Declare `{ kind: "linear", statuses: { ... } }` only to override Linear status names used for `in-progress` / `in-review` disambiguation. |
134
152
  | `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
135
153
  | `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
136
154
  | `git.branchPrefix` | OS username | Prefix groundcrew puts before the ticket id when naming a worktree branch (`<branchPrefix>-<ticket>`). Must be a slash-free slug of letters, digits, `.`, `_`, or `-`. Defaults to the OS account username. Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Prefer a per-user config for personal prefixes — a committed `git.branchPrefix` gives every contributor the same branch prefix. |
@@ -29,7 +29,7 @@ This applies to the tmux backend only.
29
29
 
30
30
  ## Tickets Stay In-Progress
31
31
 
32
- Groundcrew sets a ticket to `Started`, the first workflow state with `type === "started"` on that team, when it provisions a workspace and never advances it. The next transition, typically "In Review" when a PR opens, is left to your Linear automation rules.
32
+ Groundcrew marks a ticket `In Progress` when it provisions a workspace. When a PR opens on that worktree branch, the reviewer pass attempts to mark the ticket `In Review`. Linear's default `In Review` status works out of the box; if your team renamed it, configure `sources: [{ kind: "linear", statuses: { inReview: ["Code Review"] } }]`.
33
33
 
34
34
  ## Claude Launches In Auto Mode By Default
35
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.15.0",
3
+ "version": "4.16.0",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",