@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.
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +29 -7
- package/dist/commands/task.d.ts.map +1 -1
- package/dist/commands/task.js +4 -0
- package/dist/lib/adapters/linear/create.d.ts +15 -0
- package/dist/lib/adapters/linear/create.d.ts.map +1 -0
- package/dist/lib/adapters/linear/create.js +273 -0
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.js +10 -0
- package/dist/lib/adapters/linear/schema.d.ts +1 -0
- package/dist/lib/adapters/linear/schema.d.ts.map +1 -1
- package/dist/lib/adapters/linear/schema.js +2 -0
- package/dist/lib/repositoryValidation.d.ts +1 -0
- package/dist/lib/repositoryValidation.d.ts.map +1 -1
- package/dist/lib/repositoryValidation.js +4 -1
- package/dist/lib/runState.d.ts.map +1 -1
- package/dist/lib/runState.js +2 -6
- package/dist/lib/taskId.d.ts +4 -0
- package/dist/lib/taskId.d.ts.map +1 -0
- package/dist/lib/taskId.js +19 -0
- package/dist/lib/taskSource.d.ts +2 -0
- package/dist/lib/taskSource.d.ts.map +1 -1
- package/dist/lib/worktrees.d.ts +1 -1
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +24 -19
- package/docs/commands.md +11 -0
- package/docs/configuration.md +1 -1
- package/docs/task-sources.md +27 -0
- package/package.json +1 -1
|
@@ -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;
|
|
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
|
-
|
|
113
|
-
|
|
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 =
|
|
116
|
-
.filter((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 (
|
|
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":"
|
|
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"}
|
package/dist/commands/task.js
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
|
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;
|
|
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"}
|
package/dist/lib/runState.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
|
|
3
|
+
import { normalizePlainTaskId } from "./taskId.js";
|
|
4
4
|
const RUN_STATE_DIRECTORY_NAME = "runs";
|
|
5
5
|
function taskKey(task) {
|
|
6
|
-
|
|
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 @@
|
|
|
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
|
+
}
|
package/dist/lib/taskSource.d.ts
CHANGED
|
@@ -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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"}
|
package/dist/lib/worktrees.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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;
|
|
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"}
|
package/dist/lib/worktrees.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
162
|
-
if (
|
|
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.
|
package/docs/configuration.md
CHANGED
|
@@ -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: { ... } }`
|
|
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. |
|
package/docs/task-sources.md
CHANGED
|
@@ -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