@clipboard-health/groundcrew 4.38.0 → 4.39.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/dist/commands/task.d.ts.map +1 -1
- package/dist/commands/task.js +38 -40
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.js +11 -12
- package/dist/lib/board.d.ts +3 -2
- package/dist/lib/board.d.ts.map +1 -1
- package/dist/lib/board.js +23 -28
- package/dist/lib/taskResolution.d.ts +14 -0
- package/dist/lib/taskResolution.d.ts.map +1 -0
- package/dist/lib/taskResolution.js +53 -0
- package/dist/lib/taskSource.js +1 -1
- package/docs/commands.md +4 -3
- package/package.json +1 -1
|
@@ -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":"AAwyBA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB3D"}
|
package/dist/commands/task.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createBoard } from "../lib/board.js";
|
|
|
2
2
|
import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
|
|
3
3
|
import { AGENT_ANY, loadConfig } from "../lib/config.js";
|
|
4
4
|
import { findPullRequestsForBranch } from "../lib/pullRequests.js";
|
|
5
|
+
import { resolveTaskIdMatches } from "../lib/taskResolution.js";
|
|
5
6
|
import { naturalIdFromCanonical, } from "../lib/taskSource.js";
|
|
6
7
|
import { parseSourceFilterArgs, writeOutput } from "../lib/util.js";
|
|
7
8
|
import { worktrees } from "../lib/worktrees.js";
|
|
@@ -387,8 +388,28 @@ function canonicalParts(taskId) {
|
|
|
387
388
|
}
|
|
388
389
|
return { sourceName, naturalId };
|
|
389
390
|
}
|
|
390
|
-
|
|
391
|
-
|
|
391
|
+
function taskFromResolution({ taskId, resolution, sourceName }) {
|
|
392
|
+
if (resolution.matches.length === 0) {
|
|
393
|
+
if (resolution.rejections.length > 0) {
|
|
394
|
+
throw resolution.rejections[0];
|
|
395
|
+
}
|
|
396
|
+
if (sourceName !== undefined) {
|
|
397
|
+
throw new Error(`Task ${taskId} not found in source "${sourceName}".`);
|
|
398
|
+
}
|
|
399
|
+
throw new Error(`Task ${taskId} not found across configured sources.`);
|
|
400
|
+
}
|
|
401
|
+
if (resolution.matches.length > 1) {
|
|
402
|
+
if (resolution.matchKind === "exact" && sourceName === undefined) {
|
|
403
|
+
throw new Error(`Task id "${taskId}" matched multiple sources: ${resolution.matches.map((task) => task.id).join(", ")}. Re-run with a canonical id or --source <name>.`);
|
|
404
|
+
}
|
|
405
|
+
throw new Error(`Task id "${taskId}" matched multiple tasks: ${resolution.matches.map((task) => task.id).join(", ")}. Re-run with a longer prefix or canonical id.`);
|
|
406
|
+
}
|
|
407
|
+
const [match] = resolution.matches;
|
|
408
|
+
/* v8 ignore next 3 @preserve -- matches.length was checked above; guard exists for noUncheckedIndexedAccess */
|
|
409
|
+
if (match === undefined) {
|
|
410
|
+
throw new Error(`Task ${taskId} not found across configured sources.`);
|
|
411
|
+
}
|
|
412
|
+
return match;
|
|
392
413
|
}
|
|
393
414
|
async function resolveTask(sources, taskId, sourceName) {
|
|
394
415
|
const canonical = canonicalParts(taskId);
|
|
@@ -397,47 +418,24 @@ async function resolveTask(sources, taskId, sourceName) {
|
|
|
397
418
|
throw new Error(`crew task get: canonical id "${taskId}" already names source "${canonical.sourceName}"`);
|
|
398
419
|
}
|
|
399
420
|
const source = findSource(sources, canonical.sourceName);
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
421
|
+
return taskFromResolution({
|
|
422
|
+
taskId,
|
|
423
|
+
sourceName: source.name,
|
|
424
|
+
resolution: await resolveTaskIdMatches({ sources: [source], naturalId: canonical.naturalId }),
|
|
425
|
+
});
|
|
405
426
|
}
|
|
406
427
|
if (sourceName !== undefined) {
|
|
407
428
|
const source = findSource(sources, sourceName);
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (result.status === "fulfilled") {
|
|
419
|
-
if (result.value !== null) {
|
|
420
|
-
matches.push(result.value);
|
|
421
|
-
}
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
rejections.push(result.reason);
|
|
425
|
-
}
|
|
426
|
-
if (matches.length === 0) {
|
|
427
|
-
if (rejections.length > 0) {
|
|
428
|
-
throw rejections[0];
|
|
429
|
-
}
|
|
430
|
-
throw new Error(`Task ${taskId} not found across configured sources.`);
|
|
431
|
-
}
|
|
432
|
-
if (matches.length > 1) {
|
|
433
|
-
throw new Error(`Task id "${taskId}" matched multiple sources: ${matches.map((task) => task.id).join(", ")}. Re-run with a canonical id or --source <name>.`);
|
|
434
|
-
}
|
|
435
|
-
const [match] = matches;
|
|
436
|
-
/* v8 ignore next 3 @preserve -- matches.length was checked above; guard exists for noUncheckedIndexedAccess */
|
|
437
|
-
if (match === undefined) {
|
|
438
|
-
throw new Error(`Task ${taskId} not found across configured sources.`);
|
|
439
|
-
}
|
|
440
|
-
return match;
|
|
429
|
+
return taskFromResolution({
|
|
430
|
+
taskId,
|
|
431
|
+
sourceName: source.name,
|
|
432
|
+
resolution: await resolveTaskIdMatches({ sources: [source], naturalId: taskId }),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return taskFromResolution({
|
|
436
|
+
taskId,
|
|
437
|
+
resolution: await resolveTaskIdMatches({ sources, naturalId: taskId }),
|
|
438
|
+
});
|
|
441
439
|
}
|
|
442
440
|
function writeTaskDetails(task) {
|
|
443
441
|
writeOutput(task.id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAIL,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AAG1B;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,YAAY,EAAE,MAAM,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAIL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAIL,KAAK,KAAK,IAAI,WAAW,EAE1B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AAG1B;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,YAAY,EAAE,MAAM,CAAC;CACtB;AA8DD,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,iBAA+C,GAC3D,cAAc,CA4BhB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,cAAc,GACtB,UAAU,CAkIZ"}
|
|
@@ -23,17 +23,16 @@ function canonicalBlockerStatus(blocker, statusNames) {
|
|
|
23
23
|
stateType: blocker.stateType,
|
|
24
24
|
statusNames,
|
|
25
25
|
});
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
return status === "other"
|
|
27
|
+
? {
|
|
28
|
+
status,
|
|
29
|
+
statusReason: blocker.stateType === undefined ? "missing" : "unmapped",
|
|
30
|
+
...(blocker.status !== undefined && { nativeStatus: blocker.status }),
|
|
31
|
+
}
|
|
32
|
+
: {
|
|
28
33
|
status,
|
|
29
34
|
...(blocker.status !== undefined && { nativeStatus: blocker.status }),
|
|
30
35
|
};
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
status,
|
|
34
|
-
statusReason: blocker.stateType === undefined ? "missing" : "unmapped",
|
|
35
|
-
...(blocker.status !== undefined && { nativeStatus: blocker.status }),
|
|
36
|
-
};
|
|
37
36
|
}
|
|
38
37
|
function toCanonicalBlocker(blocker, sourceName, statusNames) {
|
|
39
38
|
const { status, statusReason, nativeStatus } = canonicalBlockerStatus(blocker, statusNames);
|
|
@@ -57,6 +56,9 @@ function isLinearNotFoundError(error, naturalId) {
|
|
|
57
56
|
error.message.startsWith(`Task ${naturalId.toUpperCase()} `) &&
|
|
58
57
|
error.message.includes("not found"));
|
|
59
58
|
}
|
|
59
|
+
function throwLinearLookupError(error) {
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
60
62
|
export function toCanonicalIssue(linearIssue, sourceName, statusNames = DEFAULT_LINEAR_STATUS_NAMES) {
|
|
61
63
|
const sourceRef = {
|
|
62
64
|
uuid: linearIssue.uuid,
|
|
@@ -127,10 +129,7 @@ export function createLinearTaskSource(config, context) {
|
|
|
127
129
|
});
|
|
128
130
|
}
|
|
129
131
|
catch (error) {
|
|
130
|
-
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
throw error;
|
|
132
|
+
return isLinearNotFoundError(error, naturalId) ? null : throwLinearLookupError(error);
|
|
134
133
|
}
|
|
135
134
|
const sourceRef = {
|
|
136
135
|
uuid: resolved.uuid,
|
package/dist/lib/board.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ export interface Board {
|
|
|
9
9
|
verify: () => Promise<void>;
|
|
10
10
|
fetch: () => Promise<BoardState>;
|
|
11
11
|
/**
|
|
12
|
-
* Accepts either canonical (`linear:eng-220`) or natural (`eng-220`) ids
|
|
13
|
-
* Natural ids fan out across
|
|
12
|
+
* Accepts either canonical (`linear:eng-220`) or natural (`eng-220`) ids,
|
|
13
|
+
* plus unique prefixes of current listed tasks. Natural ids fan out across
|
|
14
|
+
* sources; ambiguous matches throw.
|
|
14
15
|
*/
|
|
15
16
|
resolveOne: (canonicalOrNaturalId: string) => Promise<Issue | undefined>;
|
|
16
17
|
/** Routes to the adapter whose `name` matches `issue.source`. Unknown source throws. */
|
package/dist/lib/board.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IACjC;;;;OAIG;IACH,UAAU,EAAE,CAAC,oBAAoB,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACzE,wFAAwF;IACxF,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD;;;;OAIG;IACH,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC5D;;;;OAIG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACrD;AA0CD,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,KAAK,CA8FjE"}
|
package/dist/lib/board.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* back into consumers.
|
|
6
6
|
*/
|
|
7
7
|
import { AmbiguousTaskError, } from "./taskSource.js";
|
|
8
|
+
import { resolveTaskIdMatches } from "./taskResolution.js";
|
|
8
9
|
async function callVerify(source) {
|
|
9
10
|
await source.verify();
|
|
10
11
|
}
|
|
@@ -17,8 +18,21 @@ async function callFetchParentSkips(source) {
|
|
|
17
18
|
}
|
|
18
19
|
return [];
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
function uniqueResolvedIssue({ idArgument, resolution, }) {
|
|
22
|
+
if (resolution.matches.length === 0) {
|
|
23
|
+
if (resolution.rejections.length > 0) {
|
|
24
|
+
throw resolution.rejections[0];
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (resolution.matches.length === 1) {
|
|
29
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- length checked above
|
|
30
|
+
return resolution.matches[0];
|
|
31
|
+
}
|
|
32
|
+
throw new AmbiguousTaskError({
|
|
33
|
+
naturalId: idArgument,
|
|
34
|
+
matches: resolution.matches.map((match) => match.id),
|
|
35
|
+
});
|
|
22
36
|
}
|
|
23
37
|
export function createBoard(sources) {
|
|
24
38
|
const byName = new Map();
|
|
@@ -69,7 +83,10 @@ export function createBoard(sources) {
|
|
|
69
83
|
if (!source) {
|
|
70
84
|
throw new Error(`unknown source "${sourceName}" in canonical id "${idArgument}"`);
|
|
71
85
|
}
|
|
72
|
-
return
|
|
86
|
+
return uniqueResolvedIssue({
|
|
87
|
+
idArgument,
|
|
88
|
+
resolution: await resolveTaskIdMatches({ sources: [source], naturalId }),
|
|
89
|
+
});
|
|
73
90
|
}
|
|
74
91
|
// Per-source resolveOne errors must not poison sibling resolutions.
|
|
75
92
|
// A source that rejects on a natural-id lookup is treated as "I don't
|
|
@@ -78,31 +95,9 @@ export function createBoard(sources) {
|
|
|
78
95
|
// the rejection — so the user sees a real Linear/network error when
|
|
79
96
|
// there's no fallback, but a stray "not found" from one source doesn't
|
|
80
97
|
// mask a successful match from another.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
for (const result of results) {
|
|
85
|
-
if (result.status === "rejected") {
|
|
86
|
-
rejections.push(result.reason);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (result.value !== undefined) {
|
|
90
|
-
matches.push(result.value);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (matches.length === 0) {
|
|
94
|
-
if (rejections.length > 0) {
|
|
95
|
-
throw rejections[0];
|
|
96
|
-
}
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
if (matches.length === 1) {
|
|
100
|
-
// oxlint-disable-next-line typescript/no-non-null-assertion -- length checked above
|
|
101
|
-
return matches[0];
|
|
102
|
-
}
|
|
103
|
-
throw new AmbiguousTaskError({
|
|
104
|
-
naturalId: idArgument,
|
|
105
|
-
matches: matches.map((m) => m.id),
|
|
98
|
+
return uniqueResolvedIssue({
|
|
99
|
+
idArgument,
|
|
100
|
+
resolution: await resolveTaskIdMatches({ sources, naturalId: idArgument }),
|
|
106
101
|
});
|
|
107
102
|
},
|
|
108
103
|
async markInProgress(issue) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Task, type TaskSource } from "./taskSource.ts";
|
|
2
|
+
type TaskMatchKind = "exact" | "prefix" | "none";
|
|
3
|
+
export interface TaskResolutionMatches {
|
|
4
|
+
matches: Task[];
|
|
5
|
+
rejections: unknown[];
|
|
6
|
+
matchKind: TaskMatchKind;
|
|
7
|
+
}
|
|
8
|
+
interface CollectExactTaskMatchesArguments {
|
|
9
|
+
sources: readonly TaskSource[];
|
|
10
|
+
naturalId: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveTaskIdMatches(arguments_: CollectExactTaskMatchesArguments): Promise<TaskResolutionMatches>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=taskResolution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taskResolution.d.ts","sourceRoot":"","sources":["../../src/lib/taskResolution.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAErF,KAAK,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,IAAI,EAAE,CAAC;IAChB,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,SAAS,EAAE,aAAa,CAAC;CAC1B;AAED,UAAU,gCAAgC;IACxC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAYD,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,gCAAgC,GAC3C,OAAO,CAAC,qBAAqB,CAAC,CAehC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { naturalIdFromCanonical } from "./taskSource.js";
|
|
2
|
+
export async function resolveTaskIdMatches(arguments_) {
|
|
3
|
+
const exact = await collectExactTaskMatches(arguments_);
|
|
4
|
+
if (exact.matches.length > 0) {
|
|
5
|
+
return { ...exact, matchKind: "exact" };
|
|
6
|
+
}
|
|
7
|
+
const prefix = await collectPrefixTaskMatches({
|
|
8
|
+
sources: arguments_.sources,
|
|
9
|
+
naturalIdPrefix: arguments_.naturalId,
|
|
10
|
+
});
|
|
11
|
+
const rejections = [...exact.rejections, ...prefix.rejections];
|
|
12
|
+
if (prefix.matches.length > 0) {
|
|
13
|
+
return { matches: prefix.matches, rejections, matchKind: "prefix" };
|
|
14
|
+
}
|
|
15
|
+
return { matches: [], rejections, matchKind: "none" };
|
|
16
|
+
}
|
|
17
|
+
async function collectExactTaskMatches({ sources, naturalId, }) {
|
|
18
|
+
const results = await Promise.allSettled(sources.map(async (source) => await source.getTask(naturalId)));
|
|
19
|
+
const matches = [];
|
|
20
|
+
const rejections = [];
|
|
21
|
+
for (const result of results) {
|
|
22
|
+
if (result.status === "fulfilled") {
|
|
23
|
+
if (result.value !== null) {
|
|
24
|
+
matches.push(result.value);
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
rejections.push(result.reason);
|
|
29
|
+
}
|
|
30
|
+
return { matches, rejections };
|
|
31
|
+
}
|
|
32
|
+
async function collectPrefixTaskMatches({ sources, naturalIdPrefix, }) {
|
|
33
|
+
const results = await Promise.allSettled(sources.map(async (source) => {
|
|
34
|
+
const tasks = await source.listTasks();
|
|
35
|
+
return tasks.filter((task) => taskMatchesNaturalIdPrefix({ task, naturalIdPrefix }));
|
|
36
|
+
}));
|
|
37
|
+
const matches = [];
|
|
38
|
+
const rejections = [];
|
|
39
|
+
for (const result of results) {
|
|
40
|
+
if (result.status === "fulfilled") {
|
|
41
|
+
matches.push(...result.value);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
rejections.push(result.reason);
|
|
45
|
+
}
|
|
46
|
+
return { matches, rejections };
|
|
47
|
+
}
|
|
48
|
+
function taskMatchesNaturalIdPrefix({ task, naturalIdPrefix, }) {
|
|
49
|
+
if (naturalIdPrefix.length === 0) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return naturalIdFromCanonical(task.id).toLowerCase().startsWith(naturalIdPrefix.toLowerCase());
|
|
53
|
+
}
|
package/dist/lib/taskSource.js
CHANGED
|
@@ -36,7 +36,7 @@ export class TaskSourceOutputError extends Error {
|
|
|
36
36
|
export class AmbiguousTaskError extends Error {
|
|
37
37
|
constructor(arguments_) {
|
|
38
38
|
const { naturalId, matches } = arguments_;
|
|
39
|
-
super(`Task id "${naturalId}" is ambiguous; matched
|
|
39
|
+
super(`Task id "${naturalId}" is ambiguous; matched multiple tasks: ${matches.join(", ")}. Re-invoke with a longer prefix or one of those canonical ids.`);
|
|
40
40
|
this.name = "AmbiguousTaskError";
|
|
41
41
|
}
|
|
42
42
|
}
|
package/docs/commands.md
CHANGED
|
@@ -10,7 +10,7 @@ crew task list --source todo --status todo --unblocked
|
|
|
10
10
|
crew task list --agent claude-fable --repo ClipboardHealth/api --json
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
`crew task get <task-id>` prints one normalized task. Canonical IDs such as `todo:GC-20260608-001` route directly to the named source. Natural IDs can be resolved with `--source <name>` or, when unique, by searching all configured sources. If more than one
|
|
13
|
+
`crew task get <task-id>` prints one normalized task. Canonical IDs such as `todo:GC-20260608-001` route directly to the named source. Natural IDs can be resolved with `--source <name>` or, when unique, by searching all configured sources. Exact IDs are tried first; if none match, Groundcrew accepts a unique prefix of a current listed task ID. If more than one task matches, the command fails and prints the matching canonical IDs.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
crew task get todo:GC-20260608-001
|
|
@@ -44,8 +44,9 @@ crew task create "Fix cancellation retry race" \
|
|
|
44
44
|
`crew task done <task-id>` marks one task done through its source adapter. Use
|
|
45
45
|
it for completed work that intentionally does not produce a PR. The command
|
|
46
46
|
resolves canonical IDs such as `todo:flaky-triage-1` directly, or natural IDs
|
|
47
|
-
when they match exactly one configured source.
|
|
48
|
-
|
|
47
|
+
when they match exactly one configured source. Exact IDs are tried first; if
|
|
48
|
+
none match, Groundcrew accepts a unique prefix of a current listed task ID.
|
|
49
|
+
Sources without a done writeback return an unsupported error.
|
|
49
50
|
|
|
50
51
|
Groundcrew checks matching local worktrees before marking a task done. Clean
|
|
51
52
|
worktrees, and tasks with no local worktree, are allowed. A dirty worktree with
|
package/package.json
CHANGED