@clipboard-health/groundcrew 4.2.0 → 4.2.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.
Files changed (69) hide show
  1. package/dist/commands/cleaner.d.ts +1 -1
  2. package/dist/commands/cleaner.d.ts.map +1 -1
  3. package/dist/commands/cleaner.js +4 -2
  4. package/dist/commands/dispatcher.d.ts +6 -6
  5. package/dist/commands/dispatcher.d.ts.map +1 -1
  6. package/dist/commands/dispatcher.js +43 -27
  7. package/dist/commands/doctor.d.ts.map +1 -1
  8. package/dist/commands/doctor.js +18 -22
  9. package/dist/commands/eligibility.d.ts +1 -1
  10. package/dist/commands/eligibility.d.ts.map +1 -1
  11. package/dist/commands/eligibility.js +7 -6
  12. package/dist/commands/orchestrator.d.ts.map +1 -1
  13. package/dist/commands/orchestrator.js +18 -14
  14. package/dist/commands/resumeWorkspace.d.ts.map +1 -1
  15. package/dist/commands/resumeWorkspace.js +3 -2
  16. package/dist/commands/setupWorkspace.d.ts +2 -4
  17. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  18. package/dist/commands/setupWorkspace.js +27 -27
  19. package/dist/commands/status.d.ts.map +1 -1
  20. package/dist/commands/status.js +6 -3
  21. package/dist/index.d.ts +3 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -2
  24. package/dist/lib/adapters/linear/client.d.ts +22 -0
  25. package/dist/lib/adapters/linear/client.d.ts.map +1 -0
  26. package/dist/lib/adapters/linear/client.js +36 -0
  27. package/dist/lib/adapters/linear/factory.d.ts +24 -14
  28. package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
  29. package/dist/lib/adapters/linear/factory.js +113 -46
  30. package/dist/lib/{boardSource.d.ts → adapters/linear/fetch.d.ts} +19 -71
  31. package/dist/lib/adapters/linear/fetch.d.ts.map +1 -0
  32. package/dist/lib/{boardSource.js → adapters/linear/fetch.js} +21 -133
  33. package/dist/lib/adapters/linear/index.d.ts +1 -0
  34. package/dist/lib/adapters/linear/index.d.ts.map +1 -1
  35. package/dist/lib/adapters/linear/parsing.d.ts +44 -0
  36. package/dist/lib/adapters/linear/parsing.d.ts.map +1 -0
  37. package/dist/lib/adapters/linear/parsing.js +144 -0
  38. package/dist/lib/{linearIssueStatus.d.ts → adapters/linear/writeback.d.ts} +1 -2
  39. package/dist/lib/adapters/linear/writeback.d.ts.map +1 -0
  40. package/dist/lib/{linearIssueStatus.js → adapters/linear/writeback.js} +16 -17
  41. package/dist/lib/adapters/shell/factory.d.ts +1 -1
  42. package/dist/lib/adapters/shell/factory.d.ts.map +1 -1
  43. package/dist/lib/adapters/shell/factory.js +8 -4
  44. package/dist/lib/adapters/shell/invoke.d.ts +4 -7
  45. package/dist/lib/adapters/shell/invoke.d.ts.map +1 -1
  46. package/dist/lib/adapters/shell/invoke.js +46 -75
  47. package/dist/lib/adapters/shell/schema.d.ts +10 -0
  48. package/dist/lib/adapters/shell/schema.d.ts.map +1 -1
  49. package/dist/lib/adapters/shell/schema.js +9 -5
  50. package/dist/lib/board.d.ts.map +1 -1
  51. package/dist/lib/board.js +43 -4
  52. package/dist/lib/buildSources.d.ts +11 -0
  53. package/dist/lib/buildSources.d.ts.map +1 -1
  54. package/dist/lib/buildSources.js +41 -0
  55. package/dist/lib/repositoryValidation.d.ts +13 -0
  56. package/dist/lib/repositoryValidation.d.ts.map +1 -0
  57. package/dist/lib/repositoryValidation.js +20 -0
  58. package/dist/lib/testing/canonicalFixtures.d.ts +19 -0
  59. package/dist/lib/testing/canonicalFixtures.d.ts.map +1 -0
  60. package/dist/lib/testing/canonicalFixtures.js +62 -0
  61. package/dist/lib/ticketSource.d.ts +71 -1
  62. package/dist/lib/ticketSource.d.ts.map +1 -1
  63. package/dist/lib/ticketSource.js +31 -0
  64. package/dist/lib/util.d.ts +0 -20
  65. package/dist/lib/util.d.ts.map +1 -1
  66. package/dist/lib/util.js +0 -35
  67. package/package.json +1 -1
  68. package/dist/lib/boardSource.d.ts.map +0 -1
  69. package/dist/lib/linearIssueStatus.d.ts.map +0 -1
@@ -3,8 +3,8 @@
3
3
  * tickets that have reached a terminal status. One per `orchestrate()`
4
4
  * invocation; stateless across iterations. Mirrors `Dispatcher`.
5
5
  */
6
- import { type BoardState } from "../lib/boardSource.ts";
7
6
  import type { ResolvedConfig } from "../lib/config.ts";
7
+ import { type BoardState } from "../lib/ticketSource.ts";
8
8
  import { type WorktreeEntry } from "../lib/worktrees.ts";
9
9
  interface CleanerDeps {
10
10
  config: ResolvedConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"cleaner.d.ts","sourceRoot":"","sources":["../../src/commands/cleaner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,UAAU,EAA4B,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAE,KAAK,aAAa,EAAa,MAAM,qBAAqB,CAAC;AAGpE,UAAU,WAAW;IACnB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,CAAC,UAAU,EAAE;QAClB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAkDxD"}
1
+ {"version":3,"file":"cleaner.d.ts","sourceRoot":"","sources":["../../src/commands/cleaner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEjF,OAAO,EAAE,KAAK,aAAa,EAAa,MAAM,qBAAqB,CAAC;AAGpE,UAAU,WAAW;IACnB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,CAAC,UAAU,EAAE;QAClB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAoDxD"}
@@ -3,8 +3,8 @@
3
3
  * tickets that have reached a terminal status. One per `orchestrate()`
4
4
  * invocation; stateless across iterations. Mirrors `Dispatcher`.
5
5
  */
6
- import { isTerminalStatusForIssue } from "../lib/boardSource.js";
7
6
  import { recordCleanedUpRuns } from "../lib/runStateCleanup.js";
7
+ import { naturalIdFromCanonical } from "../lib/ticketSource.js";
8
8
  import { log, logEvent } from "../lib/util.js";
9
9
  import { worktrees } from "../lib/worktrees.js";
10
10
  import { logTeardown, recordTeardownEvents } from "./teardownReporter.js";
@@ -12,7 +12,9 @@ export function createCleaner(deps) {
12
12
  const { config } = deps;
13
13
  async function runOnce(arguments_) {
14
14
  const { state, worktreeEntries, dryRun, signal } = arguments_;
15
- const terminalTickets = new Set(state.issues.filter((issue) => isTerminalStatusForIssue(issue)).map((issue) => issue.id));
15
+ const terminalTickets = new Set(state.issues
16
+ .filter((issue) => issue.status === "done")
17
+ .map((issue) => naturalIdFromCanonical(issue.id)));
16
18
  if (terminalTickets.size === 0) {
17
19
  return;
18
20
  }
@@ -1,19 +1,19 @@
1
1
  /**
2
2
  * Per-iteration decider that picks Todo tickets to start and acts on the
3
- * picks. One per `orchestrate()` invocation; reuses its team-state cache
4
- * across iterations within an invocation.
3
+ * picks. Stateless across iterations. The Board adapter owns its own writeback
4
+ * caches (e.g., Linear's team-state cache lives in `src/lib/adapters/linear/writeback.ts`).
5
5
  *
6
6
  * Pure verdict logic lives in `eligibility.ts`; this module is responsible
7
- * for telemetry, Linear writes, and side-effecting setupWorkspace calls.
7
+ * for telemetry, writeback via Board, and side-effecting setupWorkspace calls.
8
8
  */
9
- import type { LinearClient } from "@linear/sdk";
10
- import { type BoardState } from "../lib/boardSource.ts";
9
+ import type { Board } from "../lib/board.ts";
11
10
  import type { ResolvedConfig } from "../lib/config.ts";
11
+ import { type BoardState } from "../lib/ticketSource.ts";
12
12
  import type { UsageByModel } from "../lib/usage.ts";
13
13
  import type { WorktreeEntry } from "../lib/worktrees.ts";
14
14
  interface DispatcherDeps {
15
15
  config: ResolvedConfig;
16
- client: LinearClient;
16
+ board: Board;
17
17
  }
18
18
  export interface Dispatcher {
19
19
  runOnce(arguments_: {
@@ -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,EAKhB,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;AAaD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAyLjE"}
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,EAIhB,MAAM,wBAAwB,CAAC;AAChC,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,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;AAaD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CA8MjE"}
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Per-iteration decider that picks Todo tickets to start and acts on the
3
- * picks. One per `orchestrate()` invocation; reuses its team-state cache
4
- * across iterations within an invocation.
3
+ * picks. Stateless across iterations. The Board adapter owns its own writeback
4
+ * caches (e.g., Linear's team-state cache lives in `src/lib/adapters/linear/writeback.ts`).
5
5
  *
6
6
  * Pure verdict logic lives in `eligibility.ts`; this module is responsible
7
- * for telemetry, Linear writes, and side-effecting setupWorkspace calls.
7
+ * for telemetry, writeback via Board, and side-effecting setupWorkspace calls.
8
8
  */
9
- import { isGroundcrewIssue, isIssueInProgress, isIssueTodo, } from "../lib/boardSource.js";
10
- import { createLinearIssueStatusUpdater } from "../lib/linearIssueStatus.js";
9
+ import { dispatchableRepository } from "../lib/repositoryValidation.js";
10
+ import { isGroundcrewIssue, naturalIdFromCanonical, } from "../lib/ticketSource.js";
11
11
  import { errorMessage, log, logEvent } from "../lib/util.js";
12
12
  import { workspaces } from "../lib/workspaces.js";
13
13
  import { classifyBlockers, classifyEligibility, classifyUsageExhaustion, } from "./eligibility.js";
@@ -17,14 +17,13 @@ function logSkip(verdict) {
17
17
  logEvent("dispatch", {
18
18
  outcome: "skipped",
19
19
  reason: verdict.eventReason,
20
- ticket: verdict.issue.id,
20
+ ticket: naturalIdFromCanonical(verdict.issue.id),
21
21
  blockers: verdict.blockers,
22
22
  model: verdict.model,
23
23
  });
24
24
  }
25
25
  export function createDispatcher(deps) {
26
- const { config, client } = deps;
27
- const issueStatusUpdater = createLinearIssueStatusUpdater({ client });
26
+ const { config, board } = deps;
28
27
  function buildExhaustedSet(usage) {
29
28
  const exhausted = new Set();
30
29
  for (const exhaustion of classifyUsageExhaustion(config, usage)) {
@@ -35,17 +34,18 @@ export function createDispatcher(deps) {
35
34
  }
36
35
  async function startEligibleIssue(start, dryRun, signal) {
37
36
  const { issue, recovery } = start;
37
+ const ticketId = naturalIdFromCanonical(issue.id);
38
38
  if (start.resolvedFromAny) {
39
- log(`Resolved agent-any for ${issue.id} → ${issue.model}`);
39
+ log(`Resolved agent-any for ${ticketId} → ${issue.model}`);
40
40
  }
41
41
  if (dryRun) {
42
42
  log(
43
43
  /* v8 ignore next @preserve -- classifyTodo forces recovery=false in dry-run, so the resume branch can't fire here */
44
- `[dry-run] Would ${recovery ? "resume" : "start"} ${issue.id} in ${issue.repository} (${issue.model})`);
44
+ `[dry-run] Would ${recovery ? "resume" : "start"} ${ticketId} in ${issue.repository} (${issue.model})`);
45
45
  logEvent("dispatch", {
46
46
  outcome: "skipped",
47
47
  reason: "dry_run",
48
- ticket: issue.id,
48
+ ticket: ticketId,
49
49
  model: issue.model,
50
50
  repository: issue.repository,
51
51
  });
@@ -53,31 +53,32 @@ export function createDispatcher(deps) {
53
53
  }
54
54
  try {
55
55
  if (recovery) {
56
- log(`Worktree and workspace already exist for ${issue.id}; resuming with markInProgress`);
56
+ log(`Worktree and workspace already exist for ${ticketId}; resuming with markInProgress`);
57
57
  }
58
58
  else {
59
59
  const setupOptions = {
60
60
  repository: issue.repository,
61
- ticket: issue.id,
61
+ ticket: ticketId,
62
62
  model: issue.model,
63
+ details: { title: issue.title, description: issue.description },
63
64
  };
64
65
  await (signal === undefined
65
66
  ? setupWorkspace(config, setupOptions)
66
67
  : setupWorkspace(config, setupOptions, { signal }));
67
68
  }
68
- await issueStatusUpdater.markInProgress(issue);
69
+ await board.markInProgress(issue);
69
70
  logEvent("dispatch", {
70
71
  outcome: recovery ? "resumed" : "started",
71
- ticket: issue.id,
72
+ ticket: ticketId,
72
73
  model: issue.model,
73
74
  repository: issue.repository,
74
75
  });
75
76
  }
76
77
  catch (error) {
77
- log(`Failed to start ${issue.id}: ${errorMessage(error)}`);
78
+ log(`Failed to start ${ticketId}: ${errorMessage(error)}`);
78
79
  logEvent("dispatch", {
79
80
  outcome: "failed",
80
- ticket: issue.id,
81
+ ticket: ticketId,
81
82
  model: issue.model,
82
83
  repository: issue.repository,
83
84
  error: errorMessage(error),
@@ -86,24 +87,24 @@ export function createDispatcher(deps) {
86
87
  }
87
88
  async function runOnce(arguments_) {
88
89
  const { state, worktreeEntries, usage, dryRun, signal, idleSuffix = "" } = arguments_;
89
- issueStatusUpdater.resetMissingInProgressCache();
90
- // Surface parent tickets that fetchBoard silently dropped. Without this
90
+ // Surface parent tickets that fetch silently dropped. Without this
91
91
  // an operator sees "No Todo tickets to pick up" with no signal that an
92
92
  // expected Todo+labelled ticket was skipped because it has sub-issues.
93
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`);
94
+ const ticket = naturalIdFromCanonical(skip.id);
95
+ log(`Skipping ${ticket}: parent ticket with ${skip.childCount} sub-issue(s) — groundcrew works sub-issues, not parents`);
95
96
  logEvent("dispatch", {
96
97
  outcome: "skipped",
97
98
  reason: "parent_with_children",
98
- ticket: skip.id,
99
+ ticket,
99
100
  children: skip.childCount,
100
101
  });
101
102
  }
102
- const activeCount = state.issues.filter((issue) => isIssueInProgress(issue)).length;
103
+ const activeCount = state.issues.filter((issue) => issue.status === "in-progress").length;
103
104
  const slots = config.orchestrator.maximumInProgress - activeCount;
104
105
  // Narrow Todo to tickets that opted in via an `agent-*` label.
105
106
  // Unlabeled tickets are not groundcrew's concern even when in Todo.
106
- const todo = state.issues.filter((issue) => isIssueTodo(issue) && isGroundcrewIssue(issue));
107
+ const todo = state.issues.filter((issue) => issue.status === "todo" && isGroundcrewIssue(issue));
107
108
  if (slots <= 0) {
108
109
  log(`At capacity (${activeCount}/${config.orchestrator.maximumInProgress}), no new work to start${idleSuffix}`);
109
110
  return;
@@ -122,6 +123,20 @@ export function createDispatcher(deps) {
122
123
  log(`No eligible Todo tickets after blocker filtering${idleSuffix}`);
123
124
  return;
124
125
  }
126
+ // Validate repositories BEFORE the expensive probes so a tick whose only
127
+ // candidates have unknown repos short-circuits without paying for the
128
+ // usage() HTTP call or the workspaces.probe shell-out. Doing this filter
129
+ // here also keeps an unknown-repo ticket at the head of the queue from
130
+ // consuming a slot in classifyEligibility and starving later valid
131
+ // tickets. Each unknown repo still emits a WARN via dispatchableRepository.
132
+ const dispatchableUnblocked = unblocked.filter((issue) => {
133
+ const repository = dispatchableRepository(issue, config.workspace.knownRepositories, log);
134
+ return repository !== undefined;
135
+ });
136
+ if (dispatchableUnblocked.length === 0) {
137
+ log(`No eligible Todo tickets after repository validation${idleSuffix}`);
138
+ return;
139
+ }
125
140
  // usage() is an HTTP call; workspaces.probe shells tmux/cmux. Kick off
126
141
  // usage first so the workspace probe can overlap with the in-flight request.
127
142
  const usagePromise = usage(signal);
@@ -144,7 +159,7 @@ export function createDispatcher(deps) {
144
159
  const exhausted = buildExhaustedSet(fetchedUsage);
145
160
  const verdicts = classifyEligibility({
146
161
  config,
147
- unblocked,
162
+ unblocked: dispatchableUnblocked,
148
163
  worktreeEntries,
149
164
  workspaceProbe,
150
165
  usage: fetchedUsage,
@@ -161,12 +176,13 @@ export function createDispatcher(deps) {
161
176
  log(`No eligible Todo tickets after eligibility filtering${idleSuffix}`);
162
177
  return;
163
178
  }
164
- log(`${slots} slot(s) available, starting ${starts.length} ticket(s): ${starts.map(({ issue }) => `${issue.id}(${issue.model})`).join(", ")}`);
179
+ const dispatchable = starts;
180
+ log(`${slots} slot(s) available, starting ${dispatchable.length} ticket(s): ${dispatchable.map(({ issue }) => `${naturalIdFromCanonical(issue.id)}(${issue.model})`).join(", ")}`);
165
181
  logEvent("dispatch", {
166
182
  outcome: "starting",
167
- tickets: starts.map(({ issue }) => `${issue.id}:${issue.model}`),
183
+ tickets: dispatchable.map(({ issue }) => `${naturalIdFromCanonical(issue.id)}:${issue.model}`),
168
184
  });
169
- for (const start of starts) {
185
+ for (const start of dispatchable) {
170
186
  // oxlint-disable-next-line no-await-in-loop -- one workspace at a time avoids racing on git
171
187
  await startEligibleIssue(start, dryRun, signal);
172
188
  }
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoJH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CA8E/C"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgJH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CA8E/C"}
@@ -3,12 +3,13 @@
3
3
  * Returns true if every required check passes; false otherwise.
4
4
  */
5
5
  import { existsSync, statSync } from "node:fs";
6
+ import { createBoard } from "../lib/board.js";
7
+ import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
6
8
  import { loadConfig, } from "../lib/config.js";
7
- import { createBoardSource } from "../lib/boardSource.js";
8
9
  import { detectHostCapabilities, which } from "../lib/host.js";
9
10
  import { resolveLocalRunner } from "../lib/localRunner.js";
10
11
  import { gatedModels } from "../lib/usage.js";
11
- import { errorMessage, getLinearClient, resolveLinearApiKey, writeOutput } from "../lib/util.js";
12
+ import { errorMessage, writeOutput } from "../lib/util.js";
12
13
  import { resolveWorkspaceKind } from "../lib/workspaces.js";
13
14
  // Tokenization stops after this many non-flag tokens. Two is enough to
14
15
  // catch wrapper + wrapped CLI commands like `safehouse claude --foo`.
@@ -26,32 +27,27 @@ async function checkCmd(cmd, required, hint) {
26
27
  }
27
28
  return result;
28
29
  }
29
- async function checkLinearReachability(config) {
30
- const resolved = resolveLinearApiKey();
31
- if (resolved === undefined) {
32
- return {
33
- name: "linear reachability",
34
- ok: false,
35
- required: true,
36
- hint: "export $GROUNDCREW_LINEAR_API_KEY or $LINEAR_API_KEY",
37
- };
38
- }
30
+ /**
31
+ * Source-agnostic reachability check: build every configured ticket source
32
+ * and run the Board's `verify()` fan-out. Replaces the old Linear-only
33
+ * "api key + reachability" probe so a misconfigured shell (or future Jira)
34
+ * source surfaces here too. A missing Linear API key still fails verify with
35
+ * its own user-facing message, so the prior behavior is preserved.
36
+ */
37
+ async function checkSourceProbe(config) {
39
38
  try {
40
- await createBoardSource({ config, client: getLinearClient() }).verify();
39
+ const sources = await buildSources(sourcesFromConfig(config), { globalConfig: config });
40
+ const board = createBoard(sources);
41
+ await board.verify();
41
42
  return {
42
- name: "linear reachability",
43
+ name: "source probe",
43
44
  ok: true,
44
45
  required: true,
45
- hint: `set via $${resolved.source}`,
46
+ hint: `${sources.length} source(s) verified`,
46
47
  };
47
48
  }
48
49
  catch (error) {
49
- return {
50
- name: "linear reachability",
51
- ok: false,
52
- required: true,
53
- hint: errorMessage(error),
54
- };
50
+ return { name: "source probe", ok: false, required: true, hint: errorMessage(error) };
55
51
  }
56
52
  }
57
53
  function checkDir(path, label) {
@@ -161,7 +157,7 @@ export async function doctor() {
161
157
  const workspaceOutcome = resolveWorkspaceOutcome(config, host);
162
158
  reportWorkspaceKind(config, workspaceOutcome);
163
159
  const checks = [
164
- await checkLinearReachability(config),
160
+ await checkSourceProbe(config),
165
161
  await checkCmd("git", true, "https://git-scm.com/"),
166
162
  ...(await workspaceChecks(workspaceOutcome)),
167
163
  checkDir(config.workspace.projectDir, "workspace.projectDir"),
@@ -6,8 +6,8 @@
6
6
  * The Dispatcher consumes the verdict list to drive logging and side
7
7
  * effects.
8
8
  */
9
- import { type GroundcrewIssue } from "../lib/boardSource.ts";
10
9
  import { type ResolvedConfig } from "../lib/config.ts";
10
+ import { type GroundcrewIssue } from "../lib/ticketSource.ts";
11
11
  import type { UsageByModel } from "../lib/usage.ts";
12
12
  import type { WorkspaceProbe } from "../lib/workspaces.ts";
13
13
  import type { WorktreeEntry } from "../lib/worktrees.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"eligibility.d.ts","sourceRoot":"","sources":["../../src/commands/eligibility.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOzD,KAAK,UAAU,GACX,SAAS,GACT,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,4BAA4B,GAC5B,mBAAmB,CAAC;AAExB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,WAAW,EAAE,UAAU,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,OAAO,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1C,MAAM,MAAM,oBAAoB,GAC5B;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,oDAAoD;IACpD,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,UAAU,qBAAqB;IAC7B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAgCD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,MAAM,GAAG,SAAS,CAepB;AAaD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,GAClB,oBAAoB,EAAE,CAmCxB;AA4CD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,GAAG,qBAAqB,CAYxF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,EAAE,CAgE5E"}
1
+ {"version":3,"file":"eligibility.d.ts","sourceRoot":"","sources":["../../src/commands/eligibility.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAwC,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOzD,KAAK,UAAU,GACX,SAAS,GACT,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,4BAA4B,GAC5B,mBAAmB,CAAC;AAExB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,WAAW,EAAE,UAAU,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,OAAO,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1C,MAAM,MAAM,oBAAoB,GAC5B;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IACtC,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,oDAAoD;IACpD,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,UAAU,qBAAqB;IAC7B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAgCD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,MAAM,GAAG,SAAS,CAepB;AAaD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,GAClB,oBAAoB,EAAE,CAmCxB;AA6CD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,GAAG,qBAAqB,CAYxF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,EAAE,CAgE5E"}
@@ -6,14 +6,14 @@
6
6
  * The Dispatcher consumes the verdict list to drive logging and side
7
7
  * effects.
8
8
  */
9
- import { isTerminalStatusForBlocker, } from "../lib/boardSource.js";
10
9
  import { AGENT_ANY_MODEL } from "../lib/config.js";
10
+ import { naturalIdFromCanonical } from "../lib/ticketSource.js";
11
11
  const PERCENT_FRACTION_DIVISOR = 100;
12
12
  const DAYS_PER_WEEK = 7;
13
13
  const MINUTES_PER_DAY = 24 * 60;
14
14
  const MINUTES_PER_WEEK = DAYS_PER_WEEK * MINUTES_PER_DAY;
15
15
  function blockerSummary(blocker) {
16
- return `${blocker.id}:${blocker.status ?? "missing"}`;
16
+ return `${blocker.id}:${blocker.status}`;
17
17
  }
18
18
  function blockerVerdictFor(issue) {
19
19
  if (issue.hasMoreBlockers) {
@@ -26,7 +26,7 @@ function blockerVerdictFor(issue) {
26
26
  blockers,
27
27
  };
28
28
  }
29
- const unresolved = issue.blockers.filter((blocker) => !isTerminalStatusForBlocker(blocker));
29
+ const unresolved = issue.blockers.filter((blocker) => blocker.status !== "done");
30
30
  if (unresolved.length === 0) {
31
31
  return undefined;
32
32
  }
@@ -110,7 +110,8 @@ function classifyRecovery(arguments_) {
110
110
  if (dryRun) {
111
111
  return { kind: "go", recovery: false };
112
112
  }
113
- const exists = worktreeEntries.some((entry) => entry.repository === issue.repository && entry.ticket === issue.id);
113
+ const naturalId = naturalIdFromCanonical(issue.id);
114
+ const exists = worktreeEntries.some((entry) => entry.repository === issue.repository && entry.ticket === naturalId);
114
115
  if (!exists) {
115
116
  return { kind: "go", recovery: false };
116
117
  }
@@ -122,11 +123,11 @@ function classifyRecovery(arguments_) {
122
123
  eventReason: "workspace_list_unavailable",
123
124
  };
124
125
  }
125
- if (!workspaceProbe.names.has(issue.id)) {
126
+ if (!workspaceProbe.names.has(naturalId)) {
126
127
  return {
127
128
  kind: "skip",
128
129
  issue,
129
- message: `Skipping ${issue.id}: worktree exists but no live workspace. Run \`crew cleanup ${issue.id}\` to allow re-provisioning.`,
130
+ message: `Skipping ${issue.id}: worktree exists but no live workspace. Run \`crew cleanup ${naturalId}\` to allow re-provisioning.`,
130
131
  eventReason: "workspace_missing",
131
132
  };
132
133
  }
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAqDH,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"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyDH,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,CA0C7E"}
@@ -4,12 +4,12 @@
4
4
  * the cleaner, and runs the dispatcher; logging from those modules is the
5
5
  * orchestrator's user-facing output.
6
6
  */
7
- import { createBoardSource } from "../lib/boardSource.js";
8
7
  import { createBoard } from "../lib/board.js";
9
- import { buildSources } from "../lib/buildSources.js";
8
+ import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
10
9
  import { loadConfig } from "../lib/config.js";
10
+ import { RepositoryResolutionError } from "../lib/ticketSource.js";
11
11
  import { getUsageByModel } from "../lib/usage.js";
12
- import { errorMessage, getLinearClient, log, sleep } from "../lib/util.js";
12
+ import { errorMessage, log, sleep } from "../lib/util.js";
13
13
  import { worktrees } from "../lib/worktrees.js";
14
14
  import { createCleaner } from "./cleaner.js";
15
15
  import { createDispatcher } from "./dispatcher.js";
@@ -24,6 +24,10 @@ async function withRetry(function_, signal, maxRetries = RETRY_MAX_ATTEMPTS, bas
24
24
  return await function_();
25
25
  }
26
26
  catch (error) {
27
+ /* v8 ignore next 2 @preserve -- fetch() warns-and-skips since PR#88; guard is a defensive no-op in practice */
28
+ if (error instanceof RepositoryResolutionError) {
29
+ throw error;
30
+ }
27
31
  if (attempt === maxRetries) {
28
32
  throw error;
29
33
  }
@@ -61,25 +65,21 @@ async function fetchUsageOrEmpty(config, signal) {
61
65
  }
62
66
  export async function orchestrate(options) {
63
67
  const config = await loadConfig();
64
- const client = getLinearClient();
65
- const boardSource = createBoardSource({ config, client });
66
- await boardSource.verify();
67
- // Verify any pluggable sources declared in config.sources (shell adapters,
68
- // future built-in adapters) at startup. The Linear path still runs through
69
- // boardSource.fetch in the main loop; shell-source dispatch is a follow-up.
70
- // An empty config.sources resolves to an empty Board and verify() is a no-op.
71
- const extraSources = await buildSources(config.sources, { globalConfig: config });
72
- const board = createBoard(extraSources);
68
+ // Build all sources (Linear implicit + any user-declared shell adapters).
69
+ // sourcesFromConfig synthesizes the implicit linear source from the config;
70
+ // this ensures the Linear adapter's markInProgress is reachable via board.
71
+ const allSources = await buildSources(sourcesFromConfig(config), { globalConfig: config });
72
+ const board = createBoard(allSources);
73
73
  await board.verify();
74
74
  const cleaner = createCleaner({ config });
75
- const dispatcher = createDispatcher({ config, client });
75
+ const dispatcher = createDispatcher({ config, board });
76
76
  // Folded into the dispatcher's idle log lines in watch mode so each idle
77
77
  // tick prints one combined line instead of "<reason>" + "Next poll in Xs".
78
78
  const idleSuffix = options.watch
79
79
  ? `; next poll in ${config.orchestrator.pollIntervalMilliseconds / MS_PER_SECOND}s`
80
80
  : undefined;
81
81
  const tick = async (signal) => {
82
- const state = await withRetry(async () => await boardSource.fetch(), signal);
82
+ const state = await withRetry(async () => await board.fetch(), signal);
83
83
  const worktreeEntries = worktrees.list(config);
84
84
  const tickArguments = {
85
85
  state,
@@ -147,6 +147,10 @@ async function runWatchLoop(tick, config) {
147
147
  if (error instanceof WatchLoopShutdownError) {
148
148
  break;
149
149
  }
150
+ /* v8 ignore next 2 @preserve -- fetch() warns-and-skips since PR#88; guard is a defensive no-op in practice */
151
+ if (error instanceof RepositoryResolutionError) {
152
+ throw error;
153
+ }
150
154
  const message = errorMessage(error);
151
155
  if (message.includes("Signal: SIGINT")) {
152
156
  if (!shutdown.signal.aborted) {
@@ -1 +1 @@
1
- {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;CAChB;AA6HD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA4Df;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
1
+ {"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;CAChB;AA6HD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA4Df;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
@@ -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"}