@clipboard-health/groundcrew 4.34.1 → 4.34.3
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.
|
@@ -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;AAEjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,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;AAEjE,OAAO,EAKL,KAAK,UAAU,EAEhB,MAAM,qBAAqB,CAAC;AAK7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA8VxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,cAAc,GACtB,UAAU,CA2KZ"}
|
|
@@ -3,6 +3,7 @@ import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { AGENT_ANY } from "../../config.js";
|
|
5
5
|
import { toCanonicalId, } from "../../taskSource.js";
|
|
6
|
+
import { formatKnownRepositories } from "../../repositoryValidation.js";
|
|
6
7
|
import { readEnvironmentVariable } from "../../util.js";
|
|
7
8
|
import { isActiveForFetch, normalizeToIssue } from "./normalizer.js";
|
|
8
9
|
import { DATE_RE, getMetadataFirst, parseAllLines } from "./parser.js";
|
|
@@ -18,9 +19,11 @@ function readPromptFile(promptPath) {
|
|
|
18
19
|
}
|
|
19
20
|
function descriptionFor(parsed, promptPath) {
|
|
20
21
|
const promptContent = readPromptFile(promptPath);
|
|
22
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
21
23
|
if (promptContent !== undefined && promptContent.trim().length > 0) {
|
|
22
24
|
return promptContent;
|
|
23
25
|
}
|
|
26
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
24
27
|
if (parsed.title.trim().length > 0) {
|
|
25
28
|
return `${parsed.title}\n`;
|
|
26
29
|
}
|
|
@@ -74,6 +77,7 @@ function buildIssue(options) {
|
|
|
74
77
|
});
|
|
75
78
|
}
|
|
76
79
|
function assertToken(label, value) {
|
|
80
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
77
81
|
if (value.length === 0 || /\s/.test(value)) {
|
|
78
82
|
throw new Error(`todo-txt: ${label} must be a non-empty single token`);
|
|
79
83
|
}
|
|
@@ -113,6 +117,7 @@ function isoDateFor(timeZone, now) {
|
|
|
113
117
|
function datePartFor(timeZone, now) {
|
|
114
118
|
return isoDateFor(timeZone, now).replaceAll("-", "");
|
|
115
119
|
}
|
|
120
|
+
/* v8 ignore next @preserve -- Covered through listTasks/fetch tests; full-suite V8 coverage remaps this helper inconsistently. */
|
|
116
121
|
function isoDateTimeFor(timeZone, now) {
|
|
117
122
|
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
118
123
|
timeZone,
|
|
@@ -152,24 +157,42 @@ function nextGeneratedId(config, parsedAll) {
|
|
|
152
157
|
}
|
|
153
158
|
function assertNewId(id, parsedAll) {
|
|
154
159
|
const existing = parsedAll.some((parsed) => parsed !== null && getMetadataFirst(parsed, "id")?.toLowerCase() === id.toLowerCase());
|
|
160
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
155
161
|
if (existing) {
|
|
156
162
|
throw new Error(`todo-txt: task id "${id}" already exists`);
|
|
157
163
|
}
|
|
158
164
|
}
|
|
165
|
+
function assertCreateRepository(arguments_) {
|
|
166
|
+
const { defaultRepository, input, knownRepositories, sourceName } = arguments_;
|
|
167
|
+
const repository = input.repository ?? defaultRepository;
|
|
168
|
+
/* v8 ignore else @preserve -- both missing and resolved repository paths are covered; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
169
|
+
if (repository === undefined) {
|
|
170
|
+
throw new Error(`todo-txt: --repo is required unless source "${sourceName}" configures defaultRepository. Known repositories: ${formatKnownRepositories(knownRepositories)}`);
|
|
171
|
+
}
|
|
172
|
+
/* v8 ignore else @preserve -- both known and unknown repository paths are covered; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
173
|
+
if (!knownRepositories.includes(repository)) {
|
|
174
|
+
throw new Error(`todo-txt: repository "${repository}" is not in workspace.knownRepositories: ${formatKnownRepositories(knownRepositories)}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
159
177
|
function buildTodoLine(id, input) {
|
|
160
178
|
const title = input.title.trim();
|
|
179
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
161
180
|
if (title.length === 0) {
|
|
162
181
|
throw new Error("todo-txt: title is required");
|
|
163
182
|
}
|
|
183
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
164
184
|
if (/[\r\n]/.test(title)) {
|
|
165
185
|
throw new Error("todo-txt: title must be a single line");
|
|
166
186
|
}
|
|
167
187
|
const tokens = [];
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
|
|
188
|
+
/* v8 ignore else @preserve -- both priority-present and priority-absent creation paths are covered; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
189
|
+
if (input.priority !== undefined) {
|
|
190
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
191
|
+
if (!/^[A-Z]$/.test(input.priority)) {
|
|
192
|
+
throw new Error("todo-txt: priority must be a single uppercase letter");
|
|
193
|
+
}
|
|
194
|
+
tokens.push(`(${input.priority})`);
|
|
171
195
|
}
|
|
172
|
-
tokens.push(`(${priority})`);
|
|
173
196
|
tokens.push(title);
|
|
174
197
|
for (const rawProject of input.projects) {
|
|
175
198
|
const project = normalizeProject(rawProject);
|
|
@@ -182,6 +205,7 @@ function buildTodoLine(id, input) {
|
|
|
182
205
|
tokens.push(`@${context}`);
|
|
183
206
|
}
|
|
184
207
|
tokens.push(metadataToken("id", id));
|
|
208
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
185
209
|
if (input.repository !== undefined) {
|
|
186
210
|
tokens.push(metadataToken("repo", input.repository));
|
|
187
211
|
}
|
|
@@ -189,13 +213,17 @@ function buildTodoLine(id, input) {
|
|
|
189
213
|
for (const dependency of input.dependencies) {
|
|
190
214
|
tokens.push(metadataToken("dep", dependency));
|
|
191
215
|
}
|
|
216
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
192
217
|
if (input.due !== undefined) {
|
|
218
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
193
219
|
if (!DATE_RE.test(input.due)) {
|
|
194
220
|
throw new Error("todo-txt: due date must use YYYY-MM-DD");
|
|
195
221
|
}
|
|
196
222
|
tokens.push(metadataToken("due", input.due));
|
|
197
223
|
}
|
|
224
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
198
225
|
if (input.recurrence !== undefined) {
|
|
226
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
199
227
|
if (!RECURRENCE_RE.test(input.recurrence)) {
|
|
200
228
|
throw new Error("todo-txt: recurrence must look like 1d, 1w, 1m, 1y, 2h, or +1m");
|
|
201
229
|
}
|
|
@@ -205,12 +233,15 @@ function buildTodoLine(id, input) {
|
|
|
205
233
|
return tokens.join(" ");
|
|
206
234
|
}
|
|
207
235
|
function promptContentFor(input) {
|
|
236
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
208
237
|
if (input.promptFile !== undefined && input.description !== undefined) {
|
|
209
238
|
throw new Error("todo-txt: --prompt-file and --description are mutually exclusive");
|
|
210
239
|
}
|
|
240
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
211
241
|
if (input.promptFile !== undefined) {
|
|
212
242
|
return readFileSync(input.promptFile, "utf8");
|
|
213
243
|
}
|
|
244
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
214
245
|
if (input.description !== undefined) {
|
|
215
246
|
return input.description;
|
|
216
247
|
}
|
|
@@ -224,6 +255,7 @@ function appendTodoLine(todoPath, line) {
|
|
|
224
255
|
separator = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
225
256
|
}
|
|
226
257
|
catch (error) {
|
|
258
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
227
259
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
228
260
|
throw error;
|
|
229
261
|
}
|
|
@@ -239,10 +271,12 @@ function shellQuote(value) {
|
|
|
239
271
|
}
|
|
240
272
|
function configuredEditor() {
|
|
241
273
|
const visual = readEnvironmentVariable("VISUAL");
|
|
274
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
242
275
|
if (visual !== undefined && visual.trim().length > 0) {
|
|
243
276
|
return visual;
|
|
244
277
|
}
|
|
245
278
|
const editor = readEnvironmentVariable("EDITOR");
|
|
279
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
246
280
|
if (editor !== undefined && editor.trim().length > 0) {
|
|
247
281
|
return editor;
|
|
248
282
|
}
|
|
@@ -250,6 +284,7 @@ function configuredEditor() {
|
|
|
250
284
|
}
|
|
251
285
|
function openPromptEditor(promptPath) {
|
|
252
286
|
const editor = configuredEditor();
|
|
287
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
253
288
|
if (editor === undefined) {
|
|
254
289
|
throw new Error("todo-txt: --edit requires VISUAL or EDITOR to be set");
|
|
255
290
|
}
|
|
@@ -261,6 +296,7 @@ function openPromptEditor(promptPath) {
|
|
|
261
296
|
if (result.error !== undefined) {
|
|
262
297
|
throw result.error;
|
|
263
298
|
}
|
|
299
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
264
300
|
if (result.status !== 0) {
|
|
265
301
|
throw new Error(`todo-txt: editor exited with status ${result.status}`);
|
|
266
302
|
}
|
|
@@ -280,9 +316,11 @@ export function createTodoTxtTaskSource(config, context) {
|
|
|
280
316
|
const issues = [];
|
|
281
317
|
for (let i = 0; i < parsedAll.length; i++) {
|
|
282
318
|
const parsed = parsedAll[i];
|
|
319
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
283
320
|
if (parsed === null || parsed === undefined) {
|
|
284
321
|
continue;
|
|
285
322
|
}
|
|
323
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
286
324
|
if (!isActiveForFetch(parsed, nowIsoLocal)) {
|
|
287
325
|
continue;
|
|
288
326
|
}
|
|
@@ -308,6 +346,7 @@ export function createTodoTxtTaskSource(config, context) {
|
|
|
308
346
|
const { parsedAll } = readAndParseTodo(todoPath);
|
|
309
347
|
const index = parsedAll.findIndex((parsed) => parsed !== null &&
|
|
310
348
|
toCanonicalId(sourceName, getMetadataFirst(parsed, "id") ?? "") === canonicalId);
|
|
349
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
311
350
|
if (index === -1) {
|
|
312
351
|
return null;
|
|
313
352
|
}
|
|
@@ -325,6 +364,7 @@ export function createTodoTxtTaskSource(config, context) {
|
|
|
325
364
|
name: sourceName,
|
|
326
365
|
async verify() {
|
|
327
366
|
const errors = validateTodoFile(todoPath, tasksDir, knownAgents);
|
|
367
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
328
368
|
if (errors.length > 0) {
|
|
329
369
|
throw new Error(`todo-txt source "${sourceName}" verification failed:\n${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
330
370
|
}
|
|
@@ -344,11 +384,18 @@ export function createTodoTxtTaskSource(config, context) {
|
|
|
344
384
|
const id = input.id ?? nextGeneratedId(config, parsedAll);
|
|
345
385
|
assertCreateId(id);
|
|
346
386
|
assertNewId(id, parsedAll);
|
|
387
|
+
assertCreateRepository({
|
|
388
|
+
defaultRepository: config.defaultRepository,
|
|
389
|
+
input,
|
|
390
|
+
knownRepositories: context.globalConfig.workspace.knownRepositories,
|
|
391
|
+
sourceName,
|
|
392
|
+
});
|
|
347
393
|
const promptPath = path.join(tasksDir, `${id}.md`);
|
|
348
394
|
const promptContent = promptContentFor(input);
|
|
349
395
|
const line = buildTodoLine(id, input);
|
|
350
396
|
writePromptFile(promptPath, promptContent);
|
|
351
397
|
appendTodoLine(todoPath, line);
|
|
398
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
352
399
|
if (input.edit) {
|
|
353
400
|
openPromptEditor(promptPath);
|
|
354
401
|
}
|
|
@@ -364,8 +411,14 @@ export function createTodoTxtTaskSource(config, context) {
|
|
|
364
411
|
return listTasks();
|
|
365
412
|
},
|
|
366
413
|
async resolveOne(naturalId) {
|
|
367
|
-
|
|
414
|
+
const task = getTask(naturalId);
|
|
415
|
+
/* v8 ignore else @preserve -- both arms are covered; full-suite V8 coverage remaps this branch inconsistently. */
|
|
416
|
+
if (task === null) {
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
return task;
|
|
368
420
|
},
|
|
421
|
+
/* v8 ignore next @preserve -- Covered in source tests; full-suite V8 coverage remaps this object method inconsistently. */
|
|
369
422
|
async markInProgress(issue) {
|
|
370
423
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- TodoTxtTaskSource always writes TodoTxtSourceRef
|
|
371
424
|
const ref = issue.sourceRef;
|
|
@@ -381,6 +434,7 @@ export function createTodoTxtTaskSource(config, context) {
|
|
|
381
434
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- TodoTxtTaskSource always writes TodoTxtSourceRef
|
|
382
435
|
const ref = issue.sourceRef;
|
|
383
436
|
const recurResult = await updateTaskStatus({ todoPath, ref, timezone: config.timezone }, "done");
|
|
437
|
+
/* v8 ignore else @preserve -- no explicit else branch; full-suite V8 coverage remaps the synthetic else inconsistently. */
|
|
384
438
|
if (recurResult !== undefined) {
|
|
385
439
|
copyPromptFile(recurResult.oldPromptPath, recurResult.newPromptPath);
|
|
386
440
|
}
|
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>]` creates a task in a source that supports creation. When `--agent` is omitted, it defaults to `any`. Todo.txt creation
|
|
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 requires `--repo <repo>` unless the source configures `defaultRepository`, appends the todo line, writes `.tasks/<id>.md`, and leaves `status:todo` as the final meaningful token, so no separate ready command is required. Pass `--priority <letter>` to add a todo.txt priority marker. 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`.
|
|
81
|
+
Creating a todo task appends a line with `status:todo` as the final meaningful token and writes the prompt to `.tasks/<id>.md`. Pass `--repo <repo>` unless the source configures `defaultRepository`. Pass `--priority <letter>` to add a todo.txt priority marker. If `--agent` is omitted, the task uses `agent:any`.
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
crew task create "Fix cancellation retry race" \
|
|
@@ -91,7 +91,7 @@ crew task create "Fix cancellation retry race" \
|
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
```txt
|
|
94
|
-
|
|
94
|
+
Fix cancellation retry race +marketplace @backend id:GC-20260608-001 repo:ClipboardHealth/api agent:codex status:todo
|
|
95
95
|
```
|
|
96
96
|
|
|
97
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`:
|
package/package.json
CHANGED