@clipboard-health/groundcrew 3.1.3 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -15
- package/crew.config.example.ts +25 -10
- package/dist/commands/cleaner.d.ts.map +1 -1
- package/dist/commands/cleaner.js +4 -4
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +6 -6
- package/dist/commands/eligibility.d.ts.map +1 -1
- package/dist/commands/eligibility.js +2 -2
- package/dist/commands/orchestrator.d.ts +4 -2
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +6 -105
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +1 -0
- package/dist/commands/ticketDoctor.d.ts.map +1 -1
- package/dist/commands/ticketDoctor.js +28 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/lib/boardSource.d.ts +41 -5
- package/dist/lib/boardSource.d.ts.map +1 -1
- package/dist/lib/boardSource.js +211 -70
- package/dist/lib/config.d.ts +59 -25
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +130 -22
- package/dist/lib/linearIssueStatus.d.ts +3 -1
- package/dist/lib/linearIssueStatus.d.ts.map +1 -1
- package/dist/lib/linearIssueStatus.js +0 -0
- package/dist/lib/util.d.ts +0 -1
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +0 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ Installs the `crew` binary. `@clipboard-health/clearance` is pulled in transitiv
|
|
|
71
71
|
|
|
72
72
|
Or drop `crew.config.ts` at the root of any repo you run `crew` from — `crew` discovers it via cosmiconfig project-walk. Any of `crew.config.{ts,mjs,js,json}`, `.crewrc{,.json,.ts}`, `.config/crew.config.{ts,json}`, or `.config/crewrc{,.json}` work.
|
|
73
73
|
|
|
74
|
-
Set `linear.projectSlug` (paste the trailing slug of your Linear project URL, e.g. `ai-strategy-5152195762f3`), `workspace.projectDir`, and `workspace.knownRepositories`. Defaults cover everything else.
|
|
74
|
+
Set `linear.projects[].projectSlug` (paste the trailing slug of your Linear project URL, e.g. `ai-strategy-5152195762f3`), `workspace.projectDir`, and `workspace.knownRepositories`. Defaults cover everything else. To watch multiple projects from one `crew` instance, add more entries to `linear.projects`; they all share the same `orchestrator.maximumInProgress` budget.
|
|
75
75
|
|
|
76
76
|
Then clone each repo before the first `crew run` — groundcrew creates per-ticket worktrees from these clones, it does not auto-clone:
|
|
77
77
|
|
|
@@ -163,11 +163,11 @@ Watch `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.log` for `DENY` lines
|
|
|
163
163
|
|
|
164
164
|
Three keys are required; everything else has a default.
|
|
165
165
|
|
|
166
|
-
| Key
|
|
167
|
-
|
|
|
168
|
-
| `linear.projectSlug`
|
|
169
|
-
| `workspace.projectDir`
|
|
170
|
-
| `workspace.knownRepositories`
|
|
166
|
+
| Key | What |
|
|
167
|
+
| ------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
168
|
+
| `linear.projects[].projectSlug` | Trailing slug of each Linear project URL to watch (e.g. `ai-strategy-5152195762f3`). One or more entries. |
|
|
169
|
+
| `workspace.projectDir` | Parent dir for cloned repos and sibling ticket worktrees. |
|
|
170
|
+
| `workspace.knownRepositories` | Repos searched for in ticket descriptions to infer where work belongs. |
|
|
171
171
|
|
|
172
172
|
`crew` resolves config as: `GROUNDCREW_CONFIG` if set → project-walk from cwd (cosmiconfig: `crew.config.{ts,mjs,js,json}`, `.crewrc{,.json,.ts}`, `.config/crew.config.{ts,json}`, `.config/crewrc{,.json}`) → `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts` (also accepts legacy `config.ts` for one release). The branch prefix (`<prefix>-<TICKET>`) is derived from `os.userInfo().username` — not configurable.
|
|
173
173
|
|
|
@@ -197,16 +197,17 @@ This keeps package defaults portable while letting your private config reference
|
|
|
197
197
|
|
|
198
198
|
| Key | Default | What it does |
|
|
199
199
|
| --------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
200
|
-
| `linear.
|
|
201
|
-
| `linear.
|
|
202
|
-
| `linear.statuses.
|
|
203
|
-
| `linear.statuses.
|
|
204
|
-
| `linear.statuses.
|
|
200
|
+
| `linear.projects` | **required** | Non-empty array of Linear projects to watch. One `crew` instance dispatches across every entry under a shared `maximumInProgress` budget. |
|
|
201
|
+
| `linear.projects[].projectSlug` | **required** | Linear project URL slug (e.g. `ai-strategy-5152195762f3`). The trailing 12-char hex `slugId` is what's matched against Linear's API; the leading name keeps `crew.config.ts` self-documenting and the lookup survives project renames. |
|
|
202
|
+
| `linear.projects[].statuses.todo` | `"Todo"` | Status name picked up for new work in this project. Per-project so multi-team setups with divergent state names can coexist. |
|
|
203
|
+
| `linear.projects[].statuses.inProgress` | `"In Progress"` | Status set after a workspace is provisioned; counts toward the shared `maximumInProgress`. |
|
|
204
|
+
| `linear.projects[].statuses.done` | `"Done"` | Status that triggers worktree cleanup for this project. |
|
|
205
|
+
| `linear.projects[].statuses.terminal` | `["Done"]` | Additional status names treated as terminal for cleanup and blocker checks. The project's `done` status is always included. |
|
|
205
206
|
| `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
|
|
206
207
|
| `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
|
|
207
208
|
| `workspace.projectDir` | **required** | Parent dir for cloned repos and sibling ticket worktrees. |
|
|
208
209
|
| `workspace.knownRepositories` | **required** | Repos searched for in ticket descriptions to infer where work belongs. A ticket labeled for groundcrew (`agent-*`) fails fast when no known repo appears; unlabeled tickets are ignored. |
|
|
209
|
-
| `orchestrator.maximumInProgress` | `4` | Cap on tickets in `linear.
|
|
210
|
+
| `orchestrator.maximumInProgress` | `4` | Cap on in-progress tickets at once, shared across every project in `linear.projects`. |
|
|
210
211
|
| `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
|
|
211
212
|
| `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. |
|
|
212
213
|
| `models.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew run --ticket <TICKET>` for unlabeled tickets. `crew run` without `--ticket` ignores unlabeled tickets and does not apply this default. Must exist in `models.definitions`. |
|
|
@@ -443,7 +444,7 @@ When a wrapped agent command fails (e.g. `safehouse-clearance` not found, `npm i
|
|
|
443
444
|
<details>
|
|
444
445
|
<summary>Status names matter</summary>
|
|
445
446
|
|
|
446
|
-
If your team uses `Started` instead of `In Progress`, set `linear.statuses.inProgress = "Started"
|
|
447
|
+
If your team uses `Started` instead of `In Progress`, set `linear.projects[].statuses.inProgress = "Started"` on that project's entry. Status overrides are per-project so divergent team workflows coexist.
|
|
447
448
|
|
|
448
449
|
</details>
|
|
449
450
|
|
|
@@ -462,9 +463,9 @@ Groundcrew sets a ticket to `inProgress` when it provisions a workspace and neve
|
|
|
462
463
|
</details>
|
|
463
464
|
|
|
464
465
|
<details>
|
|
465
|
-
<summary>
|
|
466
|
+
<summary>Cross-team projects need a consistent `inProgress` name per project</summary>
|
|
466
467
|
|
|
467
|
-
Cross-team projects work — the orchestrator caches the in-progress state ID per team — but every team in
|
|
468
|
+
Cross-team projects work — the orchestrator caches the in-progress state ID per `(team, statusName)` pair — but every team in a given project must use the same status name for that project's `statuses.inProgress`. If you watch two projects that share a Linear team but configure different `inProgress` names, each project's lookup is independent.
|
|
468
469
|
|
|
469
470
|
</details>
|
|
470
471
|
|
package/crew.config.example.ts
CHANGED
|
@@ -3,16 +3,30 @@ import type { Config } from "./src/lib/config.js";
|
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
linear: {
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
//
|
|
6
|
+
// One or more Linear projects to watch. A single `crew` process
|
|
7
|
+
// dispatches across all configured projects under a shared
|
|
8
|
+
// `orchestrator.maximumInProgress` budget.
|
|
9
|
+
//
|
|
10
|
+
// Each entry's `projectSlug` is the trailing segment of your Linear
|
|
11
|
+
// project URL — copy it verbatim, e.g. "ai-strategy-5152195762f3"
|
|
12
|
+
// from "https://linear.app/<workspace>/project/ai-strategy-5152195762f3".
|
|
13
|
+
// The 12-char hex tail is the canonical ID groundcrew uses, so the
|
|
14
|
+
// orchestrator stays resilient across project renames and across
|
|
15
|
+
// same-name projects in different teams. The leading name segment
|
|
16
|
+
// keeps the file self-documenting at a glance.
|
|
17
|
+
//
|
|
18
|
+
// `statuses` is per-project so multi-team setups with divergent
|
|
19
|
+
// workflow state names (e.g. "Todo" vs "To Do", "Shipped" vs
|
|
20
|
+
// "Done") can coexist. Each field falls back to its default when
|
|
21
|
+
// omitted: { todo: "Todo", inProgress: "In Progress",
|
|
22
|
+
// done: "Done", terminal: ["Done"] }.
|
|
23
|
+
projects: [
|
|
24
|
+
{ projectSlug: "your-project-name-0123456789ab" },
|
|
25
|
+
// {
|
|
26
|
+
// projectSlug: "platform-aaaaaaaaaaaa",
|
|
27
|
+
// statuses: { inProgress: "Doing", done: "Released", terminal: ["Released", "Won't Do"] },
|
|
28
|
+
// },
|
|
29
|
+
],
|
|
16
30
|
},
|
|
17
31
|
workspace: {
|
|
18
32
|
// Parent directory under which groundcrew clones repositories and
|
|
@@ -29,6 +43,7 @@ export default {
|
|
|
29
43
|
// git: { remote: "origin", defaultBranch: "main" },
|
|
30
44
|
//
|
|
31
45
|
// orchestrator: {
|
|
46
|
+
// // Shared across all watched projects in linear.projects.
|
|
32
47
|
// maximumInProgress: 4,
|
|
33
48
|
// pollIntervalMilliseconds: 120_000,
|
|
34
49
|
// sessionLimitPercentage: 85,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleaner.d.ts","sourceRoot":"","sources":["../../src/commands/cleaner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,UAAU,
|
|
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,CAsDxD"}
|
package/dist/commands/cleaner.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* tickets that have reached a terminal status. One per `orchestrate()`
|
|
4
4
|
* invocation; stateless across iterations. Mirrors `Dispatcher`.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { isTerminalStatusForIssue } from "../lib/boardSource.js";
|
|
7
7
|
import { recordCleanedUpRuns } from "../lib/runStateCleanup.js";
|
|
8
8
|
import { log, logEvent } from "../lib/util.js";
|
|
9
9
|
import { worktrees } from "../lib/worktrees.js";
|
|
@@ -12,10 +12,10 @@ export function createCleaner(deps) {
|
|
|
12
12
|
const { config } = deps;
|
|
13
13
|
async function runOnce(arguments_) {
|
|
14
14
|
const { state, worktreeEntries, dryRun, signal } = arguments_;
|
|
15
|
-
// Only act on tickets in
|
|
16
|
-
// like a Linear ticket from another project, leave it alone.
|
|
15
|
+
// Only act on tickets in configured projects — if the dir name happens to
|
|
16
|
+
// look like a Linear ticket from another project, leave it alone.
|
|
17
17
|
const terminalTickets = new Set(state.issues
|
|
18
|
-
.filter((issue) =>
|
|
18
|
+
.filter((issue) => isTerminalStatusForIssue(issue, config))
|
|
19
19
|
.map((issue) => issue.id));
|
|
20
20
|
if (terminalTickets.size === 0) {
|
|
21
21
|
return;
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,UAAU,EAAE;QAClB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,+FAA+F;QAC/F,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAuLjE"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Pure verdict logic lives in `eligibility.ts`; this module is responsible
|
|
7
7
|
* for telemetry, Linear writes, and side-effecting setupWorkspace calls.
|
|
8
8
|
*/
|
|
9
|
-
import { isGroundcrewIssue } from "../lib/boardSource.js";
|
|
9
|
+
import { isGroundcrewIssue, projectFor, } from "../lib/boardSource.js";
|
|
10
10
|
import { createLinearIssueStatusUpdater } from "../lib/linearIssueStatus.js";
|
|
11
11
|
import { errorMessage, log, logEvent } from "../lib/util.js";
|
|
12
12
|
import { workspaces } from "../lib/workspaces.js";
|
|
@@ -87,17 +87,17 @@ export function createDispatcher(deps) {
|
|
|
87
87
|
async function runOnce(arguments_) {
|
|
88
88
|
const { state, worktreeEntries, usage, dryRun, signal } = arguments_;
|
|
89
89
|
issueStatusUpdater.resetMissingInProgressCache();
|
|
90
|
-
const activeCount = state.issues.filter((issue) => issue.status === config.
|
|
90
|
+
const activeCount = state.issues.filter((issue) => issue.status === projectFor(issue, config).statuses.inProgress).length;
|
|
91
91
|
const slots = config.orchestrator.maximumInProgress - activeCount;
|
|
92
92
|
// Narrow Todo to tickets that opted in via an `agent-*` label.
|
|
93
93
|
// Unlabeled tickets are not groundcrew's concern even when in Todo.
|
|
94
|
-
const todo = state.issues.filter((issue) => issue.status === config.
|
|
94
|
+
const todo = state.issues.filter((issue) => issue.status === projectFor(issue, config).statuses.todo && isGroundcrewIssue(issue));
|
|
95
95
|
if (slots <= 0) {
|
|
96
96
|
log(`At capacity (${activeCount}/${config.orchestrator.maximumInProgress}), no new work to start`);
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
if (todo.length === 0) {
|
|
100
|
-
log(`No
|
|
100
|
+
log(`No Todo tickets to pick up`);
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
// Run the blocker pre-pass first so an all-blocked board short-circuits
|
|
@@ -107,7 +107,7 @@ export function createDispatcher(deps) {
|
|
|
107
107
|
logSkip(skip);
|
|
108
108
|
}
|
|
109
109
|
if (unblocked.length === 0) {
|
|
110
|
-
log(`No eligible
|
|
110
|
+
log(`No eligible Todo tickets after blocker filtering`);
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
// usage() is an HTTP call; workspaces.probe shells tmux/cmux. Kick off
|
|
@@ -146,7 +146,7 @@ export function createDispatcher(deps) {
|
|
|
146
146
|
logSkip(skip);
|
|
147
147
|
}
|
|
148
148
|
if (starts.length === 0) {
|
|
149
|
-
log(`No eligible
|
|
149
|
+
log(`No eligible Todo tickets after eligibility filtering`);
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
152
|
log(`${slots} slot(s) available, starting ${starts.length} ticket(s): ${starts.map(({ issue }) => `${issue.id}(${issue.model})`).join(", ")}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eligibility.d.ts","sourceRoot":"","sources":["../../src/commands/eligibility.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,
|
|
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;AAqCD;;;;;;;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,CAC9B,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,SAAS,eAAe,EAAE,GAC/B,qBAAqB,CAYvB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,EAAE,CAgE5E"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* The Dispatcher consumes the verdict list to drive logging and side
|
|
7
7
|
* effects.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { isTerminalStatusForBlocker, } from "../lib/boardSource.js";
|
|
10
10
|
import { AGENT_ANY_MODEL } from "../lib/config.js";
|
|
11
11
|
const PERCENT_FRACTION_DIVISOR = 100;
|
|
12
12
|
const DAYS_PER_WEEK = 7;
|
|
@@ -26,7 +26,7 @@ function blockerVerdictFor(issue, config) {
|
|
|
26
26
|
blockers,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
const unresolved = issue.blockers.filter((blocker) =>
|
|
29
|
+
const unresolved = issue.blockers.filter((blocker) => !isTerminalStatusForBlocker(blocker, config));
|
|
30
30
|
if (unresolved.length === 0) {
|
|
31
31
|
return undefined;
|
|
32
32
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* groundcrew orchestrator — polls
|
|
3
|
-
*
|
|
2
|
+
* groundcrew orchestrator — polls Linear projects and spins up workspace +
|
|
3
|
+
* git-worktree pairs for ready tickets. Each tick fetches the board, runs
|
|
4
|
+
* the cleaner, and runs the dispatcher; logging from those modules is the
|
|
5
|
+
* orchestrator's user-facing output.
|
|
4
6
|
*/
|
|
5
7
|
export interface OrchestratorOptions {
|
|
6
8
|
watch: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/commands/orchestrator.ts"],"names":[],"mappings":"AAAA
|
|
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,CA6B7E"}
|
|
@@ -1,36 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* groundcrew orchestrator — polls
|
|
3
|
-
*
|
|
2
|
+
* groundcrew orchestrator — polls Linear projects and spins up workspace +
|
|
3
|
+
* git-worktree pairs for ready tickets. Each tick fetches the board, runs
|
|
4
|
+
* the cleaner, and runs the dispatcher; logging from those modules is the
|
|
5
|
+
* orchestrator's user-facing output.
|
|
4
6
|
*/
|
|
5
|
-
import { createBoardSource,
|
|
7
|
+
import { createBoardSource, RepositoryResolutionError, } from "../lib/boardSource.js";
|
|
6
8
|
import { loadConfig } from "../lib/config.js";
|
|
7
9
|
import { getUsageByModel } from "../lib/usage.js";
|
|
8
|
-
import {
|
|
10
|
+
import { errorMessage, getLinearClient, log, sleep } from "../lib/util.js";
|
|
9
11
|
import { worktrees } from "../lib/worktrees.js";
|
|
10
12
|
import { createCleaner } from "./cleaner.js";
|
|
11
13
|
import { createDispatcher } from "./dispatcher.js";
|
|
12
14
|
const RATE_LIMIT_DELAY_MS = 60_000;
|
|
13
15
|
const RETRY_BASE_DELAY_MS = 1000;
|
|
14
16
|
const RETRY_MAX_ATTEMPTS = 3;
|
|
15
|
-
const STATUS_CARD_TITLE_WIDTH = 42;
|
|
16
|
-
const STATUS_CARD_ID_WIDTH = 8;
|
|
17
|
-
const STATUS_CARD_LIMIT = 10;
|
|
18
|
-
const HEADER_BAR_WIDTH = 70;
|
|
19
|
-
const SECTION_BAR_WIDTH = 50;
|
|
20
17
|
const MS_PER_SECOND = 1000;
|
|
21
|
-
const STATUS_ICON_DEFAULT = " ";
|
|
22
|
-
function statusIconFor(status, config) {
|
|
23
|
-
if (status === config.linear.statuses.inProgress) {
|
|
24
|
-
return ">>";
|
|
25
|
-
}
|
|
26
|
-
if (status === config.linear.statuses.todo) {
|
|
27
|
-
return "--";
|
|
28
|
-
}
|
|
29
|
-
if (isTerminalStatus(status, config)) {
|
|
30
|
-
return "ok";
|
|
31
|
-
}
|
|
32
|
-
return STATUS_ICON_DEFAULT;
|
|
33
|
-
}
|
|
34
18
|
async function withRetry(function_, signal, maxRetries = RETRY_MAX_ATTEMPTS, baseDelayMs = RETRY_BASE_DELAY_MS) {
|
|
35
19
|
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
36
20
|
try {
|
|
@@ -64,86 +48,6 @@ class WatchLoopShutdownError extends Error {
|
|
|
64
48
|
this.name = "WatchLoopShutdownError";
|
|
65
49
|
}
|
|
66
50
|
}
|
|
67
|
-
function groupByStatus(issues, knownOrder) {
|
|
68
|
-
const groups = new Map();
|
|
69
|
-
for (const status of knownOrder) {
|
|
70
|
-
groups.set(status, []);
|
|
71
|
-
}
|
|
72
|
-
for (const issue of issues) {
|
|
73
|
-
/* v8 ignore next @preserve -- knownOrder seeds an entry for each issue.status returned by buildStatusOrder */
|
|
74
|
-
const group = groups.get(issue.status) ?? [];
|
|
75
|
-
group.push(issue);
|
|
76
|
-
groups.set(issue.status, group);
|
|
77
|
-
}
|
|
78
|
-
return groups;
|
|
79
|
-
}
|
|
80
|
-
function buildStatusOrder(state, config) {
|
|
81
|
-
const head = [
|
|
82
|
-
...new Set([
|
|
83
|
-
config.linear.statuses.inProgress,
|
|
84
|
-
config.linear.statuses.todo,
|
|
85
|
-
config.linear.statuses.done,
|
|
86
|
-
...config.linear.statuses.terminal,
|
|
87
|
-
]),
|
|
88
|
-
];
|
|
89
|
-
const seen = new Set(head);
|
|
90
|
-
const tail = [];
|
|
91
|
-
for (const issue of state.issues) {
|
|
92
|
-
if (!seen.has(issue.status)) {
|
|
93
|
-
seen.add(issue.status);
|
|
94
|
-
tail.push(issue.status);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return [...head, ...tail];
|
|
98
|
-
}
|
|
99
|
-
function render(state, config, previous) {
|
|
100
|
-
const order = buildStatusOrder(state, config);
|
|
101
|
-
const grouped = groupByStatus(state.issues, order);
|
|
102
|
-
const previousGrouped = previous ? groupByStatus(previous.issues, order) : undefined;
|
|
103
|
-
const previousById = previous
|
|
104
|
-
? new Map(previous.issues.map((issue) => [issue.id, issue]))
|
|
105
|
-
: undefined;
|
|
106
|
-
clearOutput();
|
|
107
|
-
writeOutput(`groundcrew — ${config.linear.projectSlug} — ${new Date(state.timestamp).toLocaleTimeString()}`);
|
|
108
|
-
writeOutput(`Max in progress: ${config.orchestrator.maximumInProgress}`);
|
|
109
|
-
writeOutput("=".repeat(HEADER_BAR_WIDTH));
|
|
110
|
-
writeOutput();
|
|
111
|
-
for (const [status, issues] of grouped) {
|
|
112
|
-
if (issues.length === 0) {
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
const previousCount = previousGrouped?.get(status)?.length ?? issues.length;
|
|
116
|
-
const delta = issues.length === previousCount
|
|
117
|
-
? ""
|
|
118
|
-
: ` (${issues.length > previousCount ? "+" : ""}${issues.length - previousCount})`;
|
|
119
|
-
writeOutput(`${statusIconFor(status, config)} ${status} (${issues.length})${delta}`);
|
|
120
|
-
writeOutput("-".repeat(SECTION_BAR_WIDTH));
|
|
121
|
-
// Cap each status at the N most recent so a backlog of hundreds of Done
|
|
122
|
-
// tickets doesn't clog the terminal. Sort only when truncating so smaller
|
|
123
|
-
// statuses keep whatever order Linear returned.
|
|
124
|
-
const visible = issues.length > STATUS_CARD_LIMIT
|
|
125
|
-
? issues
|
|
126
|
-
.toSorted((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
|
127
|
-
.slice(0, STATUS_CARD_LIMIT)
|
|
128
|
-
: issues;
|
|
129
|
-
for (const issue of visible) {
|
|
130
|
-
const previousIssue = previousById?.get(issue.id);
|
|
131
|
-
const changed = previousIssue && previousIssue.status !== issue.status
|
|
132
|
-
? ` [was: ${previousIssue.status}]`
|
|
133
|
-
: "";
|
|
134
|
-
writeOutput(` ${issue.id.padEnd(STATUS_CARD_ID_WIDTH)} ${issue.title.slice(0, STATUS_CARD_TITLE_WIDTH).padEnd(STATUS_CARD_TITLE_WIDTH)} ${issue.assignee}${changed}`);
|
|
135
|
-
}
|
|
136
|
-
if (issues.length > STATUS_CARD_LIMIT) {
|
|
137
|
-
writeOutput(` … showing ${STATUS_CARD_LIMIT} most recent of ${issues.length}; ${issues.length - STATUS_CARD_LIMIT} older hidden`);
|
|
138
|
-
}
|
|
139
|
-
writeOutput();
|
|
140
|
-
}
|
|
141
|
-
const total = state.issues.length;
|
|
142
|
-
const done = state.issues.filter((issue) => isTerminalStatus(issue.status, config)).length;
|
|
143
|
-
/* v8 ignore next @preserve -- grouped has all known statuses pre-seeded by groupByStatus */
|
|
144
|
-
const active = grouped.get(config.linear.statuses.inProgress)?.length ?? 0;
|
|
145
|
-
writeOutput(`Total: ${total} | Active: ${active}/${config.orchestrator.maximumInProgress} | Done: ${done} | Remaining: ${total - done}`);
|
|
146
|
-
}
|
|
147
51
|
async function fetchUsageOrEmpty(config, signal) {
|
|
148
52
|
try {
|
|
149
53
|
return await getUsageByModel(config, signal);
|
|
@@ -163,10 +67,8 @@ export async function orchestrate(options) {
|
|
|
163
67
|
await boardSource.verify();
|
|
164
68
|
const cleaner = createCleaner({ config });
|
|
165
69
|
const dispatcher = createDispatcher({ config, client });
|
|
166
|
-
let previous;
|
|
167
70
|
const tick = async (signal) => {
|
|
168
71
|
const state = await withRetry(async () => await boardSource.fetch(), signal);
|
|
169
|
-
render(state, config, previous);
|
|
170
72
|
const worktreeEntries = worktrees.list(config);
|
|
171
73
|
const tickArguments = {
|
|
172
74
|
state,
|
|
@@ -181,7 +83,6 @@ export async function orchestrate(options) {
|
|
|
181
83
|
// an idle board doesn't burn a codexbar shell-out per tick.
|
|
182
84
|
usage: async (usageSignal) => await fetchUsageOrEmpty(config, usageSignal),
|
|
183
85
|
});
|
|
184
|
-
previous = state;
|
|
185
86
|
};
|
|
186
87
|
await (options.watch ? runWatchLoop(tick, config) : tick());
|
|
187
88
|
}
|
|
@@ -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,CA6Gf;AAwHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,
|
|
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,CA6Gf;AAwHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticketDoctor.d.ts","sourceRoot":"","sources":["../../src/commands/ticketDoctor.ts"],"names":[],"mappings":"AAoBA,OAAO,EAML,KAAK,OAAO,EAEZ,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,
|
|
1
|
+
{"version":3,"file":"ticketDoctor.d.ts","sourceRoot":"","sources":["../../src/commands/ticketDoctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ import { existsSync } from "node:fs";
|
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, resolveModelFor, resolveRepositoryFor, } from "../lib/boardSource.js";
|
|
20
20
|
import { runCommandAsync } from "../lib/commandRunner.js";
|
|
21
|
-
import { AGENT_ANY_MODEL, loadConfig } from "../lib/config.js";
|
|
21
|
+
import { AGENT_ANY_MODEL, findProjectBySlugId, loadConfig, } from "../lib/config.js";
|
|
22
22
|
import { which } from "../lib/host.js";
|
|
23
23
|
import { readRunState } from "../lib/runState.js";
|
|
24
24
|
import { getUsageByModel } from "../lib/usage.js";
|
|
@@ -270,6 +270,8 @@ async function runEligibilityChecks(arguments_) {
|
|
|
270
270
|
assignee: "",
|
|
271
271
|
updatedAt: "",
|
|
272
272
|
teamId: raw.teamId,
|
|
273
|
+
/* v8 ignore next @preserve -- probeLinear gates off-config projects upstream so raw.projectSlugId is always defined here */
|
|
274
|
+
projectSlugId: raw.projectSlugId ?? "",
|
|
273
275
|
repository: resolvedRepository,
|
|
274
276
|
model: resolvedModel,
|
|
275
277
|
blockers: [...blockers],
|
|
@@ -372,8 +374,31 @@ async function probeLinear(deps, upperTicket) {
|
|
|
372
374
|
}
|
|
373
375
|
try {
|
|
374
376
|
const raw = await deps.fetchRawIssue({ ticket: upperTicket });
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
+
const project = raw.projectSlugId === undefined
|
|
378
|
+
? undefined
|
|
379
|
+
: findProjectBySlugId(deps.config, raw.projectSlugId);
|
|
380
|
+
if (project === undefined) {
|
|
381
|
+
const configured = deps.config.linear.projects.map((entry) => entry.slugId).join(", ");
|
|
382
|
+
const detail = raw.projectSlugId === undefined
|
|
383
|
+
? `ticket has no associated Linear project; configure linear.projects (${configured})`
|
|
384
|
+
: `project slugId "${raw.projectSlugId}" is not in linear.projects (configured: ${configured})`;
|
|
385
|
+
return {
|
|
386
|
+
linearStatus: { kind: "unresolvable", reason: detail },
|
|
387
|
+
resolution: [
|
|
388
|
+
{ name: "Ticket exists in Linear", status: "ok", detail: `"${raw.title}"` },
|
|
389
|
+
{
|
|
390
|
+
name: "Project is configured",
|
|
391
|
+
status: "fail",
|
|
392
|
+
detail,
|
|
393
|
+
failureSummary: detail,
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
title: raw.title,
|
|
397
|
+
raw,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const isTerminal = project.statuses.terminal.includes(raw.stateName);
|
|
401
|
+
const todoState = project.statuses.todo;
|
|
377
402
|
const resolution = [
|
|
378
403
|
{ name: "Ticket exists in Linear", status: "ok", detail: `"${raw.title}"` },
|
|
379
404
|
];
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,10 @@ export { interruptWorkspace, type InterruptWorkspaceOptions, } from "./commands/
|
|
|
5
5
|
export { orchestrate, type OrchestratorOptions } from "./commands/orchestrator.ts";
|
|
6
6
|
export { resumeWorkspace, type ResumeWorkspaceOptions } from "./commands/resumeWorkspace.ts";
|
|
7
7
|
export { setupWorkspace, type SetupWorkspaceOptions } from "./commands/setupWorkspace.ts";
|
|
8
|
-
export type { Config, ModelDefinition, ResolvedConfig } from "./lib/config.ts";
|
|
9
|
-
export { loadConfig } from "./lib/config.ts";
|
|
8
|
+
export type { Config, ModelDefinition, ProjectConfig, ResolvedConfig, ResolvedProjectConfig, } from "./lib/config.ts";
|
|
9
|
+
export { findProjectBySlugId, loadConfig, unionTerminalStatuses } from "./lib/config.ts";
|
|
10
10
|
export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, type RunLifecycleState, type RunState, } from "./lib/runState.ts";
|
|
11
|
-
export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, resolveModelFor, resolveRepositoryFor, type ModelResolution, type RawLinearIssue, type RepositoryResolution, } from "./lib/boardSource.ts";
|
|
11
|
+
export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isTerminalStatusForBlocker, isTerminalStatusForIssue, projectFor, resolveModelFor, resolveRepositoryFor, UnknownProjectError, type ModelResolution, type RawLinearIssue, type RepositoryResolution, } from "./lib/boardSource.ts";
|
|
12
12
|
export { getUsageByModel, type UsageByModel } from "./lib/usage.ts";
|
|
13
13
|
export type { TicketCheck } from "./commands/ticketCheck.ts";
|
|
14
14
|
export { ticketDoctor, type TicketDoctorDependencies, type TicketDoctorResult, type TicketDoctorVerdict, } from "./commands/ticketDoctor.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,GAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EACV,MAAM,EACN,eAAe,EACf,aAAa,EACb,cAAc,EACd,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,KAAK,iBAAiB,EACtB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EACL,YAAY,EACZ,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ export { interruptWorkspace, } from "./commands/interruptWorkspace.js";
|
|
|
5
5
|
export { orchestrate } from "./commands/orchestrator.js";
|
|
6
6
|
export { resumeWorkspace } from "./commands/resumeWorkspace.js";
|
|
7
7
|
export { setupWorkspace } from "./commands/setupWorkspace.js";
|
|
8
|
-
export { loadConfig } from "./lib/config.js";
|
|
8
|
+
export { findProjectBySlugId, loadConfig, unionTerminalStatuses } from "./lib/config.js";
|
|
9
9
|
export { readRunState, recordRunState, removeRunState, runStateDirectory, runStatePath, updateRunState, } from "./lib/runState.js";
|
|
10
|
-
export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, resolveModelFor, resolveRepositoryFor, } from "./lib/boardSource.js";
|
|
10
|
+
export { fetchBlockersForTicket, fetchInProgressIssueCount, fetchRawLinearIssue, fetchResolvedIssue, isTerminalStatusForBlocker, isTerminalStatusForIssue, projectFor, resolveModelFor, resolveRepositoryFor, UnknownProjectError, } from "./lib/boardSource.js";
|
|
11
11
|
export { getUsageByModel } from "./lib/usage.js";
|
|
12
12
|
export { ticketDoctor, } from "./commands/ticketDoctor.js";
|