@clipboard-health/groundcrew 4.2.0 → 4.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +15 -25
  2. package/dist/commands/cleaner.d.ts +1 -1
  3. package/dist/commands/cleaner.d.ts.map +1 -1
  4. package/dist/commands/cleaner.js +4 -2
  5. package/dist/commands/dispatcher.d.ts +7 -6
  6. package/dist/commands/dispatcher.d.ts.map +1 -1
  7. package/dist/commands/dispatcher.js +56 -28
  8. package/dist/commands/doctor.d.ts.map +1 -1
  9. package/dist/commands/doctor.js +18 -22
  10. package/dist/commands/eligibility.d.ts +1 -1
  11. package/dist/commands/eligibility.d.ts.map +1 -1
  12. package/dist/commands/eligibility.js +7 -6
  13. package/dist/commands/orchestrator.d.ts.map +1 -1
  14. package/dist/commands/orchestrator.js +18 -14
  15. package/dist/commands/resumeWorkspace.d.ts.map +1 -1
  16. package/dist/commands/resumeWorkspace.js +3 -2
  17. package/dist/commands/setupWorkspace.d.ts +2 -4
  18. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  19. package/dist/commands/setupWorkspace.js +27 -27
  20. package/dist/commands/status.d.ts.map +1 -1
  21. package/dist/commands/status.js +6 -3
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -2
  25. package/dist/lib/adapters/linear/client.d.ts +22 -0
  26. package/dist/lib/adapters/linear/client.d.ts.map +1 -0
  27. package/dist/lib/adapters/linear/client.js +36 -0
  28. package/dist/lib/adapters/linear/factory.d.ts +24 -14
  29. package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
  30. package/dist/lib/adapters/linear/factory.js +113 -46
  31. package/dist/lib/{boardSource.d.ts → adapters/linear/fetch.d.ts} +22 -74
  32. package/dist/lib/adapters/linear/fetch.d.ts.map +1 -0
  33. package/dist/lib/{boardSource.js → adapters/linear/fetch.js} +28 -136
  34. package/dist/lib/adapters/linear/index.d.ts +1 -0
  35. package/dist/lib/adapters/linear/index.d.ts.map +1 -1
  36. package/dist/lib/adapters/linear/parsing.d.ts +44 -0
  37. package/dist/lib/adapters/linear/parsing.d.ts.map +1 -0
  38. package/dist/lib/adapters/linear/parsing.js +144 -0
  39. package/dist/lib/{linearIssueStatus.d.ts → adapters/linear/writeback.d.ts} +1 -2
  40. package/dist/lib/adapters/linear/writeback.d.ts.map +1 -0
  41. package/dist/lib/{linearIssueStatus.js → adapters/linear/writeback.js} +16 -17
  42. package/dist/lib/adapters/shell/factory.d.ts +1 -1
  43. package/dist/lib/adapters/shell/factory.d.ts.map +1 -1
  44. package/dist/lib/adapters/shell/factory.js +8 -4
  45. package/dist/lib/adapters/shell/invoke.d.ts +4 -7
  46. package/dist/lib/adapters/shell/invoke.d.ts.map +1 -1
  47. package/dist/lib/adapters/shell/invoke.js +46 -75
  48. package/dist/lib/adapters/shell/schema.d.ts +10 -0
  49. package/dist/lib/adapters/shell/schema.d.ts.map +1 -1
  50. package/dist/lib/adapters/shell/schema.js +9 -5
  51. package/dist/lib/board.d.ts.map +1 -1
  52. package/dist/lib/board.js +43 -4
  53. package/dist/lib/buildSources.d.ts +11 -0
  54. package/dist/lib/buildSources.d.ts.map +1 -1
  55. package/dist/lib/buildSources.js +41 -0
  56. package/dist/lib/repositoryValidation.d.ts +13 -0
  57. package/dist/lib/repositoryValidation.d.ts.map +1 -0
  58. package/dist/lib/repositoryValidation.js +20 -0
  59. package/dist/lib/testing/canonicalFixtures.d.ts +19 -0
  60. package/dist/lib/testing/canonicalFixtures.d.ts.map +1 -0
  61. package/dist/lib/testing/canonicalFixtures.js +62 -0
  62. package/dist/lib/ticketSource.d.ts +73 -3
  63. package/dist/lib/ticketSource.d.ts.map +1 -1
  64. package/dist/lib/ticketSource.js +31 -0
  65. package/dist/lib/util.d.ts +0 -20
  66. package/dist/lib/util.d.ts.map +1 -1
  67. package/dist/lib/util.js +0 -35
  68. package/package.json +1 -1
  69. package/dist/lib/boardSource.d.ts.map +0 -1
  70. package/dist/lib/linearIssueStatus.d.ts.map +0 -1
@@ -1,10 +1,11 @@
1
- import { fetchResolvedIssue } from "../lib/boardSource.js";
1
+ import { fetchResolvedIssue } from "../lib/adapters/linear/fetch.js";
2
+ import { getLinearClient } from "../lib/adapters/linear/client.js";
2
3
  import { loadConfig } from "../lib/config.js";
3
4
  import { openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
4
5
  import { buildLaunchCommand } from "../lib/launchCommand.js";
5
6
  import { readRunState, recordRunState } from "../lib/runState.js";
6
7
  import { removeStagedPrompt, stageBuildSecrets, stagePromptText, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
7
- import { errorMessage, getLinearClient, log } from "../lib/util.js";
8
+ import { errorMessage, log } from "../lib/util.js";
8
9
  import { workspaces } from "../lib/workspaces.js";
9
10
  import { worktrees } from "../lib/worktrees.js";
10
11
  function parseArguments(argv) {
@@ -1,5 +1,5 @@
1
1
  import { type ResolvedConfig } from "../lib/config.ts";
2
- interface TicketDetails {
2
+ export interface TicketDetails {
3
3
  title: string;
4
4
  description: string;
5
5
  }
@@ -7,8 +7,7 @@ export interface SetupWorkspaceOptions {
7
7
  ticket: string;
8
8
  repository: string;
9
9
  model: string;
10
- /** When provided, skip the Linear lookup for prompt-template fields. */
11
- details?: TicketDetails;
10
+ details: TicketDetails;
12
11
  }
13
12
  export interface SetupWorkspaceRunOptions {
14
13
  signal?: AbortSignal;
@@ -17,5 +16,4 @@ export declare function setupWorkspace(config: ResolvedConfig, options: SetupWor
17
16
  export declare function setupWorkspaceCli(ticket: string, options?: {
18
17
  dryRun?: boolean;
19
18
  }): Promise<void>;
20
- export {};
21
19
  //# sourceMappingURL=setupWorkspace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAWD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAqBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CAsGf;AAwHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAiBnE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAqBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CAgGf;AAwHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAwCf"}
@@ -1,22 +1,15 @@
1
1
  import { rmSync } from "node:fs";
2
- import { fetchResolvedIssue } from "../lib/boardSource.js";
3
2
  import { loadConfig } from "../lib/config.js";
4
3
  import { openAgentWorkspace, prepareAgentLaunch } from "../lib/agentLaunch.js";
4
+ import { createBoard } from "../lib/board.js";
5
+ import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
5
6
  import { buildLaunchCommand } from "../lib/launchCommand.js";
6
- import { createLinearIssueStatusUpdater } from "../lib/linearIssueStatus.js";
7
7
  import { recordRunState } from "../lib/runState.js";
8
8
  import { stageBuildSecrets, stagePromptFromTemplate, stageWorkspaceLaunchCommand, } from "../lib/stagedLaunch.js";
9
- import { errorMessage, getLinearClient, log } from "../lib/util.js";
9
+ import { naturalIdFromCanonical } from "../lib/ticketSource.js";
10
+ import { errorMessage, log } from "../lib/util.js";
10
11
  import { workspaces } from "../lib/workspaces.js";
11
12
  import { isWorktreeAlreadyExistsError, worktrees } from "../lib/worktrees.js";
12
- async function fetchTicket(ticket) {
13
- const client = getLinearClient();
14
- const issue = await client.issue(ticket.toUpperCase());
15
- return {
16
- title: issue.title,
17
- description: issue.description ?? "",
18
- };
19
- }
20
13
  function stagePrompt(input) {
21
14
  return stagePromptFromTemplate({
22
15
  config: input.config,
@@ -69,14 +62,7 @@ export async function setupWorkspace(config, options, runOptions = {}) {
69
62
  // the ticket strands forever.
70
63
  let promptDir;
71
64
  try {
72
- let ticketDetails;
73
- if (options.details === undefined) {
74
- log(`Fetching ${ticket} from Linear...`);
75
- ticketDetails = await fetchTicket(ticket);
76
- }
77
- else {
78
- ticketDetails = options.details;
79
- }
65
+ const ticketDetails = options.details;
80
66
  const stagedPrompt = stagePrompt({ config, ticket, ticketDetails, worktreeName });
81
67
  promptDir = stagedPrompt.directory;
82
68
  const secretsFile = stageBuildSecrets(promptDir);
@@ -218,22 +204,36 @@ async function rollbackWorktree(arguments_) {
218
204
  }
219
205
  export async function setupWorkspaceCli(ticket, options = {}) {
220
206
  const config = await loadConfig();
221
- const client = getLinearClient();
222
- const resolved = await fetchResolvedIssue({ client, config, ticket });
207
+ let sources;
208
+ try {
209
+ sources = await buildSources(sourcesFromConfig(config), { globalConfig: config });
210
+ }
211
+ catch (error) {
212
+ /* v8 ignore next @preserve -- catch re-throw always receives an Error; String(error) is an unreachable fallback */
213
+ const message = error instanceof Error ? error.message : String(error);
214
+ throw new Error(`Could not initialize ticket sources for 'crew setup ${ticket}': ${message}`, {
215
+ cause: error,
216
+ });
217
+ }
218
+ const board = createBoard(sources);
219
+ const resolved = await board.resolveOne(ticket);
220
+ if (resolved === undefined) {
221
+ throw new Error(`Ticket ${ticket} not found across configured sources.`);
222
+ }
223
+ if (resolved.repository === undefined || resolved.model === undefined) {
224
+ throw new Error(`Ticket ${ticket} resolved but isn't groundcrew-eligible (missing agent-* label or repository/model).`);
225
+ }
223
226
  log(`Resolved ${ticket}: repository=${resolved.repository}, model=${resolved.model}`);
224
227
  if (options.dryRun === true) {
225
228
  log(`[dry-run] Would launch ${ticket} in ${resolved.repository} (${resolved.model})`);
226
229
  return;
227
230
  }
231
+ const naturalId = naturalIdFromCanonical(resolved.id);
228
232
  await setupWorkspace(config, {
229
- ticket: ticket.toLowerCase(),
233
+ ticket: naturalId,
230
234
  repository: resolved.repository,
231
235
  model: resolved.model,
232
236
  details: { title: resolved.title, description: resolved.description },
233
237
  });
234
- await createLinearIssueStatusUpdater({ client }).markInProgress({
235
- id: ticket.toLowerCase(),
236
- uuid: resolved.uuid,
237
- teamId: resolved.teamId,
238
- });
238
+ await board.markInProgress(resolved);
239
239
  }
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAWnE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuLD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMnE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAyLD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
@@ -1,8 +1,9 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { fetchRawLinearIssue } from "../lib/boardSource.js";
2
+ import { getLinearClient } from "../lib/adapters/linear/client.js";
3
+ import { fetchRawLinearIssue } from "../lib/adapters/linear/fetch.js";
3
4
  import { loadConfig } from "../lib/config.js";
4
5
  import { readRunState } from "../lib/runState.js";
5
- import { errorMessage, getLinearClient, withLogOutputSuppressed, writeOutput, } from "../lib/util.js";
6
+ import { errorMessage, withLogOutputSuppressed, writeOutput } from "../lib/util.js";
6
7
  import { workspaces } from "../lib/workspaces.js";
7
8
  import { worktrees } from "../lib/worktrees.js";
8
9
  const RECENT_LOG_LINE_COUNT = 10;
@@ -95,7 +96,9 @@ function recentTicketLogLines(config, ticket) {
95
96
  async function linearStatus(ticket) {
96
97
  try {
97
98
  const issue = await fetchRawLinearIssue({ client: getLinearClient(), ticket });
98
- return `${issue.stateName} (state.type=${issue.stateType ?? "unknown"}) ${issue.title}`;
99
+ // `stateType` is "" when Linear returned a stateless ticket; surface that
100
+ // as "unknown" rather than an empty token.
101
+ return `${issue.stateName} (state.type=${issue.stateType || "unknown"}) — ${issue.title}`;
99
102
  }
100
103
  catch (error) {
101
104
  return `unavailable: ${errorMessage(error)}`;
package/dist/index.d.ts CHANGED
@@ -9,11 +9,12 @@ export { status, type StatusOptions } from "./commands/status.ts";
9
9
  export type { Config, ModelDefinition, ResolvedConfig, SourceConfig } from "./lib/config.ts";
10
10
  export { loadConfig } from "./lib/config.ts";
11
11
  export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, type RunLifecycleState, type RunState, } from "./lib/runState.ts";
12
- export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isIssueInProgress, isIssueTodo, isTerminalStateType, isTerminalStatusForBlocker, isTerminalStatusForIssue, resolveModelFor, resolveRepositoryFor, type ModelResolution, type RawLinearIssue, type RepositoryResolution, } from "./lib/boardSource.ts";
12
+ export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isIssueInProgress, isIssueTodo, isTerminalStateType, isTerminalStatusForBlocker, isTerminalStatusForIssue, type RawLinearIssue, } from "./lib/adapters/linear/fetch.ts";
13
+ export { resolveModelFor, resolveRepositoryFor, type ModelResolution, type RepositoryResolution, } from "./lib/adapters/linear/parsing.ts";
13
14
  export { getUsageByModel, type UsageByModel } from "./lib/usage.ts";
14
15
  export { type Board, createBoard } from "./lib/board.ts";
15
16
  export { buildSources, buildSourcesWith } from "./lib/buildSources.ts";
16
17
  export type { AdapterContext, AdapterDefinition } from "./lib/adapterDefinition.ts";
17
18
  export { adapterRegistry, type AdapterLoader, buildRegistry, buildSourceConfigSchema, listAdapterDirectories, } from "./lib/adapters/registry.ts";
18
- 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";
19
+ export { AmbiguousTicketError, type Blocker as CanonicalBlocker, type BoardState as CanonicalBoardState, type CanonicalStatus, type GroundcrewIssue as CanonicalGroundcrewIssue, type Issue as CanonicalIssue, isGroundcrewIssue as isCanonicalGroundcrewIssue, type ParentSkip as CanonicalParentSkip, type TicketSource, } from "./lib/ticketSource.ts";
19
20
  //# sourceMappingURL=index.d.ts.map
@@ -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,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAClE,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,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,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,eAAe,EACf,oBAAoB,EACpB,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"}
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,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAClE,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,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,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,KAAK,cAAc,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,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,UAAU,IAAI,mBAAmB,EACtC,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -8,10 +8,10 @@ export { setupWorkspace } from "./commands/setupWorkspace.js";
8
8
  export { status } from "./commands/status.js";
9
9
  export { loadConfig } from "./lib/config.js";
10
10
  export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, } from "./lib/runState.js";
11
- export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isIssueInProgress, isIssueTodo, isTerminalStateType, isTerminalStatusForBlocker, isTerminalStatusForIssue, resolveModelFor, resolveRepositoryFor, } from "./lib/boardSource.js";
11
+ export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isIssueInProgress, isIssueTodo, isTerminalStateType, isTerminalStatusForBlocker, isTerminalStatusForIssue, } from "./lib/adapters/linear/fetch.js";
12
+ export { resolveModelFor, resolveRepositoryFor, } from "./lib/adapters/linear/parsing.js";
12
13
  export { getUsageByModel } from "./lib/usage.js";
13
14
  export { createBoard } from "./lib/board.js";
14
15
  export { buildSources, buildSourcesWith } from "./lib/buildSources.js";
15
16
  export { adapterRegistry, buildRegistry, buildSourceConfigSchema, listAdapterDirectories, } from "./lib/adapters/registry.js";
16
17
  export { AmbiguousTicketError, isGroundcrewIssue as isCanonicalGroundcrewIssue, } from "./lib/ticketSource.js";
17
- // RepositoryResolutionError is exported via boardSource.ts above (single canonical location).
@@ -0,0 +1,22 @@
1
+ import { LinearClient } from "@linear/sdk";
2
+ declare const LINEAR_API_KEY_SOURCES: readonly ["GROUNDCREW_LINEAR_API_KEY", "LINEAR_API_KEY"];
3
+ export type LinearApiKeySource = (typeof LINEAR_API_KEY_SOURCES)[number];
4
+ export interface ResolvedLinearApiKey {
5
+ value: string;
6
+ source: LinearApiKeySource;
7
+ }
8
+ export declare function resolveLinearApiKey(): ResolvedLinearApiKey | undefined;
9
+ export declare function getLinearClient(): LinearClient;
10
+ /**
11
+ * Returns a zero-arg getter that lazily constructs (and caches) a Linear
12
+ * client on first call. Used by CLI entry points that may not need the
13
+ * client at all (e.g. `--no-linear`), so we avoid blowing up on a missing
14
+ * API key when no Linear call is actually made. The factory is taken as a
15
+ * parameter (rather than calling `getLinearClient` directly) so callers can
16
+ * pass their own module-level import of `getLinearClient` — that binding
17
+ * respects `vi.mock` intercepts, whereas an intra-module reference would
18
+ * not.
19
+ */
20
+ export declare function lazyLinearClient(factory: () => LinearClient): () => LinearClient;
21
+ export {};
22
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,QAAA,MAAM,sBAAsB,YAAI,2BAA2B,EAAE,gBAAgB,CAAU,CAAC;AAExF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,IAAI,oBAAoB,GAAG,SAAS,CAQtE;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,MAAM,YAAY,CAMhF"}
@@ -0,0 +1,36 @@
1
+ import { LinearClient } from "@linear/sdk";
2
+ import { readEnvironmentVariable } from "../../util.js";
3
+ const LINEAR_API_KEY_SOURCES = ["GROUNDCREW_LINEAR_API_KEY", "LINEAR_API_KEY"];
4
+ export function resolveLinearApiKey() {
5
+ for (const source of LINEAR_API_KEY_SOURCES) {
6
+ const value = readEnvironmentVariable(source);
7
+ if (value !== undefined && value.length > 0) {
8
+ return { value, source };
9
+ }
10
+ }
11
+ return undefined;
12
+ }
13
+ export function getLinearClient() {
14
+ const resolved = resolveLinearApiKey();
15
+ if (resolved === undefined) {
16
+ throw new Error("Linear API key not set. Set GROUNDCREW_LINEAR_API_KEY or LINEAR_API_KEY in your environment.");
17
+ }
18
+ return new LinearClient({ apiKey: resolved.value });
19
+ }
20
+ /**
21
+ * Returns a zero-arg getter that lazily constructs (and caches) a Linear
22
+ * client on first call. Used by CLI entry points that may not need the
23
+ * client at all (e.g. `--no-linear`), so we avoid blowing up on a missing
24
+ * API key when no Linear call is actually made. The factory is taken as a
25
+ * parameter (rather than calling `getLinearClient` directly) so callers can
26
+ * pass their own module-level import of `getLinearClient` — that binding
27
+ * respects `vi.mock` intercepts, whereas an intra-module reference would
28
+ * not.
29
+ */
30
+ export function lazyLinearClient(factory) {
31
+ let client;
32
+ return () => {
33
+ client ??= factory();
34
+ return client;
35
+ };
36
+ }
@@ -1,23 +1,33 @@
1
1
  /**
2
- * Linear `TicketSource` factory. Wraps the existing boardSource.ts machinery
3
- * (createBoardSource, fetchResolvedIssue, createLinearIssueStatusUpdater) and
4
- * converts the Linear-native `Issue`/`Blocker` shapes into the canonical
5
- * `Issue`/`Blocker` shapes consumers (via `Board`) speak.
2
+ * Linear `TicketSource` factory. Assembles the adapter from sibling modules
3
+ * (createBoardSource + fetchResolvedIssue from ./fetch.ts;
4
+ * createLinearIssueStatusUpdater from ./writeback.ts; getLinearClient from
5
+ * ./client.ts) and converts Linear-specific shapes into the canonical
6
+ * Issue/Blocker types consumers (via Board) speak.
6
7
  *
7
- * Status mapping is driven entirely by Linear's workflow `state.type`
8
- * (`unstarted` todo, `started` in-progress,
9
- * `completed`/`canceled`/`duplicate` → done) so renamed columns are classified
10
- * correctly without any per-team config.
8
+ * State classification is driven by Linear's workflow `state.type` — never
9
+ * by status name so workspaces with renamed columns Just Work without
10
+ * per-team config.
11
11
  *
12
- * Description is not populated on `fetch()` Issues (boardSource's snapshot
13
- * doesn't include it); `resolveOne()` Issues carry the full description
14
- * because `fetchResolvedIssue` fetches it explicitly.
12
+ * Description is populated on both `fetch()` Issues and `resolveOne()` Issues.
15
13
  */
16
14
  import type { AdapterContext } from "../../adapterDefinition.ts";
17
- import { type Issue as LinearIssue } from "../../boardSource.ts";
18
- import type { CanonicalStatus, Issue as CanonicalIssue, TicketSource } from "../../ticketSource.ts";
15
+ import { type Issue as CanonicalIssue, type TicketSource } from "../../ticketSource.ts";
19
16
  import type { LinearAdapterConfig } from "./schema.ts";
20
- export declare function canonicalStatusFromStateType(stateType: string | undefined): CanonicalStatus;
17
+ import { type Issue as LinearIssue } from "./fetch.ts";
18
+ /**
19
+ * Adapter-private payload threaded through `Issue.sourceRef`. Consumers
20
+ * MUST NOT inspect; only the Linear adapter reads it.
21
+ */
22
+ export interface LinearSourceRef {
23
+ uuid: string;
24
+ statusId: string;
25
+ teamId: string;
26
+ /** Linear workflow `state.type` for the issue at fetch time. */
27
+ stateType: string;
28
+ /** Human-readable native status name, e.g. "In Progress", "Shipped". Diagnostic display only. */
29
+ nativeStatus: string;
30
+ }
21
31
  export declare function toCanonicalIssue(linearIssue: LinearIssue, sourceName: string): CanonicalIssue;
22
32
  export declare function createLinearTicketSource(config: LinearAdapterConfig, context: AdapterContext): TicketSource;
23
33
  //# sourceMappingURL=factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAEV,eAAe,EACf,KAAK,IAAI,cAAc,EACvB,YAAY,EACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AASvD,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,CAW3F;AAUD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAsB7F;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,YAAY,CA6Dd"}
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,EAE5B,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAKL,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,YAAY,CAAC;AAGpB;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,YAAY,EAAE,MAAM,CAAC;CACtB;AAkFD,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAsB7F;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,YAAY,CA+Ed"}
@@ -1,38 +1,90 @@
1
1
  /**
2
- * Linear `TicketSource` factory. Wraps the existing boardSource.ts machinery
3
- * (createBoardSource, fetchResolvedIssue, createLinearIssueStatusUpdater) and
4
- * converts the Linear-native `Issue`/`Blocker` shapes into the canonical
5
- * `Issue`/`Blocker` shapes consumers (via `Board`) speak.
2
+ * Linear `TicketSource` factory. Assembles the adapter from sibling modules
3
+ * (createBoardSource + fetchResolvedIssue from ./fetch.ts;
4
+ * createLinearIssueStatusUpdater from ./writeback.ts; getLinearClient from
5
+ * ./client.ts) and converts Linear-specific shapes into the canonical
6
+ * Issue/Blocker types consumers (via Board) speak.
6
7
  *
7
- * Status mapping is driven entirely by Linear's workflow `state.type`
8
- * (`unstarted` todo, `started` in-progress,
9
- * `completed`/`canceled`/`duplicate` → done) so renamed columns are classified
10
- * correctly without any per-team config.
8
+ * State classification is driven by Linear's workflow `state.type` — never
9
+ * by status name so workspaces with renamed columns Just Work without
10
+ * per-team config.
11
11
  *
12
- * Description is not populated on `fetch()` Issues (boardSource's snapshot
13
- * doesn't include it); `resolveOne()` Issues carry the full description
14
- * because `fetchResolvedIssue` fetches it explicitly.
12
+ * Description is populated on both `fetch()` Issues and `resolveOne()` Issues.
15
13
  */
16
- import { createBoardSource, fetchResolvedIssue, isTerminalStateType, } from "../../boardSource.js";
17
- import { createLinearIssueStatusUpdater } from "../../linearIssueStatus.js";
18
- import { getLinearClient } from "../../util.js";
19
- export function canonicalStatusFromStateType(stateType) {
20
- if (stateType === "unstarted") {
21
- return "todo";
14
+ import { toCanonicalId, } from "../../ticketSource.js";
15
+ import { getLinearClient, lazyLinearClient } from "./client.js";
16
+ import { createBoardSource, fetchResolvedIssue, isTerminalStateType, } from "./fetch.js";
17
+ import { createLinearIssueStatusUpdater } from "./writeback.js";
18
+ function canonicalStatusFromStateType(stateType) {
19
+ /* v8 ignore next 3 @preserve -- LinearIssue.stateType is non-optional; this guard is defensive for the resolveOne path */
20
+ if (stateType === undefined) {
21
+ return "other";
22
22
  }
23
- if (stateType === "started") {
24
- return "in-progress";
23
+ switch (stateType) {
24
+ case "unstarted": {
25
+ return "todo";
26
+ }
27
+ case "started": {
28
+ return "in-progress";
29
+ }
30
+ case "completed":
31
+ case "canceled":
32
+ case "duplicate": {
33
+ return "done";
34
+ }
35
+ default: {
36
+ return "other";
37
+ }
25
38
  }
26
- if (isTerminalStateType(stateType)) {
27
- return "done";
39
+ }
40
+ function canonicalBlockerStatus(blocker) {
41
+ if (blocker.stateType === undefined) {
42
+ return {
43
+ status: "other",
44
+ statusReason: "missing",
45
+ ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
46
+ };
47
+ }
48
+ if (isTerminalStateType(blocker.stateType)) {
49
+ return {
50
+ status: "done",
51
+ ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
52
+ };
53
+ }
54
+ if (blocker.stateType === "started") {
55
+ return {
56
+ status: "in-progress",
57
+ ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
58
+ };
28
59
  }
29
- return "other";
60
+ if (blocker.stateType === "unstarted") {
61
+ return {
62
+ status: "todo",
63
+ ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
64
+ };
65
+ }
66
+ // backlog / triage / anything else falls through as "other"
67
+ return {
68
+ status: "other",
69
+ statusReason: "unmapped",
70
+ ...(blocker.status !== undefined && { nativeStatus: blocker.status }),
71
+ };
30
72
  }
31
73
  function toCanonicalBlocker(blocker, sourceName) {
74
+ const { status, statusReason, nativeStatus } = canonicalBlockerStatus(blocker);
32
75
  return {
33
- id: `${sourceName}:${blocker.id}`,
76
+ id: toCanonicalId(sourceName, blocker.id),
34
77
  title: blocker.title,
35
- status: canonicalStatusFromStateType(blocker.stateType),
78
+ status,
79
+ ...(statusReason !== undefined && { statusReason }),
80
+ ...(nativeStatus !== undefined && { nativeStatus }),
81
+ };
82
+ }
83
+ function toCanonicalParentSkip(skip, sourceName) {
84
+ return {
85
+ id: toCanonicalId(sourceName, skip.id),
86
+ title: skip.title,
87
+ childCount: skip.childCount,
36
88
  };
37
89
  }
38
90
  export function toCanonicalIssue(linearIssue, sourceName) {
@@ -40,14 +92,14 @@ export function toCanonicalIssue(linearIssue, sourceName) {
40
92
  uuid: linearIssue.uuid,
41
93
  statusId: linearIssue.statusId,
42
94
  teamId: linearIssue.teamId,
95
+ stateType: linearIssue.stateType,
43
96
  nativeStatus: linearIssue.status,
44
97
  };
45
98
  return {
46
- id: `${sourceName}:${linearIssue.id}`,
99
+ id: toCanonicalId(sourceName, linearIssue.id),
47
100
  source: sourceName,
48
101
  title: linearIssue.title,
49
- // Board snapshot doesn't carry description; resolveOne() populates it.
50
- description: "",
102
+ description: linearIssue.description,
51
103
  status: canonicalStatusFromStateType(linearIssue.stateType),
52
104
  repository: linearIssue.repository,
53
105
  model: linearIssue.model,
@@ -61,44 +113,59 @@ export function toCanonicalIssue(linearIssue, sourceName) {
61
113
  export function createLinearTicketSource(config, context) {
62
114
  const sourceName = config.name ?? "linear";
63
115
  const { globalConfig } = context;
64
- const client = getLinearClient();
65
- const boardSource = createBoardSource({ config: globalConfig, client });
66
- const issueStatusUpdater = createLinearIssueStatusUpdater({ client });
116
+ // Lazy: deferring `getLinearClient()` (and the sub-modules that depend on
117
+ // it) until first method use means `createLinearTicketSource` can be
118
+ // constructed without a Linear API key in env. Callers that only ever
119
+ // touch a sibling source — `crew doctor --ticket <shell-id>`,
120
+ // `crew run` with the multi-source Board's `Promise.allSettled` fan-out
121
+ // tolerating a Linear-side rejection — no longer crash at config-load
122
+ // time on a missing key.
123
+ const getClient = lazyLinearClient(getLinearClient);
124
+ let cachedBoardSource;
125
+ function getBoardSource() {
126
+ cachedBoardSource ??= createBoardSource({ config: globalConfig, client: getClient() });
127
+ return cachedBoardSource;
128
+ }
129
+ let cachedIssueStatusUpdater;
130
+ function getIssueStatusUpdater() {
131
+ cachedIssueStatusUpdater ??= createLinearIssueStatusUpdater({
132
+ client: getClient(),
133
+ });
134
+ return cachedIssueStatusUpdater;
135
+ }
136
+ let lastParentSkips = [];
67
137
  return {
68
138
  name: sourceName,
69
139
  async verify() {
70
- await boardSource.verify();
140
+ await getBoardSource().verify();
71
141
  },
72
142
  async fetch() {
73
- const state = await boardSource.fetch();
143
+ const state = await getBoardSource().fetch();
144
+ lastParentSkips = state.parentSkips.map((skip) => toCanonicalParentSkip(skip, sourceName));
74
145
  return state.issues.map((linearIssue) => toCanonicalIssue(linearIssue, sourceName));
75
146
  },
147
+ async fetchParentSkips() {
148
+ return lastParentSkips;
149
+ },
76
150
  async resolveOne(naturalId) {
77
- // fetchResolvedIssue throws on missing repo; we let those propagate.
78
- // Returning `undefined` is reserved for "ticket genuinely doesn't
79
- // exist," which fetchResolvedIssue surfaces as an Error too — for now
80
- // we let any error bubble up rather than swallow.
81
151
  const resolved = await fetchResolvedIssue({
82
- client,
152
+ client: getClient(),
83
153
  config: globalConfig,
84
154
  ticket: naturalId,
85
155
  });
86
- // fetchResolvedIssue doesn't return the native status name (it's
87
- // already been resolved through workflow state lookup). We surface
88
- // "other" until the consumer needs the canonical status, which is fine
89
- // because `crew setup` doesn't branch on it.
90
156
  const sourceRef = {
91
157
  uuid: resolved.uuid,
92
- statusId: "",
158
+ statusId: resolved.statusId,
93
159
  teamId: resolved.teamId,
94
- nativeStatus: "",
160
+ stateType: resolved.stateType,
161
+ nativeStatus: resolved.status,
95
162
  };
96
163
  return {
97
- id: `${sourceName}:${naturalId.toLowerCase()}`,
164
+ id: toCanonicalId(sourceName, naturalId),
98
165
  source: sourceName,
99
166
  title: resolved.title,
100
167
  description: resolved.description,
101
- status: "other",
168
+ status: canonicalStatusFromStateType(resolved.stateType),
102
169
  repository: resolved.repository,
103
170
  model: resolved.model,
104
171
  assignee: "Unassigned",
@@ -111,7 +178,7 @@ export function createLinearTicketSource(config, context) {
111
178
  async markInProgress(issue) {
112
179
  // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- by the Linear adapter's contract, every Issue it produces carries a LinearSourceRef in sourceRef
113
180
  const ref = issue.sourceRef;
114
- await issueStatusUpdater.markInProgress({
181
+ await getIssueStatusUpdater().markInProgress({
115
182
  id: issue.id,
116
183
  uuid: ref.uuid,
117
184
  teamId: ref.teamId,