@clipboard-health/groundcrew 4.18.2 → 4.20.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.
- package/README.md +22 -22
- package/crew.config.example.ts +5 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +39 -33
- package/dist/commands/cleaner.d.ts +4 -4
- package/dist/commands/cleaner.d.ts.map +1 -1
- package/dist/commands/cleaner.js +7 -7
- package/dist/commands/cleanupWorkspace.d.ts +1 -1
- package/dist/commands/cleanupWorkspace.d.ts.map +1 -1
- package/dist/commands/cleanupWorkspace.js +14 -14
- package/dist/commands/dispatcher.d.ts +4 -4
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +32 -32
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/eligibility.d.ts +2 -2
- package/dist/commands/eligibility.d.ts.map +1 -1
- package/dist/commands/eligibility.js +4 -4
- package/dist/commands/init.js +1 -1
- package/dist/commands/interruptWorkspace.d.ts +1 -1
- package/dist/commands/interruptWorkspace.d.ts.map +1 -1
- package/dist/commands/interruptWorkspace.js +18 -18
- package/dist/commands/orchestrator.d.ts +1 -1
- package/dist/commands/orchestrator.js +2 -2
- package/dist/commands/resumeWorkspace.d.ts +1 -1
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +35 -35
- package/dist/commands/reviewer.d.ts +7 -7
- package/dist/commands/reviewer.d.ts.map +1 -1
- package/dist/commands/reviewer.js +27 -27
- package/dist/commands/setupWorkspace.d.ts +5 -5
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +39 -39
- package/dist/commands/source.d.ts +2 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +126 -0
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +103 -103
- package/dist/commands/teardownReporter.js +10 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/lib/adapterDefinition.d.ts +3 -3
- package/dist/lib/adapterDefinition.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.d.ts +3 -3
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.js +6 -6
- package/dist/lib/adapters/linear/fetch.d.ts +18 -18
- package/dist/lib/adapters/linear/fetch.d.ts.map +1 -1
- package/dist/lib/adapters/linear/fetch.js +20 -20
- package/dist/lib/adapters/linear/index.js +2 -2
- package/dist/lib/adapters/linear/parsing.d.ts +1 -1
- package/dist/lib/adapters/linear/parsing.d.ts.map +1 -1
- package/dist/lib/adapters/linear/parsing.js +4 -4
- package/dist/lib/adapters/linear/statusNames.d.ts +1 -1
- package/dist/lib/adapters/linear/statusNames.d.ts.map +1 -1
- package/dist/lib/adapters/linear/writeback.d.ts +3 -3
- package/dist/lib/adapters/linear/writeback.d.ts.map +1 -1
- package/dist/lib/adapters/linear/writeback.js +1 -1
- package/dist/lib/adapters/shell/factory.d.ts +4 -4
- package/dist/lib/adapters/shell/factory.d.ts.map +1 -1
- package/dist/lib/adapters/shell/factory.js +40 -14
- package/dist/lib/adapters/shell/index.js +2 -2
- package/dist/lib/adapters/shell/invoke.d.ts +2 -2
- package/dist/lib/adapters/shell/invoke.js +3 -3
- package/dist/lib/adapters/shell/schema.d.ts +5 -1
- package/dist/lib/adapters/shell/schema.d.ts.map +1 -1
- package/dist/lib/adapters/shell/schema.js +24 -3
- package/dist/lib/adapters/todo-txt/index.d.ts +5 -0
- package/dist/lib/adapters/todo-txt/index.d.ts.map +1 -0
- package/dist/lib/adapters/todo-txt/index.js +8 -0
- package/dist/lib/adapters/todo-txt/normalizer.d.ts +22 -0
- package/dist/lib/adapters/todo-txt/normalizer.d.ts.map +1 -0
- package/dist/lib/adapters/todo-txt/normalizer.js +104 -0
- package/dist/lib/adapters/todo-txt/parser.d.ts +20 -0
- package/dist/lib/adapters/todo-txt/parser.d.ts.map +1 -0
- package/dist/lib/adapters/todo-txt/parser.js +93 -0
- package/dist/lib/adapters/todo-txt/schema.d.ts +12 -0
- package/dist/lib/adapters/todo-txt/schema.d.ts.map +1 -0
- package/dist/lib/adapters/todo-txt/schema.js +13 -0
- package/dist/lib/adapters/todo-txt/source.d.ts +5 -0
- package/dist/lib/adapters/todo-txt/source.d.ts.map +1 -0
- package/dist/lib/adapters/todo-txt/source.js +142 -0
- package/dist/lib/adapters/todo-txt/writeback.d.ts +18 -0
- package/dist/lib/adapters/todo-txt/writeback.d.ts.map +1 -0
- package/dist/lib/adapters/todo-txt/writeback.js +352 -0
- package/dist/lib/agentLaunch.js +1 -1
- package/dist/lib/board.d.ts +13 -13
- package/dist/lib/board.d.ts.map +1 -1
- package/dist/lib/board.js +5 -5
- package/dist/lib/buildSources.d.ts +8 -4
- package/dist/lib/buildSources.d.ts.map +1 -1
- package/dist/lib/buildSources.js +2 -2
- package/dist/lib/config.d.ts +8 -7
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +8 -8
- package/dist/lib/launchCommand.js +1 -1
- package/dist/lib/repositoryValidation.d.ts +2 -2
- package/dist/lib/repositoryValidation.d.ts.map +1 -1
- package/dist/lib/repositoryValidation.js +1 -1
- package/dist/lib/runState.d.ts +11 -11
- package/dist/lib/runState.d.ts.map +1 -1
- package/dist/lib/runState.js +19 -19
- package/dist/lib/runStateCleanup.js +2 -2
- package/dist/lib/sourceCapabilities.d.ts +17 -0
- package/dist/lib/sourceCapabilities.d.ts.map +1 -0
- package/dist/lib/sourceCapabilities.js +63 -0
- package/dist/lib/srtLaunch.d.ts +1 -1
- package/dist/lib/srtLaunch.d.ts.map +1 -1
- package/dist/lib/srtLaunch.js +1 -1
- package/dist/lib/srtPolicy.js +1 -1
- package/dist/lib/stagedLaunch.d.ts +3 -3
- package/dist/lib/stagedLaunch.d.ts.map +1 -1
- package/dist/lib/stagedLaunch.js +3 -3
- package/dist/lib/{ticketSource.d.ts → taskSource.d.ts} +30 -30
- package/dist/lib/taskSource.d.ts.map +1 -0
- package/dist/lib/{ticketSource.js → taskSource.js} +9 -9
- package/dist/lib/testing/canonicalFixtures.d.ts +1 -1
- package/dist/lib/testing/canonicalFixtures.d.ts.map +1 -1
- package/dist/lib/testing/canonicalFixtures.js +1 -1
- package/dist/lib/tmuxAdapter.d.ts +1 -1
- package/dist/lib/tmuxAdapter.js +2 -2
- package/dist/lib/util.d.ts +3 -3
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +4 -4
- package/dist/lib/workspaceAdapter.d.ts +8 -8
- package/dist/lib/workspaceAdapter.d.ts.map +1 -1
- package/dist/lib/workspaceAdapter.js +2 -2
- package/dist/lib/workspaces.d.ts +1 -1
- package/dist/lib/workspaces.js +1 -1
- package/dist/lib/worktrees.d.ts +11 -11
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +47 -47
- package/docs/adr/{0002-one-ticket-source-path-linear-is-an-adapter.md → 0002-one-task-source-path-linear-is-an-adapter.md} +3 -3
- package/docs/commands.md +10 -10
- package/docs/configuration.md +19 -19
- package/docs/credentials.md +2 -2
- package/docs/runners.md +1 -1
- package/docs/setup-hook-agent-prompt.md +2 -2
- package/docs/setup-hooks.md +1 -1
- package/docs/{ticket-sources.md → task-sources.md} +9 -9
- package/docs/troubleshooting.md +5 -5
- package/package.json +13 -13
- package/dist/lib/ticketSource.d.ts.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-iteration decider that picks Todo
|
|
2
|
+
* Per-iteration decider that picks Todo tasks to start and acts on the
|
|
3
3
|
* picks. Stateless across iterations. The Board adapter owns its own writeback
|
|
4
4
|
* caches (e.g., Linear's team-state cache lives in `src/lib/adapters/linear/writeback.ts`).
|
|
5
5
|
*
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* for telemetry, writeback via Board, and side-effecting setupWorkspace calls.
|
|
8
8
|
*/
|
|
9
9
|
import { dispatchableRepository } from "../lib/repositoryValidation.js";
|
|
10
|
-
import { isGroundcrewIssue, naturalIdFromCanonical, } from "../lib/
|
|
10
|
+
import { isGroundcrewIssue, naturalIdFromCanonical, } from "../lib/taskSource.js";
|
|
11
11
|
import { errorMessage, failMark, log, logEvent } from "../lib/util.js";
|
|
12
12
|
import { workspaces } from "../lib/workspaces.js";
|
|
13
13
|
import { classifyBlockers, classifyEligibility, classifyUsageExhaustion, } from "./eligibility.js";
|
|
@@ -17,7 +17,7 @@ function logSkip(verdict) {
|
|
|
17
17
|
logEvent("dispatch", {
|
|
18
18
|
outcome: "skipped",
|
|
19
19
|
reason: verdict.eventReason,
|
|
20
|
-
|
|
20
|
+
task: naturalIdFromCanonical(verdict.issue.id),
|
|
21
21
|
blockers: verdict.blockers,
|
|
22
22
|
model: verdict.model,
|
|
23
23
|
});
|
|
@@ -34,18 +34,18 @@ export function createDispatcher(deps) {
|
|
|
34
34
|
}
|
|
35
35
|
async function startEligibleIssue(start, dryRun, signal) {
|
|
36
36
|
const { issue, recovery } = start;
|
|
37
|
-
const
|
|
37
|
+
const taskId = naturalIdFromCanonical(issue.id);
|
|
38
38
|
if (start.resolvedFromAny) {
|
|
39
|
-
log(`Resolved agent-any for ${
|
|
39
|
+
log(`Resolved agent-any for ${taskId} → ${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"} ${
|
|
44
|
+
`[dry-run] Would ${recovery ? "resume" : "start"} ${taskId} in ${issue.repository} (${issue.model})`);
|
|
45
45
|
logEvent("dispatch", {
|
|
46
46
|
outcome: "skipped",
|
|
47
47
|
reason: "dry_run",
|
|
48
|
-
|
|
48
|
+
task: taskId,
|
|
49
49
|
model: issue.model,
|
|
50
50
|
repository: issue.repository,
|
|
51
51
|
});
|
|
@@ -53,12 +53,12 @@ export function createDispatcher(deps) {
|
|
|
53
53
|
}
|
|
54
54
|
try {
|
|
55
55
|
if (recovery) {
|
|
56
|
-
log(`Worktree and workspace already exist for ${
|
|
56
|
+
log(`Worktree and workspace already exist for ${taskId}; resuming with markInProgress`);
|
|
57
57
|
}
|
|
58
58
|
else {
|
|
59
59
|
const setupOptions = {
|
|
60
60
|
repository: issue.repository,
|
|
61
|
-
|
|
61
|
+
task: taskId,
|
|
62
62
|
model: issue.model,
|
|
63
63
|
details: {
|
|
64
64
|
title: issue.title,
|
|
@@ -73,16 +73,16 @@ export function createDispatcher(deps) {
|
|
|
73
73
|
await board.markInProgress(issue);
|
|
74
74
|
logEvent("dispatch", {
|
|
75
75
|
outcome: recovery ? "resumed" : "started",
|
|
76
|
-
|
|
76
|
+
task: taskId,
|
|
77
77
|
model: issue.model,
|
|
78
78
|
repository: issue.repository,
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
82
|
-
log(`${failMark()} Failed to start ${
|
|
82
|
+
log(`${failMark()} Failed to start ${taskId}: ${errorMessage(error)}`);
|
|
83
83
|
logEvent("dispatch", {
|
|
84
84
|
outcome: "failed",
|
|
85
|
-
|
|
85
|
+
task: taskId,
|
|
86
86
|
model: issue.model,
|
|
87
87
|
repository: issue.repository,
|
|
88
88
|
error: errorMessage(error),
|
|
@@ -91,16 +91,16 @@ export function createDispatcher(deps) {
|
|
|
91
91
|
}
|
|
92
92
|
async function runOnce(arguments_) {
|
|
93
93
|
const { state, worktreeEntries, usage, dryRun, signal, idleSuffix = "" } = arguments_;
|
|
94
|
-
// Surface parent
|
|
95
|
-
// an operator sees "No Todo
|
|
96
|
-
// expected Todo+labelled
|
|
94
|
+
// Surface parent tasks that fetch silently dropped. Without this
|
|
95
|
+
// an operator sees "No Todo tasks to pick up" with no signal that an
|
|
96
|
+
// expected Todo+labelled task was skipped because it has sub-issues.
|
|
97
97
|
for (const skip of state.parentSkips) {
|
|
98
|
-
const
|
|
99
|
-
log(`Skipping ${
|
|
98
|
+
const task = naturalIdFromCanonical(skip.id);
|
|
99
|
+
log(`Skipping ${task}: parent task with ${skip.childCount} sub-issue(s) — groundcrew works sub-issues, not parents`);
|
|
100
100
|
logEvent("dispatch", {
|
|
101
101
|
outcome: "skipped",
|
|
102
102
|
reason: "parent_with_children",
|
|
103
|
-
|
|
103
|
+
task,
|
|
104
104
|
children: skip.childCount,
|
|
105
105
|
});
|
|
106
106
|
}
|
|
@@ -109,9 +109,9 @@ export function createDispatcher(deps) {
|
|
|
109
109
|
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
110
110
|
const activeCount = active.length;
|
|
111
111
|
const slots = config.orchestrator.maximumInProgress - activeCount;
|
|
112
|
-
// Narrow Todo to
|
|
113
|
-
// Unlabeled
|
|
114
|
-
// Sort by priority so higher-priority
|
|
112
|
+
// Narrow Todo to tasks that opted in via an `agent-*` label.
|
|
113
|
+
// Unlabeled tasks are not groundcrew's concern even when in Todo.
|
|
114
|
+
// Sort by priority so higher-priority tasks fill slots first.
|
|
115
115
|
const todo = state.issues
|
|
116
116
|
.filter((issue) => issue.status === "todo" && isGroundcrewIssue(issue))
|
|
117
117
|
.toSorted((a, b) => prioritySortKey(a.priority) - prioritySortKey(b.priority));
|
|
@@ -120,7 +120,7 @@ export function createDispatcher(deps) {
|
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
122
|
if (todo.length === 0) {
|
|
123
|
-
log(`No Todo
|
|
123
|
+
log(`No Todo tasks to pick up${idleSuffix}`);
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
126
|
// Run the blocker pre-pass first so an all-blocked board short-circuits
|
|
@@ -130,21 +130,21 @@ export function createDispatcher(deps) {
|
|
|
130
130
|
logSkip(skip);
|
|
131
131
|
}
|
|
132
132
|
if (unblocked.length === 0) {
|
|
133
|
-
log(`No eligible Todo
|
|
133
|
+
log(`No eligible Todo tasks after blocker filtering${idleSuffix}`);
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
136
136
|
// Validate repositories BEFORE the expensive probes so a tick whose only
|
|
137
137
|
// candidates have unknown repos short-circuits without paying for the
|
|
138
138
|
// usage() HTTP call or the workspaces.probe shell-out. Doing this filter
|
|
139
|
-
// here also keeps an unknown-repo
|
|
139
|
+
// here also keeps an unknown-repo task at the head of the queue from
|
|
140
140
|
// consuming a slot in classifyEligibility and starving later valid
|
|
141
|
-
//
|
|
141
|
+
// tasks. Each unknown repo still emits a WARN via dispatchableRepository.
|
|
142
142
|
const dispatchableUnblocked = unblocked.filter((issue) => {
|
|
143
143
|
const repository = dispatchableRepository(issue, config.workspace.knownRepositories, log);
|
|
144
144
|
return repository !== undefined;
|
|
145
145
|
});
|
|
146
146
|
if (dispatchableUnblocked.length === 0) {
|
|
147
|
-
log(`No eligible Todo
|
|
147
|
+
log(`No eligible Todo tasks after repository validation${idleSuffix}`);
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
150
|
// usage() is an HTTP call; workspaces.probe shells tmux/cmux. Kick off
|
|
@@ -186,14 +186,14 @@ export function createDispatcher(deps) {
|
|
|
186
186
|
logSkip(skip);
|
|
187
187
|
}
|
|
188
188
|
if (starts.length === 0) {
|
|
189
|
-
log(`No eligible Todo
|
|
189
|
+
log(`No eligible Todo tasks after eligibility filtering${idleSuffix}`);
|
|
190
190
|
return;
|
|
191
191
|
}
|
|
192
192
|
const dispatchable = starts;
|
|
193
|
-
log(`Slots ${activeCount}/${config.orchestrator.maximumInProgress} used${formatActiveSlotList(active)}, starting ${dispatchable.length}
|
|
193
|
+
log(`Slots ${activeCount}/${config.orchestrator.maximumInProgress} used${formatActiveSlotList(active)}, starting ${dispatchable.length} task(s): ${dispatchable.map(({ issue }) => `${naturalIdFromCanonical(issue.id)}(${issue.model})`).join(", ")}`);
|
|
194
194
|
logEvent("dispatch", {
|
|
195
195
|
outcome: "starting",
|
|
196
|
-
|
|
196
|
+
tasks: dispatchable.map(({ issue }) => `${naturalIdFromCanonical(issue.id)}:${issue.model}`),
|
|
197
197
|
});
|
|
198
198
|
for (const start of dispatchable) {
|
|
199
199
|
// oxlint-disable-next-line no-await-in-loop -- one workspace at a time avoids racing on git
|
|
@@ -205,15 +205,15 @@ export function createDispatcher(deps) {
|
|
|
205
205
|
function hasRecoverableCandidate(issues, worktreeEntries) {
|
|
206
206
|
return issues.some((issue) => {
|
|
207
207
|
const naturalId = naturalIdFromCanonical(issue.id);
|
|
208
|
-
return worktreeEntries.some((entry) => entry.repository === issue.repository && entry.
|
|
208
|
+
return worktreeEntries.some((entry) => entry.repository === issue.repository && entry.task === naturalId);
|
|
209
209
|
});
|
|
210
210
|
}
|
|
211
211
|
function formatUsageExhaustion(exhaustion) {
|
|
212
212
|
if (exhaustion.kind === "session") {
|
|
213
213
|
const mins = exhaustion.resetMinutes ?? "?";
|
|
214
|
-
return `${exhaustion.model} session at ${exhaustion.usedPercentage.toFixed(0)}% (> ${exhaustion.limitPercentage}%), resets in ${mins}m — skipping its
|
|
214
|
+
return `${exhaustion.model} session at ${exhaustion.usedPercentage.toFixed(0)}% (> ${exhaustion.limitPercentage}%), resets in ${mins}m — skipping its tasks`;
|
|
215
215
|
}
|
|
216
|
-
return `${exhaustion.model} weekly at ${exhaustion.usedPercentage.toFixed(1)}% (> ${exhaustion.allowedPercentage.toFixed(1)}% paced budget), resets in ${exhaustion.resetMinutes}m — skipping its
|
|
216
|
+
return `${exhaustion.model} weekly at ${exhaustion.usedPercentage.toFixed(1)}% (> ${exhaustion.allowedPercentage.toFixed(1)}% paced budget), resets in ${exhaustion.resetMinutes}m — skipping its tasks`;
|
|
217
217
|
}
|
|
218
218
|
/** Undefined priority sorts last. */
|
|
219
219
|
function prioritySortKey(priority) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -34,7 +34,7 @@ async function checkCmd(cmd, required, hint) {
|
|
|
34
34
|
return result;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Source-agnostic reachability check: build every configured
|
|
37
|
+
* Source-agnostic reachability check: build every configured task source
|
|
38
38
|
* and run the Board's `verify()` fan-out. Replaces the old Linear-only
|
|
39
39
|
* "api key + reachability" probe so a misconfigured shell (or future Jira)
|
|
40
40
|
* source surfaces here too. A missing Linear API key still fails verify with
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure eligibility classifier — takes the per-iteration board snapshot plus
|
|
3
3
|
* derived state (worktrees, live workspaces, usage, slot count) and returns
|
|
4
|
-
* a verdict per Todo
|
|
4
|
+
* a verdict per Todo task. No logging, no Linear calls, no shell-outs.
|
|
5
5
|
*
|
|
6
6
|
* The Dispatcher consumes the verdict list to drive logging and side
|
|
7
7
|
* effects.
|
|
8
8
|
*/
|
|
9
9
|
import { type ResolvedConfig } from "../lib/config.ts";
|
|
10
|
-
import { type GroundcrewIssue } from "../lib/
|
|
10
|
+
import { type GroundcrewIssue } from "../lib/taskSource.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,EAAmB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAwC,KAAK,eAAe,EAAE,MAAM,
|
|
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,sBAAsB,CAAC;AAClG,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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure eligibility classifier — takes the per-iteration board snapshot plus
|
|
3
3
|
* derived state (worktrees, live workspaces, usage, slot count) and returns
|
|
4
|
-
* a verdict per Todo
|
|
4
|
+
* a verdict per Todo task. No logging, no Linear calls, no shell-outs.
|
|
5
5
|
*
|
|
6
6
|
* The Dispatcher consumes the verdict list to drive logging and side
|
|
7
7
|
* effects.
|
|
8
8
|
*/
|
|
9
9
|
import { AGENT_ANY_MODEL } from "../lib/config.js";
|
|
10
|
-
import { naturalIdFromCanonical } from "../lib/
|
|
10
|
+
import { naturalIdFromCanonical } from "../lib/taskSource.js";
|
|
11
11
|
const PERCENT_FRACTION_DIVISOR = 100;
|
|
12
12
|
const DAYS_PER_WEEK = 7;
|
|
13
13
|
const MINUTES_PER_DAY = 24 * 60;
|
|
@@ -104,14 +104,14 @@ export function classifyUsageExhaustion(config, usage) {
|
|
|
104
104
|
return exhausted;
|
|
105
105
|
}
|
|
106
106
|
// Stale worktrees with no matching live workspace are filtered out here so
|
|
107
|
-
// they don't permanently block later
|
|
107
|
+
// they don't permanently block later tasks in the Todo queue.
|
|
108
108
|
function classifyRecovery(arguments_) {
|
|
109
109
|
const { issue, worktreeEntries, workspaceProbe, dryRun } = arguments_;
|
|
110
110
|
if (dryRun) {
|
|
111
111
|
return { kind: "go", recovery: false };
|
|
112
112
|
}
|
|
113
113
|
const naturalId = naturalIdFromCanonical(issue.id);
|
|
114
|
-
const exists = worktreeEntries.some((entry) => entry.repository === issue.repository && entry.
|
|
114
|
+
const exists = worktreeEntries.some((entry) => entry.repository === issue.repository && entry.task === naturalId);
|
|
115
115
|
if (!exists) {
|
|
116
116
|
return { kind: "go", recovery: false };
|
|
117
117
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -209,7 +209,7 @@ function writeInitGuidance(destination, options) {
|
|
|
209
209
|
writeCloneGuidance(options);
|
|
210
210
|
writeOutput(" - If using Linear, export your API key:");
|
|
211
211
|
writeOutput(' export GROUNDCREW_LINEAR_API_KEY="lin_api_..."');
|
|
212
|
-
writeOutput(" - In Linear, assign
|
|
212
|
+
writeOutput(" - In Linear, assign tasks to yourself and add an agent-* label to opt them in");
|
|
213
213
|
writeOutput(" - Validate and start:");
|
|
214
214
|
writeOutput(" crew doctor");
|
|
215
215
|
writeOutput(" crew run --watch");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ResolvedConfig } from "../lib/config.ts";
|
|
2
2
|
export interface InterruptWorkspaceOptions {
|
|
3
|
-
|
|
3
|
+
task: string;
|
|
4
4
|
reason?: string;
|
|
5
5
|
}
|
|
6
6
|
export declare function interruptWorkspace(config: ResolvedConfig, options: InterruptWorkspaceOptions): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interruptWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/interruptWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMnE,MAAM,WAAW,yBAAyB;IACxC,
|
|
1
|
+
{"version":3,"file":"interruptWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/interruptWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMnE,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAqGD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzE"}
|
|
@@ -22,19 +22,19 @@ function parseArguments(argv) {
|
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
24
|
if (argument.startsWith("-")) {
|
|
25
|
-
throw new Error(`Unknown option: ${argument}\nUsage: crew stop <
|
|
25
|
+
throw new Error(`Unknown option: ${argument}\nUsage: crew stop <task> [--reason <text>]`);
|
|
26
26
|
}
|
|
27
27
|
positionals.push(argument);
|
|
28
28
|
}
|
|
29
|
-
const [
|
|
30
|
-
if (
|
|
31
|
-
throw new Error("Usage: crew stop <
|
|
29
|
+
const [task, ...extras] = positionals;
|
|
30
|
+
if (task === undefined || task.length === 0 || extras.length > 0) {
|
|
31
|
+
throw new Error("Usage: crew stop <task> [--reason <text>]");
|
|
32
32
|
}
|
|
33
|
-
return {
|
|
33
|
+
return { task: task.toLowerCase(), ...(reason === undefined ? {} : { reason }) };
|
|
34
34
|
}
|
|
35
35
|
function sourceFromState(state) {
|
|
36
36
|
return {
|
|
37
|
-
|
|
37
|
+
task: state.task,
|
|
38
38
|
repository: state.repository,
|
|
39
39
|
model: state.model,
|
|
40
40
|
worktreeDir: state.worktreeDir,
|
|
@@ -43,14 +43,14 @@ function sourceFromState(state) {
|
|
|
43
43
|
resumeCount: state.resumeCount,
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
function sourceFromWorktree(config,
|
|
46
|
+
function sourceFromWorktree(config, task, entry) {
|
|
47
47
|
return {
|
|
48
|
-
|
|
48
|
+
task,
|
|
49
49
|
repository: entry.repository,
|
|
50
50
|
model: config.models.default,
|
|
51
51
|
worktreeDir: entry.dir,
|
|
52
52
|
branchName: entry.branchName,
|
|
53
|
-
workspaceName:
|
|
53
|
+
workspaceName: task,
|
|
54
54
|
resumeCount: 0,
|
|
55
55
|
};
|
|
56
56
|
}
|
|
@@ -59,9 +59,9 @@ function resolveInterruptSource(arguments_) {
|
|
|
59
59
|
return sourceFromState(arguments_.state);
|
|
60
60
|
}
|
|
61
61
|
if (arguments_.entry !== undefined) {
|
|
62
|
-
return sourceFromWorktree(arguments_.config, arguments_.
|
|
62
|
+
return sourceFromWorktree(arguments_.config, arguments_.task, arguments_.entry);
|
|
63
63
|
}
|
|
64
|
-
throw new Error(`No run state or worktree found for ${arguments_.
|
|
64
|
+
throw new Error(`No run state or worktree found for ${arguments_.task}; nothing to interrupt.`);
|
|
65
65
|
}
|
|
66
66
|
function interruptDetail(result) {
|
|
67
67
|
if (result.kind === "missing") {
|
|
@@ -77,17 +77,17 @@ function failOnUnavailable(result) {
|
|
|
77
77
|
throw new Error(`Could not interrupt workspace: ${detail}`);
|
|
78
78
|
}
|
|
79
79
|
export async function interruptWorkspace(config, options) {
|
|
80
|
-
const
|
|
81
|
-
const state = readRunState(config,
|
|
82
|
-
const [entry] = worktrees.
|
|
83
|
-
const source = resolveInterruptSource({ config,
|
|
80
|
+
const task = options.task.toLowerCase();
|
|
81
|
+
const state = readRunState(config, task);
|
|
82
|
+
const [entry] = worktrees.findByTask(config, task);
|
|
83
|
+
const source = resolveInterruptSource({ config, task, state, entry });
|
|
84
84
|
const result = await workspaces.interrupt(config, source.workspaceName);
|
|
85
85
|
failOnUnavailable(result);
|
|
86
86
|
const detail = interruptDetail(result);
|
|
87
87
|
recordRunState({
|
|
88
88
|
config,
|
|
89
89
|
state: {
|
|
90
|
-
|
|
90
|
+
task,
|
|
91
91
|
repository: source.repository,
|
|
92
92
|
model: source.model,
|
|
93
93
|
worktreeDir: source.worktreeDir,
|
|
@@ -99,8 +99,8 @@ export async function interruptWorkspace(config, options) {
|
|
|
99
99
|
...(detail === undefined ? {} : { detail }),
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
|
-
log(`Interrupted ${
|
|
103
|
-
log(`Next: crew status ${
|
|
102
|
+
log(`Interrupted ${task}; worktree preserved at ${source.worktreeDir}`);
|
|
103
|
+
log(`Next: crew status ${task}`);
|
|
104
104
|
}
|
|
105
105
|
export async function interruptWorkspaceCli(argv) {
|
|
106
106
|
const config = await loadConfig();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* groundcrew orchestrator — polls Linear projects and spins up workspace +
|
|
3
|
-
* git-worktree pairs for ready
|
|
3
|
+
* git-worktree pairs for ready tasks. Each tick fetches the board, runs
|
|
4
4
|
* the cleaner, the reviewer, and the dispatcher; logging from those modules is
|
|
5
5
|
* the orchestrator's user-facing output.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* groundcrew orchestrator — polls Linear projects and spins up workspace +
|
|
3
|
-
* git-worktree pairs for ready
|
|
3
|
+
* git-worktree pairs for ready tasks. Each tick fetches the board, runs
|
|
4
4
|
* the cleaner, the reviewer, and the dispatcher; logging from those modules is
|
|
5
5
|
* the orchestrator's user-facing output.
|
|
6
6
|
*/
|
|
@@ -8,7 +8,7 @@ import { createBoard } from "../lib/board.js";
|
|
|
8
8
|
import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
|
|
9
9
|
import { loadConfig } from "../lib/config.js";
|
|
10
10
|
import { findPullRequestsForBranch } from "../lib/pullRequests.js";
|
|
11
|
-
import { RepositoryResolutionError } from "../lib/
|
|
11
|
+
import { RepositoryResolutionError } from "../lib/taskSource.js";
|
|
12
12
|
import { getUsageByModel } from "../lib/usage.js";
|
|
13
13
|
import { errorMessage, log, sleep } from "../lib/util.js";
|
|
14
14
|
import { worktrees } from "../lib/worktrees.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ResolvedConfig } from "../lib/config.ts";
|
|
2
2
|
export interface ResumeWorkspaceOptions {
|
|
3
|
-
|
|
3
|
+
task: string;
|
|
4
4
|
}
|
|
5
5
|
export declare function resumeWorkspace(config: ResolvedConfig, options: ResumeWorkspaceOptions): Promise<void>;
|
|
6
6
|
export declare function resumeWorkspaceCli(argv: string[]): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,sBAAsB;IACrC,
|
|
1
|
+
{"version":3,"file":"resumeWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/resumeWorkspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAenE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AA6HD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA0Ff;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE"}
|
|
@@ -10,29 +10,29 @@ import { errorMessage, log } from "../lib/util.js";
|
|
|
10
10
|
import { workspaces } from "../lib/workspaces.js";
|
|
11
11
|
import { worktrees } from "../lib/worktrees.js";
|
|
12
12
|
function parseArguments(argv) {
|
|
13
|
-
const [
|
|
14
|
-
if (
|
|
15
|
-
throw new Error("Usage: crew resume <
|
|
13
|
+
const [task, ...extras] = argv;
|
|
14
|
+
if (task === undefined || task.length === 0 || extras.length > 0 || task.startsWith("-")) {
|
|
15
|
+
throw new Error("Usage: crew resume <task>");
|
|
16
16
|
}
|
|
17
|
-
return {
|
|
17
|
+
return { task: task.toLowerCase() };
|
|
18
18
|
}
|
|
19
|
-
async function
|
|
19
|
+
async function fetchTaskDetails(task) {
|
|
20
20
|
try {
|
|
21
|
-
const issue = await getLinearClient().issue(
|
|
21
|
+
const issue = await getLinearClient().issue(task.toUpperCase());
|
|
22
22
|
return {
|
|
23
23
|
title: issue.title,
|
|
24
24
|
description: issue.description ?? "",
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
catch (error) {
|
|
28
|
-
log(`Resume Linear detail lookup failed for ${
|
|
28
|
+
log(`Resume Linear detail lookup failed for ${task}: ${errorMessage(error)}`);
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
async function contextFromLinear(config,
|
|
33
|
-
const resolved = await fetchResolvedIssue({ client: getLinearClient(), config,
|
|
32
|
+
async function contextFromLinear(config, task, worktree) {
|
|
33
|
+
const resolved = await fetchResolvedIssue({ client: getLinearClient(), config, task });
|
|
34
34
|
return {
|
|
35
|
-
|
|
35
|
+
task,
|
|
36
36
|
repository: resolved.repository,
|
|
37
37
|
model: resolved.model,
|
|
38
38
|
worktree,
|
|
@@ -41,38 +41,38 @@ async function contextFromLinear(config, ticket, worktree) {
|
|
|
41
41
|
resumeCount: 0,
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
async function contextFromState(
|
|
45
|
-
const details = await
|
|
44
|
+
async function contextFromState(task, state, worktree) {
|
|
45
|
+
const details = await fetchTaskDetails(task);
|
|
46
46
|
return {
|
|
47
|
-
|
|
47
|
+
task,
|
|
48
48
|
repository: state.repository,
|
|
49
49
|
model: state.model,
|
|
50
50
|
worktree,
|
|
51
|
-
title: details?.title ??
|
|
51
|
+
title: details?.title ?? task.toUpperCase(),
|
|
52
52
|
description: details?.description ?? "",
|
|
53
53
|
...(state.reason === undefined ? {} : { reason: state.reason }),
|
|
54
54
|
resumeCount: state.resumeCount,
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
async function buildResumeContext(config,
|
|
58
|
-
const state = readRunState(config,
|
|
59
|
-
const entries = worktrees.
|
|
57
|
+
async function buildResumeContext(config, task) {
|
|
58
|
+
const state = readRunState(config, task);
|
|
59
|
+
const entries = worktrees.findByTask(config, task);
|
|
60
60
|
const worktree = state === undefined
|
|
61
61
|
? entries[0]
|
|
62
62
|
: (entries.find((entry) => entry.repository === state.repository) ?? entries[0]);
|
|
63
63
|
if (worktree === undefined) {
|
|
64
|
-
throw new Error(`No worktree found for ${
|
|
64
|
+
throw new Error(`No worktree found for ${task}; cannot resume.`);
|
|
65
65
|
}
|
|
66
66
|
if (state !== undefined) {
|
|
67
|
-
return await contextFromState(
|
|
67
|
+
return await contextFromState(task, state, worktree);
|
|
68
68
|
}
|
|
69
|
-
return await contextFromLinear(config,
|
|
69
|
+
return await contextFromLinear(config, task, worktree);
|
|
70
70
|
}
|
|
71
71
|
function renderResumePrompt(context) {
|
|
72
72
|
return [
|
|
73
|
-
`You are resuming Groundcrew
|
|
73
|
+
`You are resuming Groundcrew task ${context.task} (${context.title}) in an existing worktree.`,
|
|
74
74
|
"",
|
|
75
|
-
"
|
|
75
|
+
"Task description:",
|
|
76
76
|
"",
|
|
77
77
|
context.description,
|
|
78
78
|
"",
|
|
@@ -89,20 +89,20 @@ function renderResumePrompt(context) {
|
|
|
89
89
|
"Run the repository's documented verification before stopping, then leave the branch ready or open a PR when possible.",
|
|
90
90
|
].join("\n");
|
|
91
91
|
}
|
|
92
|
-
async function failIfWorkspaceAlreadyLive(config,
|
|
92
|
+
async function failIfWorkspaceAlreadyLive(config, task) {
|
|
93
93
|
const probe = await workspaces.probe(config);
|
|
94
94
|
if (probe.kind === "unavailable") {
|
|
95
95
|
const detail = probe.error === undefined ? "" : `: ${errorMessage(probe.error)}`;
|
|
96
|
-
throw new Error(`Could not verify whether workspace for ${
|
|
96
|
+
throw new Error(`Could not verify whether workspace for ${task} is already live${detail}. Retry or inspect the workspace backend manually before resuming.`);
|
|
97
97
|
}
|
|
98
|
-
if (probe.names.has(
|
|
99
|
-
throw new Error(`Workspace for ${
|
|
98
|
+
if (probe.names.has(task)) {
|
|
99
|
+
throw new Error(`Workspace for ${task} is already live; attach to it instead of resuming.`);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
export async function resumeWorkspace(config, options) {
|
|
103
|
-
const
|
|
104
|
-
await failIfWorkspaceAlreadyLive(config,
|
|
105
|
-
const context = await buildResumeContext(config,
|
|
103
|
+
const task = options.task.toLowerCase();
|
|
104
|
+
await failIfWorkspaceAlreadyLive(config, task);
|
|
105
|
+
const context = await buildResumeContext(config, task);
|
|
106
106
|
const definition = config.models.definitions[context.model];
|
|
107
107
|
if (definition === undefined) {
|
|
108
108
|
throw new Error(`Unknown model: ${context.model}`);
|
|
@@ -116,7 +116,7 @@ export async function resumeWorkspace(config, options) {
|
|
|
116
116
|
await ensureReady();
|
|
117
117
|
const stagedPrompt = stagePromptText({
|
|
118
118
|
prefix: "groundcrew-resume",
|
|
119
|
-
|
|
119
|
+
task,
|
|
120
120
|
text: renderResumePrompt(context),
|
|
121
121
|
});
|
|
122
122
|
const secretsFile = stageBuildSecrets(stagedPrompt.directory);
|
|
@@ -131,7 +131,7 @@ export async function resumeWorkspace(config, options) {
|
|
|
131
131
|
const staged = buildAndStageSrtLaunch({
|
|
132
132
|
config,
|
|
133
133
|
repository: context.repository,
|
|
134
|
-
|
|
134
|
+
task,
|
|
135
135
|
worktreeDir: context.worktree.dir,
|
|
136
136
|
definition,
|
|
137
137
|
});
|
|
@@ -156,7 +156,7 @@ export async function resumeWorkspace(config, options) {
|
|
|
156
156
|
try {
|
|
157
157
|
await openAgentWorkspace({
|
|
158
158
|
config,
|
|
159
|
-
name:
|
|
159
|
+
name: task,
|
|
160
160
|
cwd: context.worktree.dir,
|
|
161
161
|
command: launchCmd,
|
|
162
162
|
model: context.model,
|
|
@@ -175,18 +175,18 @@ export async function resumeWorkspace(config, options) {
|
|
|
175
175
|
recordRunState({
|
|
176
176
|
config,
|
|
177
177
|
state: {
|
|
178
|
-
|
|
178
|
+
task,
|
|
179
179
|
repository: context.repository,
|
|
180
180
|
model: context.model,
|
|
181
181
|
worktreeDir: context.worktree.dir,
|
|
182
182
|
branchName: context.worktree.branchName,
|
|
183
|
-
workspaceName:
|
|
183
|
+
workspaceName: task,
|
|
184
184
|
state: "resumed",
|
|
185
185
|
resumeCount: context.resumeCount + 1,
|
|
186
186
|
...(context.reason === undefined ? {} : { reason: context.reason }),
|
|
187
187
|
},
|
|
188
188
|
});
|
|
189
|
-
log(`Resumed ${
|
|
189
|
+
log(`Resumed ${task} in ${context.worktree.dir} (${context.model})`);
|
|
190
190
|
}
|
|
191
191
|
export async function resumeWorkspaceCli(argv) {
|
|
192
192
|
const config = await loadConfig();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-iteration scanner that advances a
|
|
2
|
+
* Per-iteration scanner that advances a task based on its worktree's pull
|
|
3
3
|
* request state. Sits between the cleaner and the dispatcher in each
|
|
4
4
|
* `orchestrate()` tick.
|
|
5
5
|
*
|
|
6
|
-
* - An **open** PR on an **in-progress**
|
|
6
|
+
* - An **open** PR on an **in-progress** task → `markInReview`: frees a
|
|
7
7
|
* dispatch slot (slot math counts only in-progress) while leaving the
|
|
8
8
|
* worktree intact for review, since the cleaner only tears down `done`.
|
|
9
|
-
* - A **merged** PR (on an in-progress or in-review
|
|
10
|
-
* the work has landed, so the
|
|
9
|
+
* - A **merged** PR (on an in-progress or in-review task) → `markDone`:
|
|
10
|
+
* the work has landed, so the task is terminal and the cleaner tears the
|
|
11
11
|
* worktree down on a later tick. `merged` never routes to `in-review`.
|
|
12
12
|
*
|
|
13
13
|
* Sources that don't implement `markDone` (e.g. Linear) return `unsupported`;
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
* fallback. (Linear's own GitHub integration moves merged issues to Done,
|
|
16
16
|
* which groundcrew observes via `fetch()`.)
|
|
17
17
|
*
|
|
18
|
-
* The write-back lands in the
|
|
18
|
+
* The write-back lands in the task source, not the in-memory `BoardState`,
|
|
19
19
|
* so the dispatcher in the SAME tick still sees prior state; the slot frees on
|
|
20
20
|
* the NEXT tick's `board.fetch()`. That one-tick latency is deliberate. One
|
|
21
21
|
* per `orchestrate()`; stateless across iterations. Mirrors `Cleaner`.
|
|
22
22
|
*/
|
|
23
23
|
import type { Board } from "../lib/board.ts";
|
|
24
24
|
import type { PullRequestSummary } from "../lib/pullRequests.ts";
|
|
25
|
-
import { type BoardState } from "../lib/
|
|
25
|
+
import { type BoardState } from "../lib/taskSource.ts";
|
|
26
26
|
import type { WorktreeEntry } from "../lib/worktrees.ts";
|
|
27
27
|
/**
|
|
28
28
|
* Injected PR lookup. Matches `findPullRequestsForBranch`'s shape: best-effort,
|
|
@@ -47,7 +47,7 @@ interface ReviewArguments {
|
|
|
47
47
|
signal?: AbortSignal;
|
|
48
48
|
}
|
|
49
49
|
export interface Reviewer {
|
|
50
|
-
runOnce(arguments_: ReviewArguments)
|
|
50
|
+
runOnce: (arguments_: ReviewArguments) => Promise<void>;
|
|
51
51
|
}
|
|
52
52
|
export declare function createReviewer(deps: ReviewerDeps): Reviewer;
|
|
53
53
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewer.d.ts","sourceRoot":"","sources":["../../src/commands/reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,
|
|
1
|
+
{"version":3,"file":"reviewer.d.ts","sourceRoot":"","sources":["../../src/commands/reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,UAAU,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,KAAK,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAC;AAE7C,UAAU,YAAY;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED,sEAAsE;AACtE,UAAU,eAAe;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AA+CD,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAwH3D"}
|