@clipboard-health/groundcrew 4.24.0 → 4.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +29 -7
- package/dist/commands/task.d.ts.map +1 -1
- package/dist/commands/task.js +3 -6
- package/dist/lib/adapters/todo-txt/normalizer.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/normalizer.js +2 -4
- package/dist/lib/adapters/todo-txt/source.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/source.js +13 -3
- package/dist/lib/adapters/todo-txt/writeback.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/writeback.js +8 -5
- package/dist/lib/repositoryValidation.d.ts +1 -0
- package/dist/lib/repositoryValidation.d.ts.map +1 -1
- package/dist/lib/repositoryValidation.js +4 -1
- package/dist/lib/runState.d.ts.map +1 -1
- package/dist/lib/runState.js +2 -6
- package/dist/lib/taskId.d.ts +4 -0
- package/dist/lib/taskId.d.ts.map +1 -0
- package/dist/lib/taskId.js +19 -0
- package/dist/lib/worktrees.d.ts +1 -1
- package/dist/lib/worktrees.d.ts.map +1 -1
- package/dist/lib/worktrees.js +24 -19
- package/docs/commands.md +1 -1
- package/docs/task-sources.md +7 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ crew init [--global | --local] [--force] [--dry-run] # create a crew.config.
|
|
|
95
95
|
crew doctor # check setup
|
|
96
96
|
crew task list [--source <name>] # list tasks across sources
|
|
97
97
|
crew task get <TASK> [--source <name>] [--prompt] # inspect one task or its prompt
|
|
98
|
-
crew task create "Title" --source <name> --agent <name>
|
|
98
|
+
crew task create "Title" --source <name> [--agent <name>] # create a source task
|
|
99
99
|
crew status [<TASK>] # inspect current state or one task
|
|
100
100
|
crew run [--watch] # one-shot or --watch forever
|
|
101
101
|
crew start <TASK> # provision + launch one task now
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EACL,KAAK,UAAU,EAGf,KAAK,KAAK,EAEX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,UAAU,EAAE;QACpB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,+FAA+F;QAC/F,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EACL,KAAK,UAAU,EAGf,KAAK,KAAK,EAEX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWzD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,UAAU,EAAE;QACpB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,+FAA+F;QAC/F,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB;;;;WAIG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAiCD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAmOjE;AA2BD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAQrE"}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* Pure verdict logic lives in `eligibility.ts`; this module is responsible
|
|
7
7
|
* for telemetry, writeback via Board, and side-effecting setupWorkspace calls.
|
|
8
8
|
*/
|
|
9
|
-
import { dispatchableRepository } from "../lib/repositoryValidation.js";
|
|
9
|
+
import { dispatchableRepository, formatKnownRepositories } from "../lib/repositoryValidation.js";
|
|
10
10
|
import { isGroundcrewIssue, naturalIdFromCanonical, } from "../lib/taskSource.js";
|
|
11
|
-
import { errorMessage, failMark, log, logEvent } from "../lib/util.js";
|
|
11
|
+
import { errorMessage, failMark, log, logEvent, styleWarning } from "../lib/util.js";
|
|
12
12
|
import { workspaces } from "../lib/workspaces.js";
|
|
13
13
|
import { classifyBlockers, classifyEligibility, classifyUsageExhaustion, } from "./eligibility.js";
|
|
14
14
|
import { setupWorkspace } from "./setupWorkspace.js";
|
|
@@ -22,6 +22,17 @@ function logSkip(verdict) {
|
|
|
22
22
|
model: verdict.model,
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
function logMissingRepositorySkip(issue, model, knownRepositories) {
|
|
26
|
+
const task = naturalIdFromCanonical(issue.id);
|
|
27
|
+
log(styleWarning(`WARNING: ${issue.id} has agent "${model}" but no repository; skipping dispatch. Add --repo <repo> when creating the task, add repo:<repo> to the task line, or set defaultRepository on source "${issue.source}". Known repositories: ${formatKnownRepositories(knownRepositories)}`));
|
|
28
|
+
logEvent("dispatch", {
|
|
29
|
+
outcome: "skipped",
|
|
30
|
+
reason: "missing_repository",
|
|
31
|
+
task,
|
|
32
|
+
source: issue.source,
|
|
33
|
+
model,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
25
36
|
export function createDispatcher(deps) {
|
|
26
37
|
const { config, board } = deps;
|
|
27
38
|
function buildExhaustedSet(usage) {
|
|
@@ -109,20 +120,31 @@ export function createDispatcher(deps) {
|
|
|
109
120
|
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
110
121
|
const activeCount = active.length;
|
|
111
122
|
const slots = config.orchestrator.maximumInProgress - activeCount;
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
const rawTodo = state.issues.filter((issue) => issue.status === "todo");
|
|
124
|
+
for (const issue of rawTodo) {
|
|
125
|
+
const { model, repository } = issue;
|
|
126
|
+
if (model !== undefined && repository === undefined) {
|
|
127
|
+
logMissingRepositorySkip(issue, model, config.workspace.knownRepositories);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Narrow queued work to tasks that opted in with an agent and resolved a
|
|
131
|
+
// repository. Unlabeled tasks are not groundcrew's concern.
|
|
114
132
|
// Sort by priority so higher-priority tasks fill slots first.
|
|
115
|
-
const todo =
|
|
116
|
-
.filter((issue) =>
|
|
133
|
+
const todo = rawTodo
|
|
134
|
+
.filter((issue) => isGroundcrewIssue(issue))
|
|
117
135
|
.toSorted((a, b) => prioritySortKey(a.priority) - prioritySortKey(b.priority));
|
|
118
136
|
if (slots <= 0) {
|
|
119
137
|
log(`At capacity (${activeCount}/${config.orchestrator.maximumInProgress})${formatActiveSlotList(active)}, no new work to start${idleSuffix}`);
|
|
120
138
|
return;
|
|
121
139
|
}
|
|
122
|
-
if (
|
|
140
|
+
if (rawTodo.length === 0) {
|
|
123
141
|
log(`No Todo tasks to pick up${idleSuffix}`);
|
|
124
142
|
return;
|
|
125
143
|
}
|
|
144
|
+
if (todo.length === 0) {
|
|
145
|
+
log(`No eligible Todo tasks after agent/repository filtering${idleSuffix}`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
126
148
|
// Run the blocker pre-pass first so an all-blocked board short-circuits
|
|
127
149
|
// before the codexbar HTTP call and the cmux/tmux shell-out fire.
|
|
128
150
|
const { unblocked, skips: blockerSkips } = classifyBlockers(todo);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AAomBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3D"}
|
package/dist/commands/task.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
|
|
2
|
-
import { loadConfig } from "../lib/config.js";
|
|
2
|
+
import { AGENT_ANY_MODEL, loadConfig } from "../lib/config.js";
|
|
3
3
|
import { naturalIdFromCanonical, } from "../lib/taskSource.js";
|
|
4
4
|
import { writeOutput } from "../lib/util.js";
|
|
5
5
|
const TASK_USAGE = `Usage: crew task <subcommand>
|
|
@@ -25,7 +25,7 @@ Options:
|
|
|
25
25
|
--source <name> Resolve a source-native ID against a specific source.
|
|
26
26
|
--json Print normalized task JSON.
|
|
27
27
|
--prompt Print only the task description/prompt.`;
|
|
28
|
-
const CREATE_USAGE = `Usage: crew task create "Short title" --source <source> --agent <agent> [options]`;
|
|
28
|
+
const CREATE_USAGE = `Usage: crew task create "Short title" --source <source> [--agent <agent>] [options]`;
|
|
29
29
|
const CANONICAL_STATUSES = [
|
|
30
30
|
"todo",
|
|
31
31
|
"in-progress",
|
|
@@ -232,12 +232,9 @@ function parseCreateOptions(argv) {
|
|
|
232
232
|
if (state.sourceName === undefined) {
|
|
233
233
|
throw new Error("crew task create: --source is required");
|
|
234
234
|
}
|
|
235
|
-
if (state.agent === undefined) {
|
|
236
|
-
throw new Error("crew task create: --agent is required");
|
|
237
|
-
}
|
|
238
235
|
const input = {
|
|
239
236
|
title,
|
|
240
|
-
agent: state.agent,
|
|
237
|
+
agent: state.agent ?? AGENT_ANY_MODEL,
|
|
241
238
|
projects: state.projects,
|
|
242
239
|
contexts: state.contexts,
|
|
243
240
|
dependencies: state.dependencies,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/normalizer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/normalizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsC,KAAK,KAAK,EAAiB,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9F,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,KAAK,GAAG,SAAS,CAqD7E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAShE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { AGENT_ANY_MODEL } from "../../config.js";
|
|
2
3
|
import { toCanonicalId } from "../../taskSource.js";
|
|
3
4
|
import { getMetadataAll, getMetadataFirst, hashLine } from "./parser.js";
|
|
4
5
|
function derivedCanonicalStatus(parsed) {
|
|
@@ -58,7 +59,7 @@ export function normalizeToIssue(options) {
|
|
|
58
59
|
if (id === undefined) {
|
|
59
60
|
return undefined;
|
|
60
61
|
}
|
|
61
|
-
const agent = getMetadataFirst(parsed, "agent");
|
|
62
|
+
const agent = getMetadataFirst(parsed, "agent") ?? AGENT_ANY_MODEL;
|
|
62
63
|
const status = derivedCanonicalStatus(parsed);
|
|
63
64
|
const repository = getMetadataFirst(parsed, "repo") ?? defaultRepository;
|
|
64
65
|
const depIds = getMetadataAll(parsed, "dep");
|
|
@@ -96,9 +97,6 @@ export function isActiveForFetch(parsed) {
|
|
|
96
97
|
if (getMetadataFirst(parsed, "id") === undefined) {
|
|
97
98
|
return false;
|
|
98
99
|
}
|
|
99
|
-
if (getMetadataFirst(parsed, "agent") === undefined) {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
100
|
const statusValue = getMetadataFirst(parsed, "status");
|
|
103
101
|
return statusValue === "todo" || statusValue === "in-progress" || statusValue === "in-review";
|
|
104
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/source.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA0RxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,cAAc,GACvB,UAAU,CA4IZ"}
|
|
@@ -8,13 +8,23 @@ import { getMetadataFirst, parseAllLines } from "./parser.js";
|
|
|
8
8
|
import { copyPromptFile, updateTaskStatus, validateTodoFile, withLock } from "./writeback.js";
|
|
9
9
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10
10
|
const RECURRENCE_RE = /^\+?\d+[dwmy]$/;
|
|
11
|
-
function
|
|
11
|
+
function readPromptFile(promptPath) {
|
|
12
12
|
try {
|
|
13
13
|
return readFileSync(promptPath, "utf8");
|
|
14
14
|
}
|
|
15
15
|
catch {
|
|
16
|
-
return
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function descriptionFor(parsed, promptPath) {
|
|
20
|
+
const promptContent = readPromptFile(promptPath);
|
|
21
|
+
if (promptContent !== undefined && promptContent.trim().length > 0) {
|
|
22
|
+
return promptContent;
|
|
23
|
+
}
|
|
24
|
+
if (parsed.title.trim().length > 0) {
|
|
25
|
+
return `${parsed.title}\n`;
|
|
17
26
|
}
|
|
27
|
+
return promptContent ?? "";
|
|
18
28
|
}
|
|
19
29
|
function fileUpdatedAt(filePath) {
|
|
20
30
|
try {
|
|
@@ -51,7 +61,7 @@ function buildIssue(options) {
|
|
|
51
61
|
}
|
|
52
62
|
const promptOverride = getMetadataFirst(parsed, "prompt");
|
|
53
63
|
const promptPath = promptOverride ?? `${tasksDir}/${id}.md`;
|
|
54
|
-
const description =
|
|
64
|
+
const description = descriptionFor(parsed, promptPath);
|
|
55
65
|
return normalizeToIssue({
|
|
56
66
|
parsed,
|
|
57
67
|
allParsed: parsedAll,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/writeback.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAoKD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,gBAAgB,CAAC;IACtB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQxF;AAED,KAAK,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;AA6E3D,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,cAAc,GACxB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAsElC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQrE;
|
|
1
|
+
{"version":3,"file":"writeback.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/writeback.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAoKD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,gBAAgB,CAAC;IACtB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQxF;AAED,KAAK,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;AA6E3D,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,cAAc,GACxB,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAsElC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAQrE;AA4FD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA0C7E"}
|
|
@@ -262,16 +262,19 @@ export function copyPromptFile(oldPath, newPath) {
|
|
|
262
262
|
// prompt file is optional — copy is best-effort
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
|
-
function validatePromptFile(tasksDir, id, promptOverride, prefix, errors) {
|
|
265
|
+
function validatePromptFile(tasksDir, id, promptOverride, title, prefix, errors) {
|
|
266
266
|
const promptPath = promptOverride ?? path.join(tasksDir, `${id}.md`);
|
|
267
|
+
const shouldRequirePrompt = promptOverride !== undefined || title.trim().length === 0;
|
|
267
268
|
try {
|
|
268
269
|
const desc = readFileSync(promptPath, "utf8");
|
|
269
|
-
if (desc.trim().length === 0) {
|
|
270
|
+
if (desc.trim().length === 0 && shouldRequirePrompt) {
|
|
270
271
|
errors.push(`${prefix}: empty prompt file "${promptPath}" for ready task "${id}"`);
|
|
271
272
|
}
|
|
272
273
|
}
|
|
273
274
|
catch {
|
|
274
|
-
|
|
275
|
+
if (shouldRequirePrompt) {
|
|
276
|
+
errors.push(`${prefix}: missing prompt file "${promptPath}" for ready task "${id}"`);
|
|
277
|
+
}
|
|
275
278
|
}
|
|
276
279
|
}
|
|
277
280
|
function validateDepsAndDates(parsed, parsedAll, id, prefix, errors) {
|
|
@@ -308,7 +311,7 @@ function validateActiveTaskLine(parsed, parsedAll, tasksDir, id, prefix, errors)
|
|
|
308
311
|
errors.push(`${prefix}: task "${id}" has status:todo but it is not the final token — task will not be dispatched`);
|
|
309
312
|
}
|
|
310
313
|
if (statusValue === "todo" && parsed.isStatusFinalToken) {
|
|
311
|
-
validatePromptFile(tasksDir, id, parsed.metadata["prompt"]?.[0], prefix, errors);
|
|
314
|
+
validatePromptFile(tasksDir, id, parsed.metadata["prompt"]?.[0], parsed.title, prefix, errors);
|
|
312
315
|
}
|
|
313
316
|
validateDepsAndDates(parsed, parsedAll, id, prefix, errors);
|
|
314
317
|
}
|
|
@@ -340,7 +343,7 @@ export function validateTodoFile(todoPath, tasksDir) {
|
|
|
340
343
|
idsSeen.set(lower, lineNum);
|
|
341
344
|
}
|
|
342
345
|
}
|
|
343
|
-
if (id === undefined
|
|
346
|
+
if (id === undefined) {
|
|
344
347
|
continue;
|
|
345
348
|
}
|
|
346
349
|
if (parsed.completed) {
|
|
@@ -9,5 +9,6 @@
|
|
|
9
9
|
* the tick across N sources.
|
|
10
10
|
*/
|
|
11
11
|
import type { Issue } from "./taskSource.ts";
|
|
12
|
+
export declare function formatKnownRepositories(knownRepositories: readonly string[]): string;
|
|
12
13
|
export declare function dispatchableRepository(issue: Issue, knownRepositories: readonly string[], log: (message: string) => void): string | undefined;
|
|
13
14
|
//# sourceMappingURL=repositoryValidation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repositoryValidation.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,MAAM,GAAG,SAAS,CAWpB"}
|
|
1
|
+
{"version":3,"file":"repositoryValidation.d.ts","sourceRoot":"","sources":["../../src/lib/repositoryValidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,wBAAgB,uBAAuB,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAEpF;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,MAAM,GAAG,SAAS,CAWpB"}
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
* the MVP-2 plan): one badly-labelled task should not throw and abort
|
|
9
9
|
* the tick across N sources.
|
|
10
10
|
*/
|
|
11
|
+
export function formatKnownRepositories(knownRepositories) {
|
|
12
|
+
return knownRepositories.join(", ") || "(none)";
|
|
13
|
+
}
|
|
11
14
|
export function dispatchableRepository(issue, knownRepositories, log) {
|
|
12
15
|
if (issue.repository === undefined) {
|
|
13
16
|
return undefined;
|
|
14
17
|
}
|
|
15
18
|
if (!knownRepositories.includes(issue.repository)) {
|
|
16
|
-
log(`issue ${issue.id} references unknown repository ${issue.repository}; configured workspace.knownRepositories: ${knownRepositories
|
|
19
|
+
log(`issue ${issue.id} references unknown repository ${issue.repository}; configured workspace.knownRepositories: ${formatKnownRepositories(knownRepositories)}`);
|
|
17
20
|
return undefined;
|
|
18
21
|
}
|
|
19
22
|
return issue.repository;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runState.d.ts","sourceRoot":"","sources":["../../src/lib/runState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"runState.d.ts","sourceRoot":"","sources":["../../src/lib/runState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,kBAAkB,CAAC;AAE3F,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,GAAG;QACrD,KAAK,EAAE,iBAAiB,CAAC;KAC1B,CAAC;CACH;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,MAAM,CAEjF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1F;AAmFD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA0BnE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,GAAG,SAAS,CAc/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzE"}
|
package/dist/lib/runState.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
|
|
3
|
+
import { normalizePlainTaskId } from "./taskId.js";
|
|
4
4
|
const RUN_STATE_DIRECTORY_NAME = "runs";
|
|
5
5
|
function taskKey(task) {
|
|
6
|
-
|
|
7
|
-
if (!TASK_RE.test(normalized)) {
|
|
8
|
-
throw new Error(`Invalid task "${task}": must be a plain task id`);
|
|
9
|
-
}
|
|
10
|
-
return normalized;
|
|
6
|
+
return normalizePlainTaskId(task);
|
|
11
7
|
}
|
|
12
8
|
export function runStateDirectory(config) {
|
|
13
9
|
return path.resolve(path.dirname(config.logging.file), RUN_STATE_DIRECTORY_NAME);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taskId.d.ts","sourceRoot":"","sources":["../../src/lib/taskId.ts"],"names":[],"mappings":"AAMA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAIpD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMzD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const PLAIN_TASK_ID_RE = /^[a-z][\da-z]*(?:-[\da-z]+)*-\d+$/;
|
|
2
|
+
function invalidPlainTaskIdError(task) {
|
|
3
|
+
return new Error(`Invalid task "${task}": must be a plain task id`);
|
|
4
|
+
}
|
|
5
|
+
export function isPlainTaskId(task) {
|
|
6
|
+
return PLAIN_TASK_ID_RE.test(task);
|
|
7
|
+
}
|
|
8
|
+
export function assertPlainTaskId(task) {
|
|
9
|
+
if (!isPlainTaskId(task)) {
|
|
10
|
+
throw invalidPlainTaskIdError(task);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function normalizePlainTaskId(task) {
|
|
14
|
+
const normalized = task.toLowerCase();
|
|
15
|
+
if (!isPlainTaskId(normalized)) {
|
|
16
|
+
throw invalidPlainTaskIdError(task);
|
|
17
|
+
}
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
package/dist/lib/worktrees.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare class WorktreeAlreadyExistsError extends Error {
|
|
|
18
18
|
export declare function isWorktreeAlreadyExistsError(error: unknown): error is WorktreeAlreadyExistsError;
|
|
19
19
|
export interface WorktreeEntry {
|
|
20
20
|
repository: string;
|
|
21
|
-
/**
|
|
21
|
+
/** Source task id, lowercased — e.g. "team-220" or "gc-20260608-001". */
|
|
22
22
|
task: string;
|
|
23
23
|
/** Slash-free `<prefix>-<task>`. */
|
|
24
24
|
branchName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAAE,KAAK,cAAc,EAAsC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"worktrees.d.ts","sourceRoot":"","sources":["../../src/lib/worktrees.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAAE,KAAK,cAAc,EAAsC,MAAM,aAAa,CAAC;AAItF,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,iBAAiB,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,YAAmB,GAAG,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAeD,iBAAS,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvE;AA4QD,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAiHxB,iBAAS,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,EAAE,CAErD;AAED,iBAAS,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAEzE;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,iBAAe,MAAM,CACnB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sCAAsC;IACtC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC;CAChC;AAuBD,iBAAe,QAAQ,CACrB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAClD,OAAO,CAAC,cAAc,CAAC,CAiDzB;AAED,iBAAe,gBAAgB,CAAC,KAAK,EAAE;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE7B;AAED,eAAO,MAAM,SAAS;;;;;;;;CAQrB,CAAC"}
|
package/dist/lib/worktrees.js
CHANGED
|
@@ -14,6 +14,7 @@ import path from "node:path";
|
|
|
14
14
|
import { runCommandAsync } from "./commandRunner.js";
|
|
15
15
|
import { repositoryBaseDir, worktreeBaseDir } from "./config.js";
|
|
16
16
|
import { resolveDefaultBranch } from "./defaultBranch.js";
|
|
17
|
+
import { assertPlainTaskId, isPlainTaskId } from "./taskId.js";
|
|
17
18
|
import { debug, errorMessage, isVerbose } from "./util.js";
|
|
18
19
|
import { workspaces } from "./workspaces.js";
|
|
19
20
|
const WORKTREE_LIST_PREFIX = "worktree ";
|
|
@@ -28,8 +29,6 @@ export class WorktreeAlreadyExistsError extends Error {
|
|
|
28
29
|
export function isWorktreeAlreadyExistsError(error) {
|
|
29
30
|
return error instanceof WorktreeAlreadyExistsError;
|
|
30
31
|
}
|
|
31
|
-
const TASK_RE = /^[a-z][\da-z]*-\d+$/;
|
|
32
|
-
const TASK_DIR_RE = /^(?<repoBasename>.+)-(?<task>[a-z][\da-z]*-\d+)$/;
|
|
33
32
|
function branchPrefix(config) {
|
|
34
33
|
const fromConfig = config.git.branchPrefix;
|
|
35
34
|
if (fromConfig !== undefined) {
|
|
@@ -58,9 +57,7 @@ function basePaths(config, repository, task) {
|
|
|
58
57
|
// Tasks must match the same shape the worktree discovery regexes use,
|
|
59
58
|
// so create()/list()/findByTask() agree on what's a valid worktree.
|
|
60
59
|
// This also rejects traversal tokens before they reach path.resolve().
|
|
61
|
-
|
|
62
|
-
throw new Error(`Invalid task "${task}": must be a plain task id`);
|
|
63
|
-
}
|
|
60
|
+
assertPlainTaskId(task);
|
|
64
61
|
const repoDir = repoDirFor(config, repository);
|
|
65
62
|
const hostWorktreeName = `${repository}-${task}`;
|
|
66
63
|
const hostWorktreeDir = path.resolve(worktreeBaseDir(config), hostWorktreeName);
|
|
@@ -75,6 +72,22 @@ function basePaths(config, repository, task) {
|
|
|
75
72
|
function signalProperty(signal) {
|
|
76
73
|
return signal === undefined ? {} : { signal };
|
|
77
74
|
}
|
|
75
|
+
function parseWorktreeDirectoryName(directoryName, repositoryEntriesByLongestName) {
|
|
76
|
+
// Match the longest repository basename first so overlapping names like
|
|
77
|
+
// "repo-a" and "repo-a-admin" parse to the intended repository.
|
|
78
|
+
for (const [repositoryBaseName, repository] of repositoryEntriesByLongestName) {
|
|
79
|
+
const worktreePrefix = `${repositoryBaseName}-`;
|
|
80
|
+
if (!directoryName.startsWith(worktreePrefix)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const task = directoryName.slice(worktreePrefix.length);
|
|
84
|
+
if (!isPlainTaskId(task)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
return { repository, task };
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
78
91
|
/**
|
|
79
92
|
* Runs a long-running git command (fetch, worktree add/remove/prune) with no
|
|
80
93
|
* timeout. Under --verbose the git porcelain streams live to the terminal;
|
|
@@ -147,6 +160,7 @@ function listWorktrees(config) {
|
|
|
147
160
|
repoByBasename.set(basename, repository);
|
|
148
161
|
}
|
|
149
162
|
for (const [parentDir, repoByBasename] of reposByParent) {
|
|
163
|
+
const repositoryEntriesByLongestName = [...repoByBasename.entries()].toSorted(([nameA], [nameB]) => nameB.length - nameA.length);
|
|
150
164
|
let children;
|
|
151
165
|
try {
|
|
152
166
|
children = readdirSync(parentDir, { withFileTypes: true });
|
|
@@ -158,23 +172,14 @@ function listWorktrees(config) {
|
|
|
158
172
|
if (!entry.isDirectory()) {
|
|
159
173
|
continue;
|
|
160
174
|
}
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
const [, repoBasename, task] = match;
|
|
166
|
-
/* v8 ignore next 3 @preserve -- TASK_DIR_RE always captures both groups when it matches */
|
|
167
|
-
if (repoBasename === undefined || task === undefined) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const repository = repoByBasename.get(repoBasename);
|
|
171
|
-
if (repository === undefined) {
|
|
175
|
+
const parsed = parseWorktreeDirectoryName(entry.name, repositoryEntriesByLongestName);
|
|
176
|
+
if (parsed === undefined) {
|
|
172
177
|
continue;
|
|
173
178
|
}
|
|
174
179
|
entries.push({
|
|
175
|
-
repository,
|
|
176
|
-
task,
|
|
177
|
-
branchName: branchNameForTask(config, task),
|
|
180
|
+
repository: parsed.repository,
|
|
181
|
+
task: parsed.task,
|
|
182
|
+
branchName: branchNameForTask(config, parsed.task),
|
|
178
183
|
dir: path.resolve(parentDir, entry.name),
|
|
179
184
|
kind: "host",
|
|
180
185
|
});
|
package/docs/commands.md
CHANGED
|
@@ -18,7 +18,7 @@ crew task get GC-20260608-001 --source todo
|
|
|
18
18
|
crew task get todo:GC-20260608-001 --prompt
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
`crew task create "Short title" --source <source> --agent <agent
|
|
21
|
+
`crew task create "Short title" --source <source> [--agent <agent>]` creates a task in a source that supports creation. When `--agent` is omitted, it defaults to `any`. Todo.txt creation appends the todo line, defaults to priority `A` unless `--priority` is provided, writes `.tasks/<id>.md`, and leaves `status:todo` as the final meaningful token, so no separate ready command is required. Hand-written todo-txt lines can omit `.tasks/<id>.md` when the line has a non-empty title; that title becomes the prompt text.
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
crew task create "Fix cancellation retry race" \
|
package/docs/task-sources.md
CHANGED
|
@@ -78,7 +78,7 @@ export default {
|
|
|
78
78
|
};
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Creating a todo task appends a line with `status:todo` as the final meaningful token and writes the prompt to `.tasks/<id>.md`. New todo tasks default to priority `A`; pass `--priority <letter>` to override it.
|
|
81
|
+
Creating a todo task appends a line with `status:todo` as the final meaningful token and writes the prompt to `.tasks/<id>.md`. New todo tasks default to priority `A`; pass `--priority <letter>` to override it. If `--agent` is omitted, the task uses `agent:any`.
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
crew task create "Fix cancellation retry race" \
|
|
@@ -94,6 +94,12 @@ crew task create "Fix cancellation retry race" \
|
|
|
94
94
|
(A) Fix cancellation retry race +marketplace @backend id:GC-20260608-001 repo:ClipboardHealth/api agent:codex status:todo
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
For hand-written todo lines, a non-empty title is enough prompt text when `.tasks/<id>.md` is absent. Omit `agent:` to default to `agent:any`:
|
|
98
|
+
|
|
99
|
+
```txt
|
|
100
|
+
Say goodbye repo:ClipboardHealth/groundcrew id:GC-20260608-002 status:todo
|
|
101
|
+
```
|
|
102
|
+
|
|
97
103
|
## Linear
|
|
98
104
|
|
|
99
105
|
The built-in Linear source supports listing, getting, writeback, and task creation through `crew task create`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/groundcrew",
|
|
3
|
-
"version": "4.24.
|
|
3
|
+
"version": "4.24.2",
|
|
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",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@nx/js": "22.7.5",
|
|
82
82
|
"@tsconfig/node24": "24.0.4",
|
|
83
83
|
"@tsconfig/strictest": "2.0.8",
|
|
84
|
-
"@types/node": "25.9.
|
|
84
|
+
"@types/node": "25.9.2",
|
|
85
85
|
"@typescript/native-preview": "7.0.0-dev.20260604.1",
|
|
86
86
|
"@vitest/coverage-v8": "4.1.8",
|
|
87
87
|
"cspell": "10.0.1",
|