@clipboard-health/groundcrew 4.21.0 → 4.22.0

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.
@@ -2,16 +2,17 @@ import type { Config } from "@clipboard-health/groundcrew";
2
2
  // import { readFileSync } from "node:fs";
3
3
 
4
4
  export default {
5
- // Groundcrew's built-in Linear adapter is implicit and needs no config:
6
- // it picks up every Linear issue assigned to your API key's viewer that
7
- // carries an `agent-*` label. There is no project / view block. The default
8
- // Linear status names `In Progress` and `In Review` disambiguate Linear's
9
- // `started` workflow states; other statuses fall back to workflow
10
- // `state.type` (`unstarted` → todo, `started` → in progress,
11
- // `completed`/`canceled`/`duplicate` → terminal).
5
+ // Task sources at least one is required; add a `sources` array to get
6
+ // started. Two built-in options:
12
7
  //
13
- // Opt a task in: assign it to yourself and add an `agent-<model>`
14
- // label (e.g. `agent-claude`, `agent-any`).
8
+ // Zero credentials: use a local todo.txt file. No API key needed.
9
+ // sources: [{ kind: "todo-txt" }]
10
+ //
11
+ // Linear: picks up issues assigned to your API key's viewer that carry
12
+ // an `agent-*` label. Requires GROUNDCREW_LINEAR_API_KEY.
13
+ // sources: [{ kind: "linear" }]
14
+ //
15
+ // Running `crew run` without a sources array will print this guidance and exit.
15
16
  workspace: {
16
17
  // Parent directory under which groundcrew clones repositories and (by
17
18
  // default) creates per-task worktrees.
@@ -207,9 +207,11 @@ function writeInitGuidance(destination, options) {
207
207
  writeOutput(" - Set workspace.projectDir and workspace.knownRepositories");
208
208
  }
209
209
  writeCloneGuidance(options);
210
- writeOutput(" - If using Linear, export your API key:");
211
- writeOutput(' export GROUNDCREW_LINEAR_API_KEY="lin_api_..."');
212
- writeOutput(" - In Linear, assign tasks to yourself and add an agent-* label to opt them in");
210
+ writeOutput(" - Add a task source to your config (required):");
211
+ writeOutput(" # Zero credentials — uses a local todo.txt file:");
212
+ writeOutput(' sources: [{ kind: "todo-txt" }]');
213
+ writeOutput(" # Or use Linear (requires GROUNDCREW_LINEAR_API_KEY):");
214
+ writeOutput(' sources: [{ kind: "linear" }]');
213
215
  writeOutput(" - Validate and start:");
214
216
  writeOutput(" crew doctor");
215
217
  writeOutput(" crew run --watch");
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2DH,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,CAgD7E"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2DH,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,CAgE7E"}
@@ -6,11 +6,11 @@
6
6
  */
7
7
  import { createBoard } from "../lib/board.js";
8
8
  import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
9
- import { loadConfig } from "../lib/config.js";
9
+ import { loadConfigWithSource } from "../lib/config.js";
10
10
  import { findPullRequestsForBranch } from "../lib/pullRequests.js";
11
11
  import { RepositoryResolutionError } from "../lib/taskSource.js";
12
12
  import { getUsageByModel } from "../lib/usage.js";
13
- import { errorMessage, log, sleep } from "../lib/util.js";
13
+ import { errorMessage, log, sleep, writeOutput } from "../lib/util.js";
14
14
  import { worktrees } from "../lib/worktrees.js";
15
15
  import { createCleaner } from "./cleaner.js";
16
16
  import { createDispatcher } from "./dispatcher.js";
@@ -66,11 +66,24 @@ async function fetchUsageOrEmpty(config, signal) {
66
66
  }
67
67
  }
68
68
  export async function orchestrate(options) {
69
- const config = await loadConfig();
70
- // Build all sources (Linear implicit + any user-declared shell adapters).
71
- // sourcesFromConfig synthesizes the implicit linear source from the config;
72
- // this ensures the Linear adapter's markInProgress is reachable via board.
73
- const allSources = await buildSources(sourcesFromConfig(config), { globalConfig: config });
69
+ const { config, source: configSource } = await loadConfigWithSource();
70
+ const rawSources = sourcesFromConfig(config);
71
+ if (rawSources.length === 0) {
72
+ writeOutput([
73
+ "No task sources configured. Add a sources array to your config:",
74
+ "",
75
+ ` Path: ${configSource.filepath}`,
76
+ "",
77
+ " # Zero credentials — uses a local todo.txt file:",
78
+ ' sources: [{ kind: "todo-txt" }]',
79
+ "",
80
+ " # Or use Linear (requires GROUNDCREW_LINEAR_API_KEY):",
81
+ ' sources: [{ kind: "linear" }]',
82
+ ].join("\n"));
83
+ process.exitCode = 1;
84
+ return;
85
+ }
86
+ const allSources = await buildSources(rawSources, { globalConfig: config });
74
87
  const board = createBoard(allSources);
75
88
  await board.verify();
76
89
  const cleaner = createCleaner({ config });
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcnE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAoqBD,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;AAcnE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAkqBD,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"}
@@ -434,10 +434,8 @@ async function buildBoardForStatus(config) {
434
434
  }
435
435
  /**
436
436
  * Single board fetch used by both the slot count header and the
437
- * Queue/Blocked sections. `sourcesFromConfig` prepends an implicit Linear
438
- * source when none are configured, so we always attempt; failures
439
- * (e.g., missing API key) are captured and rendered later as
440
- * `unavailable: ...` in the Queue section.
437
+ * Queue/Blocked sections. Failures (e.g., missing API key) are captured
438
+ * and rendered later as `unavailable: ...` in the Queue section.
441
439
  */
442
440
  async function fetchBoardForStatus(config) {
443
441
  try {
@@ -22,27 +22,16 @@ export declare function buildSources(rawConfigs: readonly unknown[], context: Ad
22
22
  */
23
23
  export declare function buildSourcesWith(registry: Record<string, AdapterDefinition>, rawConfigs: readonly unknown[], context: AdapterContext): TaskSource[];
24
24
  /**
25
- * Build the runtime source list from a ResolvedConfig: synthesizes the
26
- * implicit Linear source (Linear is always active under the post-#110
27
- * model viewer + agent-* label filtering happens at the GraphQL layer)
28
- * and appends any user-declared `sources`. The implicit source is omitted
29
- * when the user already declared a Linear source — by `kind: "linear"`, or by
30
- * a surviving (non-disabled) source whose runtime name is "linear" — so they
31
- * can override its `name` / construction without spawning a duplicate adapter.
32
- *
33
- * Users opt out of Linear entirely with the sentinel
34
- * `{ kind: "linear", enabled: false }`: it still counts as an explicit Linear
35
- * declaration (so the implicit source is suppressed) and is itself filtered
36
- * out (so no Linear adapter is constructed and no API key is required). Any
37
- * other source with `enabled: false` is likewise dropped from the result.
25
+ * Build the runtime source list from a ResolvedConfig: returns the enabled
26
+ * entries from `config.sources`. Any source with `enabled: false` is dropped.
27
+ * Returns an empty array when no sources are configured callers are
28
+ * responsible for detecting that state and guiding the user.
38
29
  */
39
30
  export declare function sourcesFromConfig(config: ResolvedConfig): readonly unknown[];
40
31
  /**
41
- * True when the resolved config keeps Linear active i.e. the user has not
42
- * opted out with `{ kind: "linear", enabled: false }`. Callers use this to skip
43
- * Linear API calls (and the missing-API-key error they raise) when Linear is
44
- * off. Derived from `sourcesFromConfig` so it honors both the explicit opt-out
45
- * sentinel and the implicit-source synthesis.
32
+ * True when an enabled source explicitly declares `kind: "linear"`. Callers
33
+ * use this to skip Linear API calls (and the missing-API-key error they raise)
34
+ * when Linear is not configured.
46
35
  */
47
36
  export declare function isLinearEnabled(config: ResolvedConfig): boolean;
48
37
  //# sourceMappingURL=buildSources.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildSources.d.ts","sourceRoot":"","sources":["../../src/lib/buildSources.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,eAAO,MAAM,SAAS;;iBAAiC,CAAC;AAExD;;GAEG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC3C,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,UAAU,EAAE,CAcd;AA6DD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,OAAO,EAAE,CAa5E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAE/D"}
1
+ {"version":3,"file":"buildSources.d.ts","sourceRoot":"","sources":["../../src/lib/buildSources.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,eAAO,MAAM,SAAS;;iBAAiC,CAAC;AAExD;;GAEG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC3C,UAAU,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,EAAE,cAAc,GACtB,UAAU,EAAE,CAcd;AA0CD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,OAAO,EAAE,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAE/D"}
@@ -63,63 +63,24 @@ function isSourceDisabled(raw) {
63
63
  }
64
64
  /**
65
65
  * True when `raw` declares `kind: "linear"`, regardless of `name` or `enabled`.
66
- * This is what lets the `{ kind: "linear", enabled: false }` opt-out suppress
67
- * the implicit Linear source even though the entry itself is filtered out.
66
+ * Used by `isLinearEnabled` to detect explicitly configured Linear sources.
68
67
  */
69
68
  function isLinearKindSource(raw) {
70
69
  return sourceFields(raw).kind === "linear";
71
70
  }
72
71
  /**
73
- * True when `raw` is an explicitly-declared Linear source. Matches either a
74
- * `kind: "linear"` entry regardless of any `name` override — or any entry
75
- * whose resolved runtime name (explicit `name`, else `kind`) is "linear".
76
- * The latter catches a non-Linear adapter the user named "linear", which
77
- * would otherwise collide with the implicit Linear source.
78
- *
79
- * Used to suppress the synthesized implicit Linear source so a renamed Linear
80
- * entry like `{ kind: "linear", name: "custom" }` doesn't spawn a duplicate
81
- * adapter pointed at the same viewer. Returns false for malformed entries
82
- * (no `kind`/`name`) — those get rejected by the per-adapter Zod schema
83
- * downstream.
84
- */
85
- function isExplicitLinearSource(raw) {
86
- const { kind, name } = sourceFields(raw);
87
- return kind === "linear" || (name ?? kind) === "linear";
88
- }
89
- /**
90
- * Build the runtime source list from a ResolvedConfig: synthesizes the
91
- * implicit Linear source (Linear is always active under the post-#110
92
- * model — viewer + agent-* label filtering happens at the GraphQL layer)
93
- * and appends any user-declared `sources`. The implicit source is omitted
94
- * when the user already declared a Linear source — by `kind: "linear"`, or by
95
- * a surviving (non-disabled) source whose runtime name is "linear" — so they
96
- * can override its `name` / construction without spawning a duplicate adapter.
97
- *
98
- * Users opt out of Linear entirely with the sentinel
99
- * `{ kind: "linear", enabled: false }`: it still counts as an explicit Linear
100
- * declaration (so the implicit source is suppressed) and is itself filtered
101
- * out (so no Linear adapter is constructed and no API key is required). Any
102
- * other source with `enabled: false` is likewise dropped from the result.
72
+ * Build the runtime source list from a ResolvedConfig: returns the enabled
73
+ * entries from `config.sources`. Any source with `enabled: false` is dropped.
74
+ * Returns an empty array when no sources are configured — callers are
75
+ * responsible for detecting that state and guiding the user.
103
76
  */
104
77
  export function sourcesFromConfig(config) {
105
- const kept = config.sources.filter((source) => !isSourceDisabled(source));
106
- // A `kind: "linear"` entry suppresses the implicit source even when it is the
107
- // disabled opt-out sentinel — it's removed from `kept` above, leaving Linear
108
- // off entirely. A source that's Linear only by *name* (e.g. a shell source
109
- // named "linear") suppresses the implicit source only while it survives the
110
- // filter, so disabling such an entry doesn't silently drop Linear.
111
- const hasExplicitLinear = config.sources.some(isLinearKindSource) || kept.some(isExplicitLinearSource);
112
- if (hasExplicitLinear) {
113
- return kept;
114
- }
115
- return [{ kind: "linear" }, ...kept];
78
+ return config.sources.filter((source) => !isSourceDisabled(source));
116
79
  }
117
80
  /**
118
- * True when the resolved config keeps Linear active i.e. the user has not
119
- * opted out with `{ kind: "linear", enabled: false }`. Callers use this to skip
120
- * Linear API calls (and the missing-API-key error they raise) when Linear is
121
- * off. Derived from `sourcesFromConfig` so it honors both the explicit opt-out
122
- * sentinel and the implicit-source synthesis.
81
+ * True when an enabled source explicitly declares `kind: "linear"`. Callers
82
+ * use this to skip Linear API calls (and the missing-API-key error they raise)
83
+ * when Linear is not configured.
123
84
  */
124
85
  export function isLinearEnabled(config) {
125
86
  return sourcesFromConfig(config).some(isLinearKindSource);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.21.0",
3
+ "version": "4.22.0",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",