@clipboard-health/groundcrew 3.1.5 → 3.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/crew.config.example.ts +20 -0
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +12 -0
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +9 -0
- package/dist/commands/ticketDoctor.d.ts +12 -0
- package/dist/commands/ticketDoctor.d.ts.map +1 -1
- package/dist/commands/ticketDoctor.js +35 -8
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/lib/adapterDefinition.d.ts +27 -0
- package/dist/lib/adapterDefinition.d.ts.map +1 -0
- package/dist/lib/adapterDefinition.js +7 -0
- package/dist/lib/adapters/linear/factory.d.ts +27 -0
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -0
- package/dist/lib/adapters/linear/factory.js +161 -0
- package/dist/lib/adapters/linear/index.d.ts +5 -0
- package/dist/lib/adapters/linear/index.d.ts.map +1 -0
- package/dist/lib/adapters/linear/index.js +8 -0
- package/dist/lib/adapters/linear/schema.d.ts +14 -0
- package/dist/lib/adapters/linear/schema.d.ts.map +1 -0
- package/dist/lib/adapters/linear/schema.js +15 -0
- package/dist/lib/adapters/registry.d.ts +38 -0
- package/dist/lib/adapters/registry.d.ts.map +1 -0
- package/dist/lib/adapters/registry.js +82 -0
- package/dist/lib/adapters/shell/factory.d.ts +21 -0
- package/dist/lib/adapters/shell/factory.d.ts.map +1 -0
- package/dist/lib/adapters/shell/factory.js +130 -0
- package/dist/lib/adapters/shell/index.d.ts +5 -0
- package/dist/lib/adapters/shell/index.d.ts.map +1 -0
- package/dist/lib/adapters/shell/index.js +8 -0
- package/dist/lib/adapters/shell/invoke.d.ts +45 -0
- package/dist/lib/adapters/shell/invoke.d.ts.map +1 -0
- package/dist/lib/adapters/shell/invoke.js +153 -0
- package/dist/lib/adapters/shell/schema.d.ts +90 -0
- package/dist/lib/adapters/shell/schema.d.ts.map +1 -0
- package/dist/lib/adapters/shell/schema.js +54 -0
- package/dist/lib/board.d.ts +20 -0
- package/dist/lib/board.d.ts.map +1 -0
- package/dist/lib/board.js +77 -0
- package/dist/lib/boardSource.d.ts +23 -7
- package/dist/lib/boardSource.d.ts.map +1 -1
- package/dist/lib/boardSource.js +35 -8
- package/dist/lib/buildSources.d.ts +19 -0
- package/dist/lib/buildSources.d.ts.map +1 -0
- package/dist/lib/buildSources.js +34 -0
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +36 -0
- package/dist/lib/defaultBranch.d.ts +24 -0
- package/dist/lib/defaultBranch.d.ts.map +1 -0
- package/dist/lib/defaultBranch.js +39 -0
- package/dist/lib/launchCommand.d.ts +1 -1
- package/dist/lib/launchCommand.d.ts.map +1 -1
- package/dist/lib/launchCommand.js +1 -1
- package/dist/lib/ticketSource.d.ts +85 -0
- package/dist/lib/ticketSource.d.ts.map +1 -0
- package/dist/lib/ticketSource.js +26 -0
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +9 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -173,6 +173,53 @@ Three keys are required; everything else has a default.
|
|
|
173
173
|
|
|
174
174
|
Agent selection uses Linear labels: `agent-claude`, `agent-codex`, `agent-<name>`. `crew run` without `--ticket` only fetches tickets carrying an `agent-*` label — the GraphQL query filters server-side, so unlabeled tickets are never returned by Linear and do not appear on the board. Use `crew run --ticket <TICKET>` to provision an unlabeled ticket on demand (falls back to `models.default`). `agent-any` routes to the model with the most available session capacity. Todo tickets blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
|
|
175
175
|
|
|
176
|
+
### Pluggable ticket sources
|
|
177
|
+
|
|
178
|
+
`sources` declares extra ticket-system adapters. The current release verifies configured extra sources during `crew run` startup; the dispatch loop still reads Linear through `linear.projects` until the consumer refactor lands. This lets you validate shell/Jira/local-plan integrations without changing existing Linear behavior.
|
|
179
|
+
|
|
180
|
+
The built-in `shell` adapter runs command templates and reads JSON from stdout:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
export default {
|
|
184
|
+
// ...
|
|
185
|
+
sources: [
|
|
186
|
+
{
|
|
187
|
+
kind: "shell",
|
|
188
|
+
name: "jira",
|
|
189
|
+
commands: {
|
|
190
|
+
verify: "jira me",
|
|
191
|
+
fetch: "~/.config/groundcrew/jira-fetch.sh",
|
|
192
|
+
resolveOne: "~/.config/groundcrew/jira-resolve.sh ${id}",
|
|
193
|
+
markInProgress: "jira issue move ${id} 'In Progress'",
|
|
194
|
+
},
|
|
195
|
+
timeouts: { fetch: 60_000 },
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`commands.fetch` must print a JSON array of issues. `commands.resolveOne`, when set, must print one issue, print nothing for "not found", or exit `3` for "not found". `commands.markInProgress`, when set, receives the issue's `sourceRef` as JSON on stdin. `${id}`, `${canonicalId}`, and `${name}` placeholders are shell-quoted before substitution.
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
[
|
|
205
|
+
{
|
|
206
|
+
"id": "JIRA-123",
|
|
207
|
+
"title": "Add retry logic",
|
|
208
|
+
"description": "Ticket body",
|
|
209
|
+
"status": "todo",
|
|
210
|
+
"repository": "your-org/your-repo",
|
|
211
|
+
"model": "claude",
|
|
212
|
+
"assignee": "Alice",
|
|
213
|
+
"updatedAt": "2026-05-22T15:00:00Z",
|
|
214
|
+
"blockers": [{ "id": "JIRA-122", "title": "Schema migration", "status": "done" }],
|
|
215
|
+
"hasMoreBlockers": false,
|
|
216
|
+
"sourceRef": { "nativeId": "10042" }
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Allowed `status` values are `todo`, `in-progress`, `in-review`, `done`, and `other`. Use `null` for `repository` or `model` when a ticket should not be groundcrew-eligible. `hasMoreBlockers` is optional and defaults to `false`; `sourceRef` is opaque data that groundcrew passes back to your writeback command.
|
|
222
|
+
|
|
176
223
|
### Prompt customization
|
|
177
224
|
|
|
178
225
|
Groundcrew ships one model-agnostic unattended prompt by default. It tells the agent to make reasonable assumptions, follow repository instructions, run documented verification, review its diff, open a PR when GitHub/`gh` is available, and include a workspace continuation hint when known.
|
|
@@ -203,6 +250,7 @@ This keeps package defaults portable while letting your private config reference
|
|
|
203
250
|
| `linear.projects[].statuses.inProgress` | `"In Progress"` | Status set after a workspace is provisioned; counts toward the shared `maximumInProgress`. |
|
|
204
251
|
| `linear.projects[].statuses.done` | `"Done"` | Status that triggers worktree cleanup for this project. |
|
|
205
252
|
| `linear.projects[].statuses.terminal` | `["Done"]` | Additional status names treated as terminal for cleanup and blocker checks. The project's `done` status is always included. |
|
|
253
|
+
| `sources` | `[]` | Additional pluggable ticket sources. Extra sources are verified at startup; Linear remains the dispatch read path until the consumer refactor. Built-in kinds: `shell`, `linear`. |
|
|
206
254
|
| `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
|
|
207
255
|
| `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
|
|
208
256
|
| `workspace.projectDir` | **required** | Parent dir for cloned repos and sibling ticket worktrees. |
|
package/crew.config.example.ts
CHANGED
|
@@ -40,6 +40,26 @@ export default {
|
|
|
40
40
|
// Everything below is optional — defaults shown for reference. Uncomment
|
|
41
41
|
// and edit to override.
|
|
42
42
|
//
|
|
43
|
+
// // Additional pluggable ticket sources beyond the implicit built-in
|
|
44
|
+
// // Linear adapter (configured via `linear.projects` above). The most
|
|
45
|
+
// // common use is `kind: "shell"`, which wires any external system via
|
|
46
|
+
// // command templates that emit/consume JSON. See the shell adapter's
|
|
47
|
+
// // ShellIssue schema for the JSON contract `fetch` / `resolveOne` must
|
|
48
|
+
// // emit.
|
|
49
|
+
// sources: [
|
|
50
|
+
// {
|
|
51
|
+
// kind: "shell",
|
|
52
|
+
// name: "jira",
|
|
53
|
+
// commands: {
|
|
54
|
+
// verify: "jira me",
|
|
55
|
+
// fetch: "~/.config/groundcrew/jira-fetch.sh",
|
|
56
|
+
// resolveOne: "~/.config/groundcrew/jira-resolve.sh ${id}",
|
|
57
|
+
// markInProgress: "jira issue move ${id} 'In Progress'",
|
|
58
|
+
// },
|
|
59
|
+
// timeouts: { fetch: 60_000 },
|
|
60
|
+
// },
|
|
61
|
+
// ],
|
|
62
|
+
//
|
|
43
63
|
// git: { remote: "origin", defaultBranch: "main" },
|
|
44
64
|
//
|
|
45
65
|
// orchestrator: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,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,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,UAAU,EAAE;QAClB,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,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,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,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,UAAU,EAAE;QAClB,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,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAuMjE"}
|
|
@@ -87,6 +87,18 @@ export function createDispatcher(deps) {
|
|
|
87
87
|
async function runOnce(arguments_) {
|
|
88
88
|
const { state, worktreeEntries, usage, dryRun, signal, idleSuffix = "" } = arguments_;
|
|
89
89
|
issueStatusUpdater.resetMissingInProgressCache();
|
|
90
|
+
// Surface parent tickets that fetchBoard silently dropped. Without this
|
|
91
|
+
// an operator sees "No Todo tickets to pick up" with no signal that an
|
|
92
|
+
// expected Todo+labelled ticket was skipped because it has sub-issues.
|
|
93
|
+
for (const skip of state.parentSkips) {
|
|
94
|
+
log(`Skipping ${skip.id}: parent ticket with ${skip.childCount} sub-issue(s) — groundcrew works sub-issues, not parents`);
|
|
95
|
+
logEvent("dispatch", {
|
|
96
|
+
outcome: "skipped",
|
|
97
|
+
reason: "parent_with_children",
|
|
98
|
+
ticket: skip.id,
|
|
99
|
+
children: skip.childCount,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
90
102
|
const activeCount = state.issues.filter((issue) => issue.status === projectFor(issue, config).statuses.inProgress).length;
|
|
91
103
|
const slots = config.orchestrator.maximumInProgress - activeCount;
|
|
92
104
|
// Narrow Todo to tickets that opted in via an `agent-*` label.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA6DH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAiBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4C7E"}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* orchestrator's user-facing output.
|
|
6
6
|
*/
|
|
7
7
|
import { createBoardSource, RepositoryResolutionError, } from "../lib/boardSource.js";
|
|
8
|
+
import { createBoard } from "../lib/board.js";
|
|
9
|
+
import { buildSources } from "../lib/buildSources.js";
|
|
8
10
|
import { loadConfig } from "../lib/config.js";
|
|
9
11
|
import { getUsageByModel } from "../lib/usage.js";
|
|
10
12
|
import { errorMessage, getLinearClient, log, sleep } from "../lib/util.js";
|
|
@@ -65,6 +67,13 @@ export async function orchestrate(options) {
|
|
|
65
67
|
const client = getLinearClient();
|
|
66
68
|
const boardSource = createBoardSource({ config, client });
|
|
67
69
|
await boardSource.verify();
|
|
70
|
+
// Verify any pluggable sources declared in config.sources (shell adapters,
|
|
71
|
+
// future built-in adapters) at startup. The Linear path still runs through
|
|
72
|
+
// boardSource.fetch in the main loop; shell-source dispatch is a follow-up.
|
|
73
|
+
// An empty config.sources resolves to an empty Board and verify() is a no-op.
|
|
74
|
+
const extraSources = await buildSources(config.sources, { globalConfig: config });
|
|
75
|
+
const board = createBoard(extraSources);
|
|
76
|
+
await board.verify();
|
|
68
77
|
const cleaner = createCleaner({ config });
|
|
69
78
|
const dispatcher = createDispatcher({ config, client });
|
|
70
79
|
// Folded into the dispatcher's idle log lines in watch mode so each idle
|
|
@@ -145,14 +145,26 @@ export interface TicketDoctorDependencies {
|
|
|
145
145
|
probeWorkingTree: (input: {
|
|
146
146
|
worktreeDir: string;
|
|
147
147
|
}) => Promise<WorktreeDirtiness>;
|
|
148
|
+
/**
|
|
149
|
+
* Resolves the default branch for `repoDir` (e.g. "master" vs "main") from
|
|
150
|
+
* the local clone's `refs/remotes/<remote>/HEAD`, falling back to
|
|
151
|
+
* `config.git.defaultBranch`. Injected so probeLocalBranchSection can pass a
|
|
152
|
+
* per-repo branch into `probeLocalBranch` without each probe needing to
|
|
153
|
+
* shell out to git itself.
|
|
154
|
+
*/
|
|
155
|
+
resolveDefaultBranch: (input: {
|
|
156
|
+
repoDir: string;
|
|
157
|
+
}) => Promise<string>;
|
|
148
158
|
probeLocalBranch: (input: {
|
|
149
159
|
repoDir: string;
|
|
150
160
|
branch: string;
|
|
161
|
+
remote: string;
|
|
151
162
|
defaultBranch: string;
|
|
152
163
|
}) => Promise<LocalBranchProbe>;
|
|
153
164
|
probeRemoteBranch: (input: {
|
|
154
165
|
repoDir: string;
|
|
155
166
|
branch: string;
|
|
167
|
+
remote: string;
|
|
156
168
|
doFetch: boolean;
|
|
157
169
|
}) => Promise<RemoteBranchProbe>;
|
|
158
170
|
probePullRequest: (input: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticketDoctor.d.ts","sourceRoot":"","sources":["../../src/commands/ticketDoctor.ts"],"names":[],"mappings":"AAoBA,OAAO,EAML,KAAK,OAAO,EAEZ,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ticketDoctor.d.ts","sourceRoot":"","sources":["../../src/commands/ticketDoctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,6 +18,7 @@ import { existsSync } from "node:fs";
|
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, resolveModelFor, resolveRepositoryFor, } from "../lib/boardSource.js";
|
|
20
20
|
import { runCommandAsync } from "../lib/commandRunner.js";
|
|
21
|
+
import { resolveDefaultBranch } from "../lib/defaultBranch.js";
|
|
21
22
|
import { AGENT_ANY_MODEL, findProjectBySlugId, loadConfig, } from "../lib/config.js";
|
|
22
23
|
import { which } from "../lib/host.js";
|
|
23
24
|
import { readRunState } from "../lib/runState.js";
|
|
@@ -258,6 +259,17 @@ function buildRepoChecks(raw, config, ticket) {
|
|
|
258
259
|
const resolvedRepository = repositoryResolution.kind === "ok" ? repositoryResolution.repository : "";
|
|
259
260
|
return { resolvedRepository, checks };
|
|
260
261
|
}
|
|
262
|
+
function buildChildrenCheck(raw) {
|
|
263
|
+
if (raw.hasChildren) {
|
|
264
|
+
return {
|
|
265
|
+
name: "Has no sub-issues",
|
|
266
|
+
status: "fail",
|
|
267
|
+
detail: "parent ticket with sub-issues — groundcrew works sub-issues, not parents; label a sub-issue or detach the children",
|
|
268
|
+
failureSummary: "parent ticket with sub-issues — groundcrew works sub-issues, not parents",
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return { name: "Has no sub-issues", status: "ok" };
|
|
272
|
+
}
|
|
261
273
|
async function runEligibilityChecks(arguments_) {
|
|
262
274
|
const { ticket, raw, config, resolvedRepository, resolvedModel, dependencies, eligibility } = arguments_;
|
|
263
275
|
const blockers = await dependencies.fetchBlockersFor({ ticket, uuid: raw.uuid });
|
|
@@ -531,10 +543,12 @@ async function probeLocalBranchSection(deps, entry) {
|
|
|
531
543
|
};
|
|
532
544
|
}
|
|
533
545
|
const repoDir = repoDirFromEntry(entry, deps);
|
|
546
|
+
const defaultBranch = await deps.resolveDefaultBranch({ repoDir });
|
|
534
547
|
const probe = await deps.probeLocalBranch({
|
|
535
548
|
repoDir,
|
|
536
549
|
branch: entry.branchName,
|
|
537
|
-
|
|
550
|
+
remote: deps.config.git.remote,
|
|
551
|
+
defaultBranch,
|
|
538
552
|
});
|
|
539
553
|
if (probe.kind === "present") {
|
|
540
554
|
const defaultBranchName = probe.defaultBranch ?? deps.config.git.defaultBranch;
|
|
@@ -543,7 +557,7 @@ async function probeLocalBranchSection(deps, entry) {
|
|
|
543
557
|
{
|
|
544
558
|
name: "Local branch exists",
|
|
545
559
|
status: "ok",
|
|
546
|
-
detail: `${entry.branchName}, ${probe.ahead} ahead / ${probe.behind} behind
|
|
560
|
+
detail: `${entry.branchName}, ${probe.ahead} ahead / ${probe.behind} behind ${deps.config.git.remote}/${defaultBranchName}`,
|
|
547
561
|
},
|
|
548
562
|
],
|
|
549
563
|
skipReason: "",
|
|
@@ -572,24 +586,26 @@ async function probeRemoteBranchSection(deps, entry) {
|
|
|
572
586
|
return { checks: [], skipReason: "repo dir unresolved", probe: { kind: "absent" } };
|
|
573
587
|
}
|
|
574
588
|
const repoDir = repoDirFromEntry(entry, deps);
|
|
589
|
+
const checkName = `Branch present on ${deps.config.git.remote}`;
|
|
575
590
|
const probe = await deps.probeRemoteBranch({
|
|
576
591
|
repoDir,
|
|
577
592
|
branch: entry.branchName,
|
|
593
|
+
remote: deps.config.git.remote,
|
|
578
594
|
doFetch: deps.doFetch,
|
|
579
595
|
});
|
|
580
596
|
if (probe.kind === "present") {
|
|
581
|
-
return { checks: [{ name:
|
|
597
|
+
return { checks: [{ name: checkName, status: "ok" }], skipReason: "", probe };
|
|
582
598
|
}
|
|
583
599
|
if (probe.kind === "absent") {
|
|
584
600
|
return {
|
|
585
|
-
checks: [{ name:
|
|
601
|
+
checks: [{ name: checkName, status: "fail", detail: "not pushed" }],
|
|
586
602
|
skipReason: "",
|
|
587
603
|
probe,
|
|
588
604
|
};
|
|
589
605
|
}
|
|
590
606
|
// probe.kind === "unknown"
|
|
591
607
|
return {
|
|
592
|
-
checks: [{ name:
|
|
608
|
+
checks: [{ name: checkName, status: "skipped", detail: probe.reason }],
|
|
593
609
|
skipReason: "",
|
|
594
610
|
probe,
|
|
595
611
|
};
|
|
@@ -642,6 +658,12 @@ async function runPreDispatch(input) {
|
|
|
642
658
|
const eligibility = [];
|
|
643
659
|
const { resolvedModel, checks: modelChecks } = buildModelChecks(raw, config);
|
|
644
660
|
resolutionExtra.push(...modelChecks);
|
|
661
|
+
// Children check comes before repo checks: a parent ticket is a
|
|
662
|
+
// structural property of the issue itself (filtered out by `fetchBoard`,
|
|
663
|
+
// so the dispatcher will never act on it). Reporting that first beats
|
|
664
|
+
// surfacing "your repo isn't cloned" for a ticket groundcrew won't pick
|
|
665
|
+
// up either way.
|
|
666
|
+
resolutionExtra.push(buildChildrenCheck(raw));
|
|
645
667
|
const { resolvedRepository, checks: repoChecks } = buildRepoChecks(raw, config, ticket);
|
|
646
668
|
resolutionExtra.push(...repoChecks);
|
|
647
669
|
if (input.statusCheckFailed) {
|
|
@@ -969,6 +991,11 @@ export async function runTicketDoctor(parsed) {
|
|
|
969
991
|
probeWorkspaces: async () => await workspaces.probe(config),
|
|
970
992
|
workspaceAccessHint: async (name) => await workspaces.accessHint(config, name),
|
|
971
993
|
probeWorkingTree: async ({ worktreeDir }) => await worktrees.probeWorkingTree({ worktreeDir }),
|
|
994
|
+
resolveDefaultBranch: async ({ repoDir }) => await resolveDefaultBranch({
|
|
995
|
+
repoDir,
|
|
996
|
+
remote: config.git.remote,
|
|
997
|
+
fallback: config.git.defaultBranch,
|
|
998
|
+
}),
|
|
972
999
|
probeLocalBranch: probeLocalBranchImpl,
|
|
973
1000
|
probeRemoteBranch: probeRemoteBranchImpl,
|
|
974
1001
|
probePullRequest: probePullRequestImpl,
|
|
@@ -1028,7 +1055,7 @@ async function probeLocalBranchImpl(input) {
|
|
|
1028
1055
|
"rev-list",
|
|
1029
1056
|
"--left-right",
|
|
1030
1057
|
"--count",
|
|
1031
|
-
`${input.branch}
|
|
1058
|
+
`${input.branch}...${input.remote}/${input.defaultBranch}`,
|
|
1032
1059
|
]);
|
|
1033
1060
|
const [aheadString, behindString] = output.trim().split(/\s+/);
|
|
1034
1061
|
const ahead = Number.parseInt(aheadString ?? "0", 10);
|
|
@@ -1050,7 +1077,7 @@ async function probeRemoteBranchImpl(input) {
|
|
|
1050
1077
|
input.repoDir,
|
|
1051
1078
|
"fetch",
|
|
1052
1079
|
"--quiet",
|
|
1053
|
-
|
|
1080
|
+
input.remote,
|
|
1054
1081
|
input.branch,
|
|
1055
1082
|
]);
|
|
1056
1083
|
}
|
|
@@ -1064,7 +1091,7 @@ async function probeRemoteBranchImpl(input) {
|
|
|
1064
1091
|
input.repoDir,
|
|
1065
1092
|
"ls-remote",
|
|
1066
1093
|
"--exit-code",
|
|
1067
|
-
|
|
1094
|
+
input.remote,
|
|
1068
1095
|
`refs/heads/${input.branch}`,
|
|
1069
1096
|
]);
|
|
1070
1097
|
return { kind: "present" };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,11 +5,16 @@ export { interruptWorkspace, type InterruptWorkspaceOptions, } from "./commands/
|
|
|
5
5
|
export { orchestrate, type OrchestratorOptions } from "./commands/orchestrator.ts";
|
|
6
6
|
export { resumeWorkspace, type ResumeWorkspaceOptions } from "./commands/resumeWorkspace.ts";
|
|
7
7
|
export { setupWorkspace, type SetupWorkspaceOptions } from "./commands/setupWorkspace.ts";
|
|
8
|
-
export type { Config, ModelDefinition, ProjectConfig, ResolvedConfig, ResolvedProjectConfig, } from "./lib/config.ts";
|
|
8
|
+
export type { Config, ModelDefinition, ProjectConfig, ResolvedConfig, ResolvedProjectConfig, SourceConfig, } from "./lib/config.ts";
|
|
9
9
|
export { findProjectBySlugId, loadConfig, unionTerminalStatuses } from "./lib/config.ts";
|
|
10
10
|
export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, type RunLifecycleState, type RunState, } from "./lib/runState.ts";
|
|
11
11
|
export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isTerminalStatusForBlocker, isTerminalStatusForIssue, projectFor, resolveModelFor, resolveRepositoryFor, UnknownProjectError, type ModelResolution, type RawLinearIssue, type RepositoryResolution, } from "./lib/boardSource.ts";
|
|
12
12
|
export { getUsageByModel, type UsageByModel } from "./lib/usage.ts";
|
|
13
|
+
export { type Board, createBoard } from "./lib/board.ts";
|
|
14
|
+
export { buildSources, buildSourcesWith } from "./lib/buildSources.ts";
|
|
15
|
+
export type { AdapterContext, AdapterDefinition } from "./lib/adapterDefinition.ts";
|
|
16
|
+
export { adapterRegistry, type AdapterLoader, buildRegistry, buildSourceConfigSchema, listAdapterDirectories, } from "./lib/adapters/registry.ts";
|
|
17
|
+
export { AmbiguousTicketError, type Blocker as CanonicalBlocker, type BoardState as CanonicalBoardState, type CanonicalStatus, type GroundcrewIssue as CanonicalGroundcrewIssue, type Issue as CanonicalIssue, isGroundcrewIssue as isCanonicalGroundcrewIssue, type TicketSource, } from "./lib/ticketSource.ts";
|
|
13
18
|
export type { TicketCheck } from "./commands/ticketCheck.ts";
|
|
14
19
|
export { ticketDoctor, type TicketDoctorDependencies, type TicketDoctorResult, type TicketDoctorVerdict, } from "./commands/ticketDoctor.ts";
|
|
15
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EACV,MAAM,EACN,eAAe,EACf,aAAa,EACb,cAAc,EACd,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EACV,MAAM,EACN,eAAe,EACf,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,KAAK,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,EACL,eAAe,EACf,KAAK,aAAa,EAClB,aAAa,EACb,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,oBAAoB,EACpB,KAAK,OAAO,IAAI,gBAAgB,EAChC,KAAK,UAAU,IAAI,mBAAmB,EACtC,KAAK,eAAe,EACpB,KAAK,eAAe,IAAI,wBAAwB,EAChD,KAAK,KAAK,IAAI,cAAc,EAC5B,iBAAiB,IAAI,0BAA0B,EAC/C,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACL,YAAY,EACZ,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,4 +9,8 @@ export { findProjectBySlugId, loadConfig, unionTerminalStatuses } from "./lib/co
|
|
|
9
9
|
export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, } from "./lib/runState.js";
|
|
10
10
|
export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isTerminalStatusForBlocker, isTerminalStatusForIssue, projectFor, resolveModelFor, resolveRepositoryFor, UnknownProjectError, } from "./lib/boardSource.js";
|
|
11
11
|
export { getUsageByModel } from "./lib/usage.js";
|
|
12
|
+
export { createBoard } from "./lib/board.js";
|
|
13
|
+
export { buildSources, buildSourcesWith } from "./lib/buildSources.js";
|
|
14
|
+
export { adapterRegistry, buildRegistry, buildSourceConfigSchema, listAdapterDirectories, } from "./lib/adapters/registry.js";
|
|
15
|
+
export { AmbiguousTicketError, isGroundcrewIssue as isCanonicalGroundcrewIssue, } from "./lib/ticketSource.js";
|
|
12
16
|
export { ticketDoctor, } from "./commands/ticketDoctor.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `AdapterDefinition` shape that every built-in adapter
|
|
3
|
+
* (`src/lib/adapters/<kind>/index.ts`) default-exports. The runtime registry
|
|
4
|
+
* (`./adapters/registry.ts`) discovers adapters by enumerating that
|
|
5
|
+
* directory and reading each module's default export.
|
|
6
|
+
*/
|
|
7
|
+
import type { z } from "zod";
|
|
8
|
+
import type { ResolvedConfig } from "./config.ts";
|
|
9
|
+
import type { TicketSource } from "./ticketSource.ts";
|
|
10
|
+
/**
|
|
11
|
+
* Cross-cutting context every adapter receives at construction time. Holds
|
|
12
|
+
* the global resolved config so adapters can read shared concerns (the
|
|
13
|
+
* `workspace.knownRepositories` list, `models.*` definitions, etc.) without
|
|
14
|
+
* each one duplicating them in its per-source config block.
|
|
15
|
+
*/
|
|
16
|
+
export interface AdapterContext {
|
|
17
|
+
readonly globalConfig: ResolvedConfig;
|
|
18
|
+
}
|
|
19
|
+
export interface AdapterDefinition<TSchema extends z.ZodType = z.ZodType> {
|
|
20
|
+
/** Discriminator value used in `SourceConfig.kind`. Must equal the directory name. */
|
|
21
|
+
readonly kind: string;
|
|
22
|
+
/** Zod schema for this adapter's config block. The `kind` field must be `z.literal(kind)`. */
|
|
23
|
+
readonly configSchema: TSchema;
|
|
24
|
+
/** Builds a TicketSource from a validated config and the shared adapter context. */
|
|
25
|
+
readonly create: (config: z.infer<TSchema>, context: AdapterContext) => TicketSource;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=adapterDefinition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapterDefinition.d.ts","sourceRoot":"","sources":["../../src/lib/adapterDefinition.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,YAAY,EAAE,cAAc,CAAC;CACvC;AAED,MAAM,WAAW,iBAAiB,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO;IACtE,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,cAAc,KAAK,YAAY,CAAC;CACtF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `AdapterDefinition` shape that every built-in adapter
|
|
3
|
+
* (`src/lib/adapters/<kind>/index.ts`) default-exports. The runtime registry
|
|
4
|
+
* (`./adapters/registry.ts`) discovers adapters by enumerating that
|
|
5
|
+
* directory and reading each module's default export.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear `TicketSource` factory. Wraps the existing boardSource.ts machinery
|
|
3
|
+
* (createBoardSource, fetchResolvedIssue, createLinearIssueStatusUpdater) and
|
|
4
|
+
* converts the legacy Linear-specific `Issue`/`Blocker` shapes into the
|
|
5
|
+
* canonical `Issue`/`Blocker` shapes consumers (via `Board`) speak.
|
|
6
|
+
*
|
|
7
|
+
* Per-project canonical-status mapping lives here: each `Issue` is mapped
|
|
8
|
+
* against its own project's `statuses` block (the multi-project semantics
|
|
9
|
+
* shipped in PR #75). Off-config blockers fall back to the union of all
|
|
10
|
+
* configured projects' status sets — preserving today's
|
|
11
|
+
* `isTerminalStatusForBlocker` behavior.
|
|
12
|
+
*
|
|
13
|
+
* Description is not populated on `fetch()` Issues (boardSource's snapshot
|
|
14
|
+
* doesn't include it); `resolveOne()` Issues carry the full description
|
|
15
|
+
* because `fetchResolvedIssue` fetches it explicitly. Phase 6 can lift
|
|
16
|
+
* description onto the board snapshot when it refactors setupWorkspace.
|
|
17
|
+
*/
|
|
18
|
+
import type { AdapterContext } from "../../adapterDefinition.ts";
|
|
19
|
+
import { type Blocker as LinearBlocker, type Issue as LinearIssue } from "../../boardSource.ts";
|
|
20
|
+
import { type ResolvedProjectConfig } from "../../config.ts";
|
|
21
|
+
import type { CanonicalStatus, Issue as CanonicalIssue, TicketSource } from "../../ticketSource.ts";
|
|
22
|
+
import type { LinearAdapterConfig } from "./schema.ts";
|
|
23
|
+
export declare function canonicalStatusForProject(nativeStatus: string, project: ResolvedProjectConfig): CanonicalStatus;
|
|
24
|
+
export declare function canonicalBlockerStatus(blocker: LinearBlocker, globalConfig: AdapterContext["globalConfig"]): CanonicalStatus;
|
|
25
|
+
export declare function toCanonicalIssue(linearIssue: LinearIssue, globalConfig: AdapterContext["globalConfig"], sourceName: string): CanonicalIssue;
|
|
26
|
+
export declare function createLinearTicketSource(config: LinearAdapterConfig, context: AdapterContext): TicketSource;
|
|
27
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EACL,KAAK,OAAO,IAAI,aAAa,EAG7B,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAElF,OAAO,KAAK,EAEV,eAAe,EACf,KAAK,IAAI,cAAc,EACvB,YAAY,EACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAUvD,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,qBAAqB,GAC7B,eAAe,CAcjB;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,aAAa,EACtB,YAAY,EAAE,cAAc,CAAC,cAAc,CAAC,GAC3C,eAAe,CAoBjB;AAcD,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,cAAc,CAAC,cAAc,CAAC,EAC5C,UAAU,EAAE,MAAM,GACjB,cAAc,CA8BhB;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,YAAY,CAwEd"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear `TicketSource` factory. Wraps the existing boardSource.ts machinery
|
|
3
|
+
* (createBoardSource, fetchResolvedIssue, createLinearIssueStatusUpdater) and
|
|
4
|
+
* converts the legacy Linear-specific `Issue`/`Blocker` shapes into the
|
|
5
|
+
* canonical `Issue`/`Blocker` shapes consumers (via `Board`) speak.
|
|
6
|
+
*
|
|
7
|
+
* Per-project canonical-status mapping lives here: each `Issue` is mapped
|
|
8
|
+
* against its own project's `statuses` block (the multi-project semantics
|
|
9
|
+
* shipped in PR #75). Off-config blockers fall back to the union of all
|
|
10
|
+
* configured projects' status sets — preserving today's
|
|
11
|
+
* `isTerminalStatusForBlocker` behavior.
|
|
12
|
+
*
|
|
13
|
+
* Description is not populated on `fetch()` Issues (boardSource's snapshot
|
|
14
|
+
* doesn't include it); `resolveOne()` Issues carry the full description
|
|
15
|
+
* because `fetchResolvedIssue` fetches it explicitly. Phase 6 can lift
|
|
16
|
+
* description onto the board snapshot when it refactors setupWorkspace.
|
|
17
|
+
*/
|
|
18
|
+
import { createBoardSource, fetchResolvedIssue, isTerminalStatusForBlocker, } from "../../boardSource.js";
|
|
19
|
+
import { findProjectBySlugId } from "../../config.js";
|
|
20
|
+
import { createLinearIssueStatusUpdater } from "../../linearIssueStatus.js";
|
|
21
|
+
import { getLinearClient } from "../../util.js";
|
|
22
|
+
export function canonicalStatusForProject(nativeStatus, project) {
|
|
23
|
+
if (project.statuses.todo === nativeStatus) {
|
|
24
|
+
return "todo";
|
|
25
|
+
}
|
|
26
|
+
if (project.statuses.inProgress === nativeStatus) {
|
|
27
|
+
return "in-progress";
|
|
28
|
+
}
|
|
29
|
+
if (project.statuses.done === nativeStatus) {
|
|
30
|
+
return "done";
|
|
31
|
+
}
|
|
32
|
+
if (project.statuses.terminal.includes(nativeStatus)) {
|
|
33
|
+
return "done";
|
|
34
|
+
}
|
|
35
|
+
return "other";
|
|
36
|
+
}
|
|
37
|
+
export function canonicalBlockerStatus(blocker, globalConfig) {
|
|
38
|
+
if (blocker.status === undefined) {
|
|
39
|
+
return "other";
|
|
40
|
+
}
|
|
41
|
+
// Terminal first — handles off-config blockers via the union fallback that
|
|
42
|
+
// isTerminalStatusForBlocker already implements.
|
|
43
|
+
if (isTerminalStatusForBlocker(blocker, globalConfig)) {
|
|
44
|
+
return "done";
|
|
45
|
+
}
|
|
46
|
+
// Non-terminal: if the blocker's project is configured, use its statuses to
|
|
47
|
+
// distinguish todo vs in-progress. For off-config blockers we collapse to
|
|
48
|
+
// "other" — eligibility only cares whether the blocker is terminal, so the
|
|
49
|
+
// distinction is informational at most.
|
|
50
|
+
if (blocker.projectSlugId !== undefined) {
|
|
51
|
+
const project = findProjectBySlugId(globalConfig, blocker.projectSlugId);
|
|
52
|
+
if (project !== undefined) {
|
|
53
|
+
return canonicalStatusForProject(blocker.status, project);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return "other";
|
|
57
|
+
}
|
|
58
|
+
function toCanonicalBlocker(blocker, globalConfig, sourceName) {
|
|
59
|
+
return {
|
|
60
|
+
id: `${sourceName}:${blocker.id}`,
|
|
61
|
+
title: blocker.title,
|
|
62
|
+
status: canonicalBlockerStatus(blocker, globalConfig),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function toCanonicalIssue(linearIssue, globalConfig, sourceName) {
|
|
66
|
+
const project = findProjectBySlugId(globalConfig, linearIssue.projectSlugId);
|
|
67
|
+
/* v8 ignore next 5 @preserve -- fetchBoard's slugId filter and issueStatusBelongsToOwnProject guarantee project is configured by the time we get here */
|
|
68
|
+
if (project === undefined) {
|
|
69
|
+
throw new Error(`Linear adapter: issue ${linearIssue.id} carries unknown projectSlugId "${linearIssue.projectSlugId}"`);
|
|
70
|
+
}
|
|
71
|
+
const sourceRef = {
|
|
72
|
+
uuid: linearIssue.uuid,
|
|
73
|
+
statusId: linearIssue.statusId,
|
|
74
|
+
teamId: linearIssue.teamId,
|
|
75
|
+
projectSlugId: linearIssue.projectSlugId,
|
|
76
|
+
nativeStatus: linearIssue.status,
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
id: `${sourceName}:${linearIssue.id}`,
|
|
80
|
+
source: sourceName,
|
|
81
|
+
title: linearIssue.title,
|
|
82
|
+
// Board snapshot doesn't carry description; resolveOne() populates it.
|
|
83
|
+
description: "",
|
|
84
|
+
status: canonicalStatusForProject(linearIssue.status, project),
|
|
85
|
+
repository: linearIssue.repository,
|
|
86
|
+
model: linearIssue.model,
|
|
87
|
+
assignee: linearIssue.assignee,
|
|
88
|
+
updatedAt: linearIssue.updatedAt,
|
|
89
|
+
blockers: linearIssue.blockers.map((b) => toCanonicalBlocker(b, globalConfig, sourceName)),
|
|
90
|
+
hasMoreBlockers: linearIssue.hasMoreBlockers,
|
|
91
|
+
sourceRef,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function createLinearTicketSource(config, context) {
|
|
95
|
+
const sourceName = config.name ?? "linear";
|
|
96
|
+
const { globalConfig } = context;
|
|
97
|
+
const client = getLinearClient();
|
|
98
|
+
const boardSource = createBoardSource({ config: globalConfig, client });
|
|
99
|
+
const issueStatusUpdater = createLinearIssueStatusUpdater({ config: globalConfig, client });
|
|
100
|
+
return {
|
|
101
|
+
name: sourceName,
|
|
102
|
+
async verify() {
|
|
103
|
+
await boardSource.verify();
|
|
104
|
+
},
|
|
105
|
+
async fetch() {
|
|
106
|
+
const state = await boardSource.fetch();
|
|
107
|
+
return state.issues.map((linearIssue) => toCanonicalIssue(linearIssue, globalConfig, sourceName));
|
|
108
|
+
},
|
|
109
|
+
async resolveOne(naturalId) {
|
|
110
|
+
// fetchResolvedIssue throws on unknown project / missing repo; we let
|
|
111
|
+
// those propagate. Returning `undefined` is reserved for "ticket genuinely
|
|
112
|
+
// doesn't exist," which fetchResolvedIssue surfaces as an Error too —
|
|
113
|
+
// for now we let any error bubble up rather than swallow.
|
|
114
|
+
const resolved = await fetchResolvedIssue({
|
|
115
|
+
client,
|
|
116
|
+
config: globalConfig,
|
|
117
|
+
ticket: naturalId,
|
|
118
|
+
});
|
|
119
|
+
const project = findProjectBySlugId(globalConfig, resolved.projectSlugId);
|
|
120
|
+
/* v8 ignore next 5 @preserve -- fetchResolvedIssue already throws UnknownProjectError before reaching this guard */
|
|
121
|
+
if (project === undefined) {
|
|
122
|
+
throw new Error(`Linear adapter: resolved issue ${naturalId} carries unknown projectSlugId "${resolved.projectSlugId}"`);
|
|
123
|
+
}
|
|
124
|
+
// fetchResolvedIssue doesn't return the native status name (it's
|
|
125
|
+
// already been resolved through workflow state lookup). We surface
|
|
126
|
+
// "other" until the consumer needs the canonical status, which is fine
|
|
127
|
+
// because `crew setup` doesn't branch on it.
|
|
128
|
+
const sourceRef = {
|
|
129
|
+
uuid: resolved.uuid,
|
|
130
|
+
statusId: "",
|
|
131
|
+
teamId: resolved.teamId,
|
|
132
|
+
projectSlugId: resolved.projectSlugId,
|
|
133
|
+
nativeStatus: "",
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
id: `${sourceName}:${naturalId.toLowerCase()}`,
|
|
137
|
+
source: sourceName,
|
|
138
|
+
title: resolved.title,
|
|
139
|
+
description: resolved.description,
|
|
140
|
+
status: "other",
|
|
141
|
+
repository: resolved.repository,
|
|
142
|
+
model: resolved.model,
|
|
143
|
+
assignee: "Unassigned",
|
|
144
|
+
updatedAt: new Date().toISOString(),
|
|
145
|
+
blockers: [],
|
|
146
|
+
hasMoreBlockers: false,
|
|
147
|
+
sourceRef,
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
async markInProgress(issue) {
|
|
151
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- by the Linear adapter's contract, every Issue it produces carries a LinearSourceRef in sourceRef
|
|
152
|
+
const ref = issue.sourceRef;
|
|
153
|
+
await issueStatusUpdater.markInProgress({
|
|
154
|
+
id: issue.id,
|
|
155
|
+
uuid: ref.uuid,
|
|
156
|
+
teamId: ref.teamId,
|
|
157
|
+
projectSlugId: ref.projectSlugId,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AdapterDefinition } from "../../adapterDefinition.ts";
|
|
2
|
+
import { linearAdapterConfigSchema } from "./schema.ts";
|
|
3
|
+
declare const definition: AdapterDefinition<typeof linearAdapterConfigSchema>;
|
|
4
|
+
export default definition;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAExD,QAAA,MAAM,UAAU,EAAE,iBAAiB,CAAC,OAAO,yBAAyB,CAInE,CAAC;eAEa,UAAU"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createLinearTicketSource } from "./factory.js";
|
|
2
|
+
import { linearAdapterConfigSchema } from "./schema.js";
|
|
3
|
+
const definition = {
|
|
4
|
+
kind: "linear",
|
|
5
|
+
configSchema: linearAdapterConfigSchema,
|
|
6
|
+
create: createLinearTicketSource,
|
|
7
|
+
};
|
|
8
|
+
export default definition;
|