@clipboard-health/groundcrew 4.23.0 → 4.24.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.
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EACL,KAAK,UAAU,EAGf,KAAK,KAAK,EAEX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,UAAU,EAAE;QACpB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,+FAA+F;QAC/F,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAaD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAyNjE;AA2BD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAQrE"}
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EACL,KAAK,UAAU,EAGf,KAAK,KAAK,EAEX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,UAAU,EAAE;QACpB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,+FAA+F;QAC/F,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAiCD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAmOjE;AA2BD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAQrE"}
@@ -6,9 +6,9 @@
6
6
  * Pure verdict logic lives in `eligibility.ts`; this module is responsible
7
7
  * for telemetry, writeback via Board, and side-effecting setupWorkspace calls.
8
8
  */
9
- import { dispatchableRepository } from "../lib/repositoryValidation.js";
9
+ import { dispatchableRepository, formatKnownRepositories } from "../lib/repositoryValidation.js";
10
10
  import { isGroundcrewIssue, naturalIdFromCanonical, } from "../lib/taskSource.js";
11
- import { errorMessage, failMark, log, logEvent } from "../lib/util.js";
11
+ import { errorMessage, failMark, log, logEvent, styleWarning } from "../lib/util.js";
12
12
  import { workspaces } from "../lib/workspaces.js";
13
13
  import { classifyBlockers, classifyEligibility, classifyUsageExhaustion, } from "./eligibility.js";
14
14
  import { setupWorkspace } from "./setupWorkspace.js";
@@ -22,6 +22,17 @@ function logSkip(verdict) {
22
22
  model: verdict.model,
23
23
  });
24
24
  }
25
+ function logMissingRepositorySkip(issue, model, knownRepositories) {
26
+ const task = naturalIdFromCanonical(issue.id);
27
+ log(styleWarning(`WARNING: ${issue.id} has agent "${model}" but no repository; skipping dispatch. Add --repo <repo> when creating the task, add repo:<repo> to the task line, or set defaultRepository on source "${issue.source}". Known repositories: ${formatKnownRepositories(knownRepositories)}`));
28
+ logEvent("dispatch", {
29
+ outcome: "skipped",
30
+ reason: "missing_repository",
31
+ task,
32
+ source: issue.source,
33
+ model,
34
+ });
35
+ }
25
36
  export function createDispatcher(deps) {
26
37
  const { config, board } = deps;
27
38
  function buildExhaustedSet(usage) {
@@ -109,20 +120,31 @@ export function createDispatcher(deps) {
109
120
  .toSorted((a, b) => a.id.localeCompare(b.id));
110
121
  const activeCount = active.length;
111
122
  const slots = config.orchestrator.maximumInProgress - activeCount;
112
- // Narrow Todo to tasks that opted in via an `agent-*` label.
113
- // Unlabeled tasks are not groundcrew's concern even when in Todo.
123
+ const rawTodo = state.issues.filter((issue) => issue.status === "todo");
124
+ for (const issue of rawTodo) {
125
+ const { model, repository } = issue;
126
+ if (model !== undefined && repository === undefined) {
127
+ logMissingRepositorySkip(issue, model, config.workspace.knownRepositories);
128
+ }
129
+ }
130
+ // Narrow queued work to tasks that opted in with an agent and resolved a
131
+ // repository. Unlabeled tasks are not groundcrew's concern.
114
132
  // Sort by priority so higher-priority tasks fill slots first.
115
- const todo = state.issues
116
- .filter((issue) => issue.status === "todo" && isGroundcrewIssue(issue))
133
+ const todo = rawTodo
134
+ .filter((issue) => isGroundcrewIssue(issue))
117
135
  .toSorted((a, b) => prioritySortKey(a.priority) - prioritySortKey(b.priority));
118
136
  if (slots <= 0) {
119
137
  log(`At capacity (${activeCount}/${config.orchestrator.maximumInProgress})${formatActiveSlotList(active)}, no new work to start${idleSuffix}`);
120
138
  return;
121
139
  }
122
- if (todo.length === 0) {
140
+ if (rawTodo.length === 0) {
123
141
  log(`No Todo tasks to pick up${idleSuffix}`);
124
142
  return;
125
143
  }
144
+ if (todo.length === 0) {
145
+ log(`No eligible Todo tasks after agent/repository filtering${idleSuffix}`);
146
+ return;
147
+ }
126
148
  // Run the blocker pre-pass first so an all-blocked board short-circuits
127
149
  // before the codexbar HTTP call and the cmux/tmux shell-out fire.
128
150
  const { unblocked, skips: blockerSkips } = classifyBlockers(todo);
@@ -1 +1 @@
1
- {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AAmmBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3D"}
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AAwmBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3D"}
@@ -54,6 +54,9 @@ const CREATE_VALUE_HANDLERS = {
54
54
  "--repo": (state, value) => {
55
55
  state.repository = value;
56
56
  },
57
+ "--team": (state, value) => {
58
+ state.team = value;
59
+ },
57
60
  "--id": (state, value) => {
58
61
  state.id = value;
59
62
  },
@@ -240,6 +243,7 @@ function parseCreateOptions(argv) {
240
243
  dependencies: state.dependencies,
241
244
  edit: state.edit,
242
245
  ...(state.repository === undefined ? {} : { repository: state.repository }),
246
+ ...(state.team === undefined ? {} : { team: state.team }),
243
247
  ...(state.id === undefined ? {} : { id: state.id }),
244
248
  ...(state.priority === undefined ? {} : { priority: state.priority }),
245
249
  ...(state.due === undefined ? {} : { due: state.due }),
@@ -0,0 +1,15 @@
1
+ import type { LinearClient } from "@linear/sdk";
2
+ import type { ResolvedConfig } from "../../config.ts";
3
+ import type { CreateTaskInput } from "../../taskSource.ts";
4
+ import { type Issue as LinearIssue } from "./fetch.ts";
5
+ import type { LinearAdapterConfig } from "./schema.ts";
6
+ interface CreateLinearIssueArguments {
7
+ client: LinearClient;
8
+ config: ResolvedConfig;
9
+ input: CreateTaskInput;
10
+ sourceConfig: LinearAdapterConfig;
11
+ sourceName: string;
12
+ }
13
+ export declare function createLinearIssue(arguments_: CreateLinearIssueArguments): Promise<LinearIssue>;
14
+ export {};
15
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAsB,KAAK,KAAK,IAAI,WAAW,EAAE,MAAM,YAAY,CAAC;AAE3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKvD,UAAU,0BAA0B;IAClC,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,mBAAmB,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AA4BD,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,0BAA0B,GACrC,OAAO,CAAC,WAAW,CAAC,CA2DtB"}
@@ -0,0 +1,273 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fetchResolvedIssue } from "./fetch.js";
3
+ import { AGENT_LABEL_PREFIX, resolveRepositoryFor } from "./parsing.js";
4
+ import { findLinearWorkflowStateByName } from "./statusNames.js";
5
+ const TODO_STATE_NAMES = ["Todo", "To Do"];
6
+ export async function createLinearIssue(arguments_) {
7
+ const { client, config, input, sourceConfig, sourceName } = arguments_;
8
+ assertSupportedCreateInput(input);
9
+ const title = normalizeTitle(input.title);
10
+ const repository = resolveCreateRepository({ config, input });
11
+ const teamSelector = resolveTeamSelector({ input, sourceConfig });
12
+ const agentLabelName = resolveAgentLabelName(input.agent);
13
+ const priority = linearPriority(input.priority);
14
+ const description = buildLinearDescription({ input, repository, title });
15
+ const context = await fetchCreateContext({ client, teamSelector, agentLabelName });
16
+ const team = requireExactlyOne(context.teams.nodes, `Linear team "${teamSelector}"`);
17
+ const todoState = requireTodoState(team.states.nodes, team.key);
18
+ const agentLabel = requireExactlyOne(team.labels.nodes, `Linear label "${agentLabelName}"`);
19
+ if (context.viewer === null) {
20
+ throw new Error("Linear API did not return a viewer for this API key.");
21
+ }
22
+ const createdIdentifier = await createIssue({
23
+ client,
24
+ assigneeId: context.viewer.id,
25
+ description,
26
+ dueDate: input.due,
27
+ labelId: agentLabel.id,
28
+ priority,
29
+ stateId: todoState.id,
30
+ teamId: team.id,
31
+ title,
32
+ });
33
+ for (const dependency of input.dependencies) {
34
+ // oxlint-disable-next-line no-await-in-loop -- relation creation targets the issue created above and should fail fast in input order
35
+ await createBlockedByRelation({
36
+ client,
37
+ dependency,
38
+ relatedIssueId: createdIdentifier,
39
+ sourceName,
40
+ });
41
+ }
42
+ const resolved = await fetchResolvedIssue({ client, config, task: createdIdentifier });
43
+ return {
44
+ id: createdIdentifier.toLowerCase(),
45
+ uuid: resolved.uuid,
46
+ title: resolved.title,
47
+ description: resolved.description,
48
+ status: resolved.status,
49
+ statusId: resolved.statusId,
50
+ stateType: resolved.stateType,
51
+ assignee: resolved.assignee,
52
+ updatedAt: resolved.updatedAt,
53
+ repository: resolved.repository,
54
+ model: resolved.model,
55
+ teamId: resolved.teamId,
56
+ blockers: resolved.blockers,
57
+ hasMoreBlockers: resolved.hasMoreBlockers,
58
+ url: resolved.url,
59
+ priority: resolved.priority,
60
+ };
61
+ }
62
+ function assertSupportedCreateInput(input) {
63
+ if (input.id !== undefined) {
64
+ throw new Error("linear: --id is not supported; Linear assigns issue identifiers");
65
+ }
66
+ if (input.recurrence !== undefined) {
67
+ throw new Error("linear: --rec is not supported");
68
+ }
69
+ if (input.edit) {
70
+ throw new Error("linear: --edit is not supported");
71
+ }
72
+ }
73
+ function normalizeTitle(title) {
74
+ const normalized = title.trim();
75
+ if (normalized.length === 0) {
76
+ throw new Error("linear: title is required");
77
+ }
78
+ if (/[\r\n]/.test(normalized)) {
79
+ throw new Error("linear: title must be a single line");
80
+ }
81
+ return normalized;
82
+ }
83
+ function resolveCreateRepository(arguments_) {
84
+ const { config, input } = arguments_;
85
+ if (input.repository === undefined) {
86
+ throw new Error("linear: --repo is required so Groundcrew can route the task");
87
+ }
88
+ const resolution = resolveRepositoryFor({
89
+ description: `Repository: ${input.repository}`,
90
+ config,
91
+ });
92
+ if (resolution.kind === "missing") {
93
+ throw new Error(`linear: repository "${input.repository}" is not in workspace.knownRepositories`);
94
+ }
95
+ return resolution.repository;
96
+ }
97
+ function resolveTeamSelector(arguments_) {
98
+ const { input, sourceConfig } = arguments_;
99
+ const selector = input.team ?? sourceConfig.team;
100
+ if (selector === undefined) {
101
+ throw new Error('linear: team is required. Pass --team <key-or-id> or configure sources: [{ kind: "linear", team: "<key-or-id>" }].');
102
+ }
103
+ const normalized = selector.trim();
104
+ if (normalized.length === 0) {
105
+ throw new Error("linear: team must be a non-empty string");
106
+ }
107
+ return normalized;
108
+ }
109
+ function resolveAgentLabelName(agent) {
110
+ const normalized = agent.trim();
111
+ if (normalized.length === 0) {
112
+ throw new Error("linear: --agent must be a non-empty string");
113
+ }
114
+ return `${AGENT_LABEL_PREFIX}${normalized}`;
115
+ }
116
+ function linearPriority(priority) {
117
+ if (priority === undefined) {
118
+ return undefined;
119
+ }
120
+ const parsed = Number.parseInt(priority, 10);
121
+ if (!Number.isInteger(parsed) || String(parsed) !== priority || parsed < 0 || parsed > 4) {
122
+ throw new Error("linear: --priority must be an integer from 0 to 4");
123
+ }
124
+ return parsed;
125
+ }
126
+ function buildLinearDescription(arguments_) {
127
+ const { input, repository, title } = arguments_;
128
+ return [
129
+ "## Groundcrew",
130
+ "",
131
+ `Repository: ${repository}`,
132
+ "Implementation workflow: use the `core:go`/`go` skill when available. If that skill is unavailable, follow this repo's AGENTS.md/CLAUDE.md implementation workflow and run the documented verification.",
133
+ "",
134
+ "## Task",
135
+ "",
136
+ promptBody(input, title),
137
+ "",
138
+ "## Acceptance Criteria",
139
+ "",
140
+ `- [ ] ${title}`,
141
+ "",
142
+ "## Notes",
143
+ "",
144
+ notesFor(input),
145
+ ].join("\n");
146
+ }
147
+ function promptBody(input, fallbackTitle) {
148
+ if (input.promptFile !== undefined && input.description !== undefined) {
149
+ throw new Error("linear: --prompt-file and --description are mutually exclusive");
150
+ }
151
+ if (input.promptFile !== undefined) {
152
+ return readFileSync(input.promptFile, "utf8").trim();
153
+ }
154
+ return input.description?.trim() ?? fallbackTitle;
155
+ }
156
+ function notesFor(input) {
157
+ const notes = [];
158
+ if (input.projects.length > 0) {
159
+ notes.push(`Projects: ${input.projects.join(", ")}`);
160
+ }
161
+ if (input.contexts.length > 0) {
162
+ notes.push(`Contexts: ${input.contexts.join(", ")}`);
163
+ }
164
+ if (input.dependencies.length > 0) {
165
+ notes.push(`Blocked by: ${input.dependencies.join(", ")}`);
166
+ }
167
+ if (input.due !== undefined) {
168
+ notes.push(`Due: ${input.due}`);
169
+ }
170
+ return notes.length === 0 ? "None" : notes.join("\n");
171
+ }
172
+ async function fetchCreateContext(arguments_) {
173
+ const { client, teamSelector, agentLabelName } = arguments_;
174
+ const response = await client.client.rawRequest(`query CreateLinearTaskContext($teamSelector: String!, $teamSelectorId: ID!, $agentLabelName: String!) {
175
+ viewer { id name }
176
+ teams(
177
+ filter: {
178
+ or: [
179
+ { id: { eq: $teamSelectorId } }
180
+ { key: { eqIgnoreCase: $teamSelector } }
181
+ ]
182
+ }
183
+ first: 2
184
+ includeArchived: false
185
+ ) {
186
+ nodes {
187
+ id
188
+ key
189
+ name
190
+ labels(filter: { name: { eq: $agentLabelName } }, first: 2, includeArchived: false) {
191
+ nodes { id name }
192
+ }
193
+ states(first: 50, includeArchived: false) {
194
+ nodes { id name type position }
195
+ }
196
+ }
197
+ }
198
+ }`, { teamSelector, teamSelectorId: teamSelector, agentLabelName });
199
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- shape is fixed by the GraphQL query above
200
+ return response.data;
201
+ }
202
+ function requireExactlyOne(items, label) {
203
+ const [item] = items;
204
+ if (items.length !== 1 || item === undefined) {
205
+ throw new Error(`linear: expected exactly one ${label}, found ${items.length}`);
206
+ }
207
+ return item;
208
+ }
209
+ function requireTodoState(states, teamKey) {
210
+ const unstartedStates = states.filter((state) => state.type === "unstarted");
211
+ const todoState = findLinearWorkflowStateByName(unstartedStates, TODO_STATE_NAMES) ??
212
+ unstartedStates.toSorted((a, b) => a.position - b.position).at(0);
213
+ if (todoState === undefined) {
214
+ throw new Error(`linear: could not find a Todo workflow state for team ${teamKey}`);
215
+ }
216
+ return todoState;
217
+ }
218
+ async function createIssue(arguments_) {
219
+ const { client, assigneeId, description, dueDate, labelId, priority, stateId, teamId, title } = arguments_;
220
+ const response = await client.client.rawRequest(`mutation CreateLinearTask($input: IssueCreateInput!) {
221
+ issueCreate(input: $input) {
222
+ success
223
+ issue { identifier }
224
+ }
225
+ }`, {
226
+ input: {
227
+ assigneeId,
228
+ description,
229
+ labelIds: [labelId],
230
+ stateId,
231
+ teamId,
232
+ title,
233
+ ...(dueDate === undefined ? {} : { dueDate }),
234
+ ...(priority === undefined ? {} : { priority }),
235
+ },
236
+ });
237
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- shape is fixed by the GraphQL mutation above
238
+ const data = response.data;
239
+ if (!data.issueCreate.success || data.issueCreate.issue === null) {
240
+ throw new Error("linear: issueCreate did not return a created issue");
241
+ }
242
+ return data.issueCreate.issue.identifier;
243
+ }
244
+ async function createBlockedByRelation(arguments_) {
245
+ const { client, dependency, relatedIssueId, sourceName } = arguments_;
246
+ const issueId = normalizeLinearDependencyId(dependency, sourceName);
247
+ const response = await client.client.rawRequest(`mutation CreateLinearTaskBlockedBy($input: IssueRelationCreateInput!) {
248
+ issueRelationCreate(input: $input) {
249
+ success
250
+ }
251
+ }`, {
252
+ input: {
253
+ issueId,
254
+ relatedIssueId,
255
+ type: "blocks",
256
+ },
257
+ });
258
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- shape is fixed by the GraphQL mutation above
259
+ const data = response.data;
260
+ if (!data.issueRelationCreate.success) {
261
+ throw new Error(`linear: could not create blocked-by relation for ${dependency}`);
262
+ }
263
+ }
264
+ function normalizeLinearDependencyId(dependency, sourceName) {
265
+ const sourcePrefix = `${sourceName}:`;
266
+ if (dependency.toLowerCase().startsWith(sourcePrefix.toLowerCase())) {
267
+ return dependency.slice(sourcePrefix.length);
268
+ }
269
+ if (dependency.includes(":")) {
270
+ throw new Error(`linear: dependency "${dependency}" is not a Linear task id for source "${sourceName}"`);
271
+ }
272
+ return dependency;
273
+ }
@@ -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,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAC7B,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;AA2DD,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,iBAA+C,GAC3D,cAAc,CA4BhB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,UAAU,CAwHZ"}
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,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAIL,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,YAAY,CAAC;AAEpB,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;AA2DD,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,iBAA+C,GAC3D,cAAc,CA4BhB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,UAAU,CAqIZ"}
@@ -14,6 +14,7 @@
14
14
  import { toCanonicalId, } from "../../taskSource.js";
15
15
  import { getLinearClient, lazyLinearClient } from "./client.js";
16
16
  import { createBoardSource, fetchResolvedIssue, } from "./fetch.js";
17
+ import { createLinearIssue } from "./create.js";
17
18
  import { canonicalStatusFromLinearState, DEFAULT_LINEAR_STATUS_NAMES, resolveLinearStatusNames, } from "./statusNames.js";
18
19
  import { createLinearIssueStatusUpdater } from "./writeback.js";
19
20
  function canonicalBlockerStatus(blocker, statusNames) {
@@ -170,6 +171,15 @@ export function createLinearTaskSource(config, context) {
170
171
  async getTask(naturalId) {
171
172
  return await getTask(naturalId);
172
173
  },
174
+ async createTask(input) {
175
+ return toCanonicalIssue(await createLinearIssue({
176
+ client: getClient(),
177
+ config: globalConfig,
178
+ input,
179
+ sourceConfig: config,
180
+ sourceName,
181
+ }), sourceName, statusNames);
182
+ },
173
183
  async fetch() {
174
184
  return await listTasks();
175
185
  },
@@ -9,6 +9,7 @@ export declare const linearAdapterConfigSchema: z.ZodObject<{
9
9
  kind: z.ZodLiteral<"linear">;
10
10
  enabled: z.ZodOptional<z.ZodBoolean>;
11
11
  name: z.ZodOptional<z.ZodString>;
12
+ team: z.ZodOptional<z.ZodString>;
12
13
  statuses: z.ZodOptional<z.ZodObject<{
13
14
  inProgress: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
15
  inReview: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1 +1 @@
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;;;;;;;;iBAkBpC,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;;;;;;;;;iBAoBpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
@@ -18,6 +18,8 @@ export const linearAdapterConfigSchema = z.object({
18
18
  .string()
19
19
  .regex(/^[a-z][a-z0-9-]*$/, "name must be kebab-case (lowercase letters, digits, hyphens)")
20
20
  .optional(),
21
+ /** Team key or id used by `crew task create --source linear` when --team is omitted. */
22
+ team: z.string().trim().min(1).optional(),
21
23
  statuses: z
22
24
  .object({
23
25
  inProgress: statusNamesSchema.optional(),
@@ -9,5 +9,6 @@
9
9
  * the tick across N sources.
10
10
  */
11
11
  import type { Issue } from "./taskSource.ts";
12
+ export declare function formatKnownRepositories(knownRepositories: readonly string[]): string;
12
13
  export declare function dispatchableRepository(issue: Issue, knownRepositories: readonly string[], log: (message: string) => void): string | undefined;
13
14
  //# sourceMappingURL=repositoryValidation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repositoryValidation.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,MAAM,GAAG,SAAS,CAWpB"}
1
+ {"version":3,"file":"repositoryValidation.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,wBAAgB,uBAAuB,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAEpF;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,MAAM,GAAG,SAAS,CAWpB"}
@@ -8,12 +8,15 @@
8
8
  * the MVP-2 plan): one badly-labelled task should not throw and abort
9
9
  * the tick across N sources.
10
10
  */
11
+ export function formatKnownRepositories(knownRepositories) {
12
+ return knownRepositories.join(", ") || "(none)";
13
+ }
11
14
  export function dispatchableRepository(issue, knownRepositories, log) {
12
15
  if (issue.repository === undefined) {
13
16
  return undefined;
14
17
  }
15
18
  if (!knownRepositories.includes(issue.repository)) {
16
- log(`issue ${issue.id} references unknown repository ${issue.repository}; configured workspace.knownRepositories: ${knownRepositories.join(", ") || "(none)"}`);
19
+ log(`issue ${issue.id} references unknown repository ${issue.repository}; configured workspace.knownRepositories: ${formatKnownRepositories(knownRepositories)}`);
17
20
  return undefined;
18
21
  }
19
22
  return issue.repository;
@@ -1 +1 @@
1
- {"version":3,"file":"runState.d.ts","sourceRoot":"","sources":["../../src/lib/runState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,kBAAkB,CAAC;AAE3F,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,GAAG;QACrD,KAAK,EAAE,iBAAiB,CAAC;KAC1B,CAAC;CACH;AAaD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,MAAM,CAEjF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1F;AAmFD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA0BnE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,GAAG,SAAS,CAc/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzE"}
1
+ {"version":3,"file":"runState.d.ts","sourceRoot":"","sources":["../../src/lib/runState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,kBAAkB,CAAC;AAE3F,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,GAAG;QACrD,KAAK,EAAE,iBAAiB,CAAC;KAC1B,CAAC;CACH;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,MAAM,CAEjF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1F;AAmFD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA0BnE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,GAAG,SAAS,CAc/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzE"}
@@ -1,13 +1,9 @@
1
1
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
- const TASK_RE = /^[a-z][\da-z]*-\d+$/;
3
+ import { normalizePlainTaskId } from "./taskId.js";
4
4
  const RUN_STATE_DIRECTORY_NAME = "runs";
5
5
  function taskKey(task) {
6
- const normalized = task.toLowerCase();
7
- if (!TASK_RE.test(normalized)) {
8
- throw new Error(`Invalid task "${task}": must be a plain task id`);
9
- }
10
- return normalized;
6
+ return normalizePlainTaskId(task);
11
7
  }
12
8
  export function runStateDirectory(config) {
13
9
  return path.resolve(path.dirname(config.logging.file), RUN_STATE_DIRECTORY_NAME);
@@ -0,0 +1,4 @@
1
+ export declare function isPlainTaskId(task: string): boolean;
2
+ export declare function assertPlainTaskId(task: string): void;
3
+ export declare function normalizePlainTaskId(task: string): string;
4
+ //# sourceMappingURL=taskId.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taskId.d.ts","sourceRoot":"","sources":["../../src/lib/taskId.ts"],"names":[],"mappings":"AAMA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAIpD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMzD"}
@@ -0,0 +1,19 @@
1
+ const PLAIN_TASK_ID_RE = /^[a-z][\da-z]*(?:-[\da-z]+)*-\d+$/;
2
+ function invalidPlainTaskIdError(task) {
3
+ return new Error(`Invalid task "${task}": must be a plain task id`);
4
+ }
5
+ export function isPlainTaskId(task) {
6
+ return PLAIN_TASK_ID_RE.test(task);
7
+ }
8
+ export function assertPlainTaskId(task) {
9
+ if (!isPlainTaskId(task)) {
10
+ throw invalidPlainTaskIdError(task);
11
+ }
12
+ }
13
+ export function normalizePlainTaskId(task) {
14
+ const normalized = task.toLowerCase();
15
+ if (!isPlainTaskId(normalized)) {
16
+ throw invalidPlainTaskIdError(task);
17
+ }
18
+ return normalized;
19
+ }
@@ -131,6 +131,8 @@ export interface CreateTaskInput {
131
131
  agent: string;
132
132
  /** Optional repository override in the same format Groundcrew resolves from task descriptions. */
133
133
  repository?: string;
134
+ /** Optional source-native team selector, such as a Linear team key or id. */
135
+ team?: string;
134
136
  /** Optional source-native task id. When omitted, sources may generate one. */
135
137
  id?: string;
136
138
  /** Optional source-native priority token, such as a todo.txt `A`/`B`/`C` priority. */
@@ -1 +1 @@
1
- {"version":3,"file":"taskSource.d.ts","sourceRoot":"","sources":["../../src/lib/taskSource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"taskSource.d.ts","sourceRoot":"","sources":["../../src/lib/taskSource.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ export declare class WorktreeAlreadyExistsError extends Error {
18
18
  export declare function isWorktreeAlreadyExistsError(error: unknown): error is WorktreeAlreadyExistsError;
19
19
  export interface WorktreeEntry {
20
20
  repository: string;
21
- /** Linear task id, lowercased — e.g. "team-220". */
21
+ /** Source task id, lowercased — e.g. "team-220" or "gc-20260608-001". */
22
22
  task: string;
23
23
  /** Slash-free `<prefix>-<task>`. */
24
24
  branchName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAAE,KAAK,cAAc,EAAsC,MAAM,aAAa,CAAC;AAGtF,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAkBD,iBAAS,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvE;AAgQD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAiHxB,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAEzE;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAuBD,iBAAe,QAAQ,CACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,cAAc,CAAC,CAiDzB;AAED,iBAAe,gBAAgB,CAAC,KAAK,EAAE;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7B;AAED,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
1
+ {"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAAE,KAAK,cAAc,EAAsC,MAAM,aAAa,CAAC;AAItF,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAeD,iBAAS,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvE;AA4QD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAiHxB,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAEzE;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAuBD,iBAAe,QAAQ,CACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,cAAc,CAAC,CAiDzB;AAED,iBAAe,gBAAgB,CAAC,KAAK,EAAE;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7B;AAED,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
@@ -14,6 +14,7 @@ import path from "node:path";
14
14
  import { runCommandAsync } from "./commandRunner.js";
15
15
  import { repositoryBaseDir, worktreeBaseDir } from "./config.js";
16
16
  import { resolveDefaultBranch } from "./defaultBranch.js";
17
+ import { assertPlainTaskId, isPlainTaskId } from "./taskId.js";
17
18
  import { debug, errorMessage, isVerbose } from "./util.js";
18
19
  import { workspaces } from "./workspaces.js";
19
20
  const WORKTREE_LIST_PREFIX = "worktree ";
@@ -28,8 +29,6 @@ export class WorktreeAlreadyExistsError extends Error {
28
29
  export function isWorktreeAlreadyExistsError(error) {
29
30
  return error instanceof WorktreeAlreadyExistsError;
30
31
  }
31
- const TASK_RE = /^[a-z][\da-z]*-\d+$/;
32
- const TASK_DIR_RE = /^(?<repoBasename>.+)-(?<task>[a-z][\da-z]*-\d+)$/;
33
32
  function branchPrefix(config) {
34
33
  const fromConfig = config.git.branchPrefix;
35
34
  if (fromConfig !== undefined) {
@@ -58,9 +57,7 @@ function basePaths(config, repository, task) {
58
57
  // Tasks must match the same shape the worktree discovery regexes use,
59
58
  // so create()/list()/findByTask() agree on what's a valid worktree.
60
59
  // This also rejects traversal tokens before they reach path.resolve().
61
- if (!TASK_RE.test(task)) {
62
- throw new Error(`Invalid task "${task}": must be a plain task id`);
63
- }
60
+ assertPlainTaskId(task);
64
61
  const repoDir = repoDirFor(config, repository);
65
62
  const hostWorktreeName = `${repository}-${task}`;
66
63
  const hostWorktreeDir = path.resolve(worktreeBaseDir(config), hostWorktreeName);
@@ -75,6 +72,22 @@ function basePaths(config, repository, task) {
75
72
  function signalProperty(signal) {
76
73
  return signal === undefined ? {} : { signal };
77
74
  }
75
+ function parseWorktreeDirectoryName(directoryName, repositoryEntriesByLongestName) {
76
+ // Match the longest repository basename first so overlapping names like
77
+ // "repo-a" and "repo-a-admin" parse to the intended repository.
78
+ for (const [repositoryBaseName, repository] of repositoryEntriesByLongestName) {
79
+ const worktreePrefix = `${repositoryBaseName}-`;
80
+ if (!directoryName.startsWith(worktreePrefix)) {
81
+ continue;
82
+ }
83
+ const task = directoryName.slice(worktreePrefix.length);
84
+ if (!isPlainTaskId(task)) {
85
+ continue;
86
+ }
87
+ return { repository, task };
88
+ }
89
+ return undefined;
90
+ }
78
91
  /**
79
92
  * Runs a long-running git command (fetch, worktree add/remove/prune) with no
80
93
  * timeout. Under --verbose the git porcelain streams live to the terminal;
@@ -147,6 +160,7 @@ function listWorktrees(config) {
147
160
  repoByBasename.set(basename, repository);
148
161
  }
149
162
  for (const [parentDir, repoByBasename] of reposByParent) {
163
+ const repositoryEntriesByLongestName = [...repoByBasename.entries()].toSorted(([nameA], [nameB]) => nameB.length - nameA.length);
150
164
  let children;
151
165
  try {
152
166
  children = readdirSync(parentDir, { withFileTypes: true });
@@ -158,23 +172,14 @@ function listWorktrees(config) {
158
172
  if (!entry.isDirectory()) {
159
173
  continue;
160
174
  }
161
- const match = TASK_DIR_RE.exec(entry.name);
162
- if (!match) {
163
- continue;
164
- }
165
- const [, repoBasename, task] = match;
166
- /* v8 ignore next 3 @preserve -- TASK_DIR_RE always captures both groups when it matches */
167
- if (repoBasename === undefined || task === undefined) {
168
- continue;
169
- }
170
- const repository = repoByBasename.get(repoBasename);
171
- if (repository === undefined) {
175
+ const parsed = parseWorktreeDirectoryName(entry.name, repositoryEntriesByLongestName);
176
+ if (parsed === undefined) {
172
177
  continue;
173
178
  }
174
179
  entries.push({
175
- repository,
176
- task,
177
- branchName: branchNameForTask(config, task),
180
+ repository: parsed.repository,
181
+ task: parsed.task,
182
+ branchName: branchNameForTask(config, parsed.task),
178
183
  dir: path.resolve(parentDir, entry.name),
179
184
  kind: "host",
180
185
  });
package/docs/commands.md CHANGED
@@ -30,6 +30,17 @@ crew task create "Fix cancellation retry race" \
30
30
  --edit
31
31
  ```
32
32
 
33
+ Linear creation creates a Todo issue assigned to the current Linear API viewer, with exactly one `agent-*` label and a `Repository: <repo>` line in the description. Configure `sources: [{ kind: "linear", team: "ENG" }]` or pass `--team ENG`; the CLI option wins when both are present.
34
+
35
+ ```bash
36
+ crew task create "Fix cancellation retry race" \
37
+ --source linear \
38
+ --agent codex \
39
+ --team ENG \
40
+ --repo ClipboardHealth/api \
41
+ --description "Investigate retry handling."
42
+ ```
43
+
33
44
  ## Status
34
45
 
35
46
  `crew status <TASK>` prints a read-only snapshot for one task: cached title and URL when present, recorded run state, live workspace presence, matching worktrees, git dirtiness, PR links for matching branches, recent log lines when present, and the task status from the configured task source.
@@ -180,7 +180,7 @@ and hook contract.
180
180
 
181
181
  | Key | Default | What it does |
182
182
  | ---------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
183
- | `sources` | `[]` | Additional pluggable task 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. Disable the implicit Linear source with `{ kind: "linear", enabled: false }` (no API key required) — useful for shell-only setups. |
183
+ | `sources` | `[]` | Additional pluggable task sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. Declare `{ kind: "linear", team: "ENG", statuses: { ... } }` to configure Linear task creation's default team and/or override Linear status names used for `in-progress` / `in-review` disambiguation. Disable the implicit Linear source with `{ kind: "linear", enabled: false }` (no API key required) — useful for shell-only setups. |
184
184
  | `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
185
185
  | `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
186
186
  | `git.branchPrefix` | OS username | Prefix groundcrew puts before the task id when naming a worktree branch (`<branchPrefix>-<task>`). 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. |
@@ -94,6 +94,33 @@ crew task create "Fix cancellation retry race" \
94
94
  (A) Fix cancellation retry race +marketplace @backend id:GC-20260608-001 repo:ClipboardHealth/api agent:codex status:todo
95
95
  ```
96
96
 
97
+ ## Linear
98
+
99
+ The built-in Linear source supports listing, getting, writeback, and task creation through `crew task create`.
100
+
101
+ Linear task creation needs a team because Linear issues are team-scoped. Configure it once on the source, or pass `--team <key-or-id>` for one command:
102
+
103
+ ```ts
104
+ export default {
105
+ sources: [
106
+ {
107
+ kind: "linear",
108
+ team: "ENG",
109
+ },
110
+ ],
111
+ };
112
+ ```
113
+
114
+ ```bash
115
+ crew task create "Fix cancellation retry race" \
116
+ --source linear \
117
+ --agent codex \
118
+ --repo ClipboardHealth/api \
119
+ --description "Investigate retry handling."
120
+ ```
121
+
122
+ Created Linear issues are assigned to the API key's viewer, moved into a Todo workflow state, labeled with exactly one `agent-*` label, and given a description that includes `Repository: <repo>` near the top. Repeated `--dep <ISSUE>` values create Linear blocked-by relations when the dependency is a Linear issue id.
123
+
97
124
  ## The `description` is the agent's prompt
98
125
 
99
126
  Groundcrew wraps each issue's `description` in its generic unattended-execution prompt and hands it to the agent as the task. It does not pick a different prompt per source or task type. Specialized behavior belongs in the `description` your adapter emits, not in groundcrew.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.23.0",
3
+ "version": "4.24.1",
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",