@clipboard-health/groundcrew 4.20.2 → 4.22.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 +3 -0
- package/crew.config.example.ts +10 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/commands/init.js +5 -3
- package/dist/commands/orchestrator.d.ts.map +1 -1
- package/dist/commands/orchestrator.js +20 -7
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -4
- package/dist/commands/task.d.ts +2 -0
- package/dist/commands/task.d.ts.map +1 -0
- package/dist/commands/task.js +488 -0
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.js +61 -34
- package/dist/lib/adapters/linear/fetch.d.ts +9 -0
- package/dist/lib/adapters/linear/fetch.d.ts.map +1 -1
- package/dist/lib/adapters/linear/fetch.js +11 -0
- package/dist/lib/adapters/shell/factory.d.ts.map +1 -1
- package/dist/lib/adapters/shell/factory.js +31 -22
- package/dist/lib/adapters/todo-txt/source.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/source.js +230 -21
- package/dist/lib/adapters/todo-txt/writeback.d.ts +1 -0
- package/dist/lib/adapters/todo-txt/writeback.d.ts.map +1 -1
- package/dist/lib/adapters/todo-txt/writeback.js +1 -1
- package/dist/lib/board.d.ts +1 -1
- package/dist/lib/board.js +3 -3
- package/dist/lib/buildSources.d.ts +7 -18
- package/dist/lib/buildSources.d.ts.map +1 -1
- package/dist/lib/buildSources.js +9 -48
- package/dist/lib/sourceCapabilities.js +1 -1
- package/dist/lib/taskSource.d.ts +36 -0
- package/dist/lib/taskSource.d.ts.map +1 -1
- package/docs/commands.md +30 -0
- package/docs/task-sources.md +42 -5
- package/package.json +1 -1
|
@@ -51,6 +51,11 @@ function toCanonicalParentSkip(skip, sourceName) {
|
|
|
51
51
|
childCount: skip.childCount,
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
function isLinearNotFoundError(error, naturalId) {
|
|
55
|
+
return (error instanceof Error &&
|
|
56
|
+
error.message.startsWith(`Task ${naturalId.toUpperCase()} `) &&
|
|
57
|
+
error.message.includes("not found"));
|
|
58
|
+
}
|
|
54
59
|
export function toCanonicalIssue(linearIssue, sourceName, statusNames = DEFAULT_LINEAR_STATUS_NAMES) {
|
|
55
60
|
const sourceRef = {
|
|
56
61
|
uuid: linearIssue.uuid,
|
|
@@ -106,51 +111,73 @@ export function createLinearTaskSource(config, context) {
|
|
|
106
111
|
return cachedIssueStatusUpdater;
|
|
107
112
|
}
|
|
108
113
|
let lastParentSkips = [];
|
|
114
|
+
async function listTasks() {
|
|
115
|
+
const state = await getBoardSource().fetch();
|
|
116
|
+
lastParentSkips = state.parentSkips.map((skip) => toCanonicalParentSkip(skip, sourceName));
|
|
117
|
+
return state.issues.map((linearIssue) => toCanonicalIssue(linearIssue, sourceName, statusNames));
|
|
118
|
+
}
|
|
119
|
+
async function getTask(naturalId) {
|
|
120
|
+
let resolved;
|
|
121
|
+
try {
|
|
122
|
+
resolved = await fetchResolvedIssue({
|
|
123
|
+
client: getClient(),
|
|
124
|
+
config: globalConfig,
|
|
125
|
+
task: naturalId,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (isLinearNotFoundError(error, naturalId)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
const sourceRef = {
|
|
135
|
+
uuid: resolved.uuid,
|
|
136
|
+
statusId: resolved.statusId,
|
|
137
|
+
teamId: resolved.teamId,
|
|
138
|
+
stateType: resolved.stateType,
|
|
139
|
+
nativeStatus: resolved.status,
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
id: toCanonicalId(sourceName, naturalId),
|
|
143
|
+
source: sourceName,
|
|
144
|
+
title: resolved.title,
|
|
145
|
+
description: resolved.description,
|
|
146
|
+
status: canonicalStatusFromLinearState({
|
|
147
|
+
nativeStatus: resolved.status,
|
|
148
|
+
stateType: resolved.stateType,
|
|
149
|
+
statusNames,
|
|
150
|
+
}),
|
|
151
|
+
repository: resolved.repository,
|
|
152
|
+
model: resolved.model,
|
|
153
|
+
assignee: resolved.assignee,
|
|
154
|
+
updatedAt: resolved.updatedAt,
|
|
155
|
+
blockers: resolved.blockers.map((b) => toCanonicalBlocker(b, sourceName, statusNames)),
|
|
156
|
+
hasMoreBlockers: resolved.hasMoreBlockers,
|
|
157
|
+
url: resolved.url,
|
|
158
|
+
...(resolved.priority === 0 ? {} : { priority: resolved.priority }),
|
|
159
|
+
sourceRef,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
109
162
|
return {
|
|
110
163
|
name: sourceName,
|
|
111
164
|
async verify() {
|
|
112
165
|
await getBoardSource().verify();
|
|
113
166
|
},
|
|
167
|
+
async listTasks() {
|
|
168
|
+
return await listTasks();
|
|
169
|
+
},
|
|
170
|
+
async getTask(naturalId) {
|
|
171
|
+
return await getTask(naturalId);
|
|
172
|
+
},
|
|
114
173
|
async fetch() {
|
|
115
|
-
|
|
116
|
-
lastParentSkips = state.parentSkips.map((skip) => toCanonicalParentSkip(skip, sourceName));
|
|
117
|
-
return state.issues.map((linearIssue) => toCanonicalIssue(linearIssue, sourceName, statusNames));
|
|
174
|
+
return await listTasks();
|
|
118
175
|
},
|
|
119
176
|
async fetchParentSkips() {
|
|
120
177
|
return lastParentSkips;
|
|
121
178
|
},
|
|
122
179
|
async resolveOne(naturalId) {
|
|
123
|
-
|
|
124
|
-
client: getClient(),
|
|
125
|
-
config: globalConfig,
|
|
126
|
-
task: naturalId,
|
|
127
|
-
});
|
|
128
|
-
const sourceRef = {
|
|
129
|
-
uuid: resolved.uuid,
|
|
130
|
-
statusId: resolved.statusId,
|
|
131
|
-
teamId: resolved.teamId,
|
|
132
|
-
stateType: resolved.stateType,
|
|
133
|
-
nativeStatus: resolved.status,
|
|
134
|
-
};
|
|
135
|
-
return {
|
|
136
|
-
id: toCanonicalId(sourceName, naturalId),
|
|
137
|
-
source: sourceName,
|
|
138
|
-
title: resolved.title,
|
|
139
|
-
description: resolved.description,
|
|
140
|
-
status: canonicalStatusFromLinearState({
|
|
141
|
-
nativeStatus: resolved.status,
|
|
142
|
-
stateType: resolved.stateType,
|
|
143
|
-
statusNames,
|
|
144
|
-
}),
|
|
145
|
-
repository: resolved.repository,
|
|
146
|
-
model: resolved.model,
|
|
147
|
-
assignee: "Unassigned",
|
|
148
|
-
updatedAt: new Date().toISOString(),
|
|
149
|
-
blockers: [],
|
|
150
|
-
hasMoreBlockers: false,
|
|
151
|
-
url: resolved.url,
|
|
152
|
-
sourceRef,
|
|
153
|
-
};
|
|
180
|
+
return (await getTask(naturalId)) ?? undefined;
|
|
154
181
|
},
|
|
155
182
|
async markInProgress(issue) {
|
|
156
183
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- by the Linear adapter's contract, every Issue it produces carries a LinearSourceRef in sourceRef
|
|
@@ -116,7 +116,12 @@ interface ResolvedIssue {
|
|
|
116
116
|
stateType: string;
|
|
117
117
|
status: string;
|
|
118
118
|
statusId: string;
|
|
119
|
+
assignee: string;
|
|
120
|
+
updatedAt: string;
|
|
121
|
+
blockers: Blocker[];
|
|
122
|
+
hasMoreBlockers: boolean;
|
|
119
123
|
url: string;
|
|
124
|
+
priority: number;
|
|
120
125
|
}
|
|
121
126
|
export interface RawLinearIssue {
|
|
122
127
|
uuid: string;
|
|
@@ -130,6 +135,8 @@ export interface RawLinearIssue {
|
|
|
130
135
|
stateName: string;
|
|
131
136
|
stateType: string;
|
|
132
137
|
stateId: string;
|
|
138
|
+
assignee: string;
|
|
139
|
+
updatedAt: string;
|
|
133
140
|
blockers: Blocker[];
|
|
134
141
|
hasMoreBlockers: boolean;
|
|
135
142
|
/**
|
|
@@ -141,6 +148,8 @@ export interface RawLinearIssue {
|
|
|
141
148
|
hasChildren: boolean;
|
|
142
149
|
/** Linear `Issue.url` — direct web link to the task. */
|
|
143
150
|
url: string;
|
|
151
|
+
/** Linear priority: 1=Urgent, 2=High, 3=Medium, 4=Low, 0=No priority. */
|
|
152
|
+
priority: number;
|
|
144
153
|
}
|
|
145
154
|
export declare function fetchBlockersForTask(arguments_: {
|
|
146
155
|
client: LinearClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/fetch.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/linear/fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -260,9 +260,12 @@ export async function fetchRawLinearIssue(arguments_) {
|
|
|
260
260
|
id
|
|
261
261
|
title
|
|
262
262
|
description
|
|
263
|
+
updatedAt
|
|
263
264
|
url
|
|
265
|
+
priority
|
|
264
266
|
team { id }
|
|
265
267
|
state { id name type }
|
|
268
|
+
assignee { name }
|
|
266
269
|
children { nodes { id } }
|
|
267
270
|
labels(first: ${ISSUE_LABEL_PAGE_SIZE}) {
|
|
268
271
|
nodes { name }
|
|
@@ -298,10 +301,13 @@ export async function fetchRawLinearIssue(arguments_) {
|
|
|
298
301
|
stateType: issue.state?.type ?? "",
|
|
299
302
|
/* v8 ignore next @preserve -- ResolveIssue query selects state; null only if Linear genuinely returns a stateless task */
|
|
300
303
|
stateId: issue.state?.id ?? "",
|
|
304
|
+
assignee: issue.assignee?.name ?? "Unassigned",
|
|
305
|
+
updatedAt: issue.updatedAt,
|
|
301
306
|
blockers: blockersFromRelations(issue.inverseRelations?.nodes ?? []),
|
|
302
307
|
hasMoreBlockers: issue.inverseRelations?.pageInfo.hasNextPage ?? false,
|
|
303
308
|
hasChildren: (issue.children?.nodes.length ?? 0) > 0,
|
|
304
309
|
url: issue.url,
|
|
310
|
+
priority: issue.priority,
|
|
305
311
|
};
|
|
306
312
|
}
|
|
307
313
|
export async function fetchInProgressIssueCount(arguments_) {
|
|
@@ -378,7 +384,12 @@ export async function fetchResolvedIssue(arguments_) {
|
|
|
378
384
|
stateType: raw.stateType,
|
|
379
385
|
status: raw.stateName,
|
|
380
386
|
statusId: raw.stateId,
|
|
387
|
+
assignee: raw.assignee,
|
|
388
|
+
updatedAt: raw.updatedAt,
|
|
389
|
+
blockers: raw.blockers,
|
|
390
|
+
hasMoreBlockers: raw.hasMoreBlockers,
|
|
381
391
|
url: raw.url,
|
|
392
|
+
priority: raw.priority,
|
|
382
393
|
};
|
|
383
394
|
}
|
|
384
395
|
export function warnIfNotEnabledFallback(task, modelResolution, config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAGL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAkErB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB3F;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,UAAU,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/shell/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAGL,KAAK,KAAK,IAAI,cAAc,EAG5B,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAkErB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,cAAc,CAuB3F;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,cAAc,GACvB,UAAU,CAyHZ"}
|
|
@@ -101,6 +101,30 @@ export function createShellTaskSource(config, _context) {
|
|
|
101
101
|
const parsed = shellFetchOutputSchema.parse(JSON.parse(stdout));
|
|
102
102
|
return parsed.map((si) => toCanonicalIssue(si, sourceName));
|
|
103
103
|
}
|
|
104
|
+
async function getTask(naturalId) {
|
|
105
|
+
const canonicalId = toCanonicalId(sourceName, naturalId);
|
|
106
|
+
if (getTaskCommand === undefined) {
|
|
107
|
+
const all = await runFetch();
|
|
108
|
+
return all.find((i) => i.id === canonicalId) ?? null;
|
|
109
|
+
}
|
|
110
|
+
const result = await invokeShellCommand({
|
|
111
|
+
command: getTaskCommand,
|
|
112
|
+
timeoutMs: timeouts.getTask,
|
|
113
|
+
cwd: config.cwd,
|
|
114
|
+
env: config.env,
|
|
115
|
+
substitutions: {
|
|
116
|
+
id: naturalId,
|
|
117
|
+
canonicalId,
|
|
118
|
+
name: sourceName,
|
|
119
|
+
},
|
|
120
|
+
sourceName,
|
|
121
|
+
});
|
|
122
|
+
if (result.exitCode === 3 || result.stdout.trim().length === 0) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const parsed = shellIssueSchema.parse(JSON.parse(result.stdout));
|
|
126
|
+
return toCanonicalIssue(parsed, sourceName);
|
|
127
|
+
}
|
|
104
128
|
// Shared by markInProgress / markInReview: both pipe the canonical issue's
|
|
105
129
|
// opaque sourceRef to a status-transition script on stdin, with the natural
|
|
106
130
|
// and canonical ids substituted into the command.
|
|
@@ -140,30 +164,15 @@ export function createShellTaskSource(config, _context) {
|
|
|
140
164
|
sourceName,
|
|
141
165
|
});
|
|
142
166
|
},
|
|
167
|
+
async listTasks() {
|
|
168
|
+
return await runFetch();
|
|
169
|
+
},
|
|
170
|
+
async getTask(naturalId) {
|
|
171
|
+
return await getTask(naturalId);
|
|
172
|
+
},
|
|
143
173
|
fetch: runFetch,
|
|
144
174
|
async resolveOne(naturalId) {
|
|
145
|
-
|
|
146
|
-
if (getTaskCommand === undefined) {
|
|
147
|
-
const all = await runFetch();
|
|
148
|
-
return all.find((i) => i.id === canonicalId);
|
|
149
|
-
}
|
|
150
|
-
const result = await invokeShellCommand({
|
|
151
|
-
command: getTaskCommand,
|
|
152
|
-
timeoutMs: timeouts.getTask,
|
|
153
|
-
cwd: config.cwd,
|
|
154
|
-
env: config.env,
|
|
155
|
-
substitutions: {
|
|
156
|
-
id: naturalId,
|
|
157
|
-
canonicalId,
|
|
158
|
-
name: sourceName,
|
|
159
|
-
},
|
|
160
|
-
sourceName,
|
|
161
|
-
});
|
|
162
|
-
if (result.exitCode === 3 || result.stdout.trim().length === 0) {
|
|
163
|
-
return undefined;
|
|
164
|
-
}
|
|
165
|
-
const parsed = shellIssueSchema.parse(JSON.parse(result.stdout));
|
|
166
|
-
return toCanonicalIssue(parsed, sourceName);
|
|
175
|
+
return (await getTask(naturalId)) ?? undefined;
|
|
167
176
|
},
|
|
168
177
|
async markInProgress(issue) {
|
|
169
178
|
await invokeWriteback(config.commands.markInProgress, timeouts.markInProgress, issue);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/todo-txt/source.ts"],"names":[],"mappings":"
|
|
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;AA+QxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,cAAc,GACvB,UAAU,CA4IZ"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { toCanonicalId, } from "../../taskSource.js";
|
|
5
|
+
import { readEnvironmentVariable } from "../../util.js";
|
|
3
6
|
import { isActiveForFetch, normalizeToIssue } from "./normalizer.js";
|
|
4
7
|
import { getMetadataFirst, parseAllLines } from "./parser.js";
|
|
5
|
-
import { copyPromptFile, updateTaskStatus, validateTodoFile } from "./writeback.js";
|
|
8
|
+
import { copyPromptFile, updateTaskStatus, validateTodoFile, withLock } from "./writeback.js";
|
|
9
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
10
|
+
const RECURRENCE_RE = /^\+?\d+[dwmy]$/;
|
|
6
11
|
function readDescription(promptPath) {
|
|
7
12
|
try {
|
|
8
13
|
return readFileSync(promptPath, "utf8");
|
|
@@ -29,7 +34,6 @@ function readAndParseTodo(todoPath) {
|
|
|
29
34
|
content = "";
|
|
30
35
|
}
|
|
31
36
|
return {
|
|
32
|
-
rawLines: content.split("\n"),
|
|
33
37
|
parsedAll: parseAllLines(content),
|
|
34
38
|
};
|
|
35
39
|
}
|
|
@@ -59,10 +63,183 @@ function buildIssue(options) {
|
|
|
59
63
|
updatedAt,
|
|
60
64
|
});
|
|
61
65
|
}
|
|
66
|
+
function assertToken(label, value) {
|
|
67
|
+
if (value.length === 0 || /\s/.test(value)) {
|
|
68
|
+
throw new Error(`todo-txt: ${label} must be a non-empty single token`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function assertCreateId(id) {
|
|
72
|
+
assertToken("id", id);
|
|
73
|
+
if (id === "." || id === ".." || id.includes("/") || id.includes("\\")) {
|
|
74
|
+
throw new Error("todo-txt: id must be a filename-safe token");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function normalizeProject(project) {
|
|
78
|
+
return project.startsWith("+") ? project.slice(1) : project;
|
|
79
|
+
}
|
|
80
|
+
function normalizeContext(context) {
|
|
81
|
+
return context.startsWith("@") ? context.slice(1) : context;
|
|
82
|
+
}
|
|
83
|
+
function metadataToken(key, value) {
|
|
84
|
+
assertToken(`${key}: value`, value);
|
|
85
|
+
return `${key}:${value}`;
|
|
86
|
+
}
|
|
87
|
+
function datePartFor(timeZone, now) {
|
|
88
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
89
|
+
timeZone,
|
|
90
|
+
year: "numeric",
|
|
91
|
+
month: "2-digit",
|
|
92
|
+
day: "2-digit",
|
|
93
|
+
}).formatToParts(now);
|
|
94
|
+
const year = parts.find((part) => part.type === "year")?.value;
|
|
95
|
+
const month = parts.find((part) => part.type === "month")?.value;
|
|
96
|
+
const day = parts.find((part) => part.type === "day")?.value;
|
|
97
|
+
/* v8 ignore next 3 @preserve -- Intl.DateTimeFormat with year/month/day always returns these parts */
|
|
98
|
+
if (year === undefined || month === undefined || day === undefined) {
|
|
99
|
+
throw new Error(`todo-txt: could not format date in timezone "${timeZone}"`);
|
|
100
|
+
}
|
|
101
|
+
return `${year}${month}${day}`;
|
|
102
|
+
}
|
|
103
|
+
/* v8 ignore next @preserve -- Covered in source tests; full-suite V8 coverage remaps this helper inconsistently. */
|
|
104
|
+
function nextGeneratedId(config, parsedAll) {
|
|
105
|
+
const datePart = datePartFor(config.timezone, new Date());
|
|
106
|
+
const prefix = `${config.idPrefix}-${datePart}-`;
|
|
107
|
+
let maximumSequence = 0;
|
|
108
|
+
for (const parsed of parsedAll) {
|
|
109
|
+
if (parsed === null || parsed === undefined) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const id = getMetadataFirst(parsed, "id");
|
|
113
|
+
if (id === undefined || !id.startsWith(prefix)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const sequence = Number.parseInt(id.slice(prefix.length), 10);
|
|
117
|
+
if (Number.isFinite(sequence) && sequence > maximumSequence) {
|
|
118
|
+
maximumSequence = sequence;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return `${prefix}${String(maximumSequence + 1).padStart(3, "0")}`;
|
|
122
|
+
}
|
|
123
|
+
function assertNewId(id, parsedAll) {
|
|
124
|
+
const existing = parsedAll.some((parsed) => parsed !== null && getMetadataFirst(parsed, "id")?.toLowerCase() === id.toLowerCase());
|
|
125
|
+
if (existing) {
|
|
126
|
+
throw new Error(`todo-txt: task id "${id}" already exists`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function buildTodoLine(id, input) {
|
|
130
|
+
const title = input.title.trim();
|
|
131
|
+
if (title.length === 0) {
|
|
132
|
+
throw new Error("todo-txt: title is required");
|
|
133
|
+
}
|
|
134
|
+
if (/[\r\n]/.test(title)) {
|
|
135
|
+
throw new Error("todo-txt: title must be a single line");
|
|
136
|
+
}
|
|
137
|
+
const tokens = [];
|
|
138
|
+
const priority = input.priority ?? "A";
|
|
139
|
+
if (!/^[A-Z]$/.test(priority)) {
|
|
140
|
+
throw new Error("todo-txt: priority must be a single uppercase letter");
|
|
141
|
+
}
|
|
142
|
+
tokens.push(`(${priority})`);
|
|
143
|
+
tokens.push(title);
|
|
144
|
+
for (const rawProject of input.projects) {
|
|
145
|
+
const project = normalizeProject(rawProject);
|
|
146
|
+
assertToken("project", project);
|
|
147
|
+
tokens.push(`+${project}`);
|
|
148
|
+
}
|
|
149
|
+
for (const rawContext of input.contexts) {
|
|
150
|
+
const context = normalizeContext(rawContext);
|
|
151
|
+
assertToken("context", context);
|
|
152
|
+
tokens.push(`@${context}`);
|
|
153
|
+
}
|
|
154
|
+
tokens.push(metadataToken("id", id));
|
|
155
|
+
if (input.repository !== undefined) {
|
|
156
|
+
tokens.push(metadataToken("repo", input.repository));
|
|
157
|
+
}
|
|
158
|
+
tokens.push(metadataToken("agent", input.agent));
|
|
159
|
+
for (const dependency of input.dependencies) {
|
|
160
|
+
tokens.push(metadataToken("dep", dependency));
|
|
161
|
+
}
|
|
162
|
+
if (input.due !== undefined) {
|
|
163
|
+
if (!DATE_RE.test(input.due)) {
|
|
164
|
+
throw new Error("todo-txt: due date must use YYYY-MM-DD");
|
|
165
|
+
}
|
|
166
|
+
tokens.push(metadataToken("due", input.due));
|
|
167
|
+
}
|
|
168
|
+
if (input.recurrence !== undefined) {
|
|
169
|
+
if (!RECURRENCE_RE.test(input.recurrence)) {
|
|
170
|
+
throw new Error("todo-txt: recurrence must look like 1d, 1w, 1m, 1y, or +1m");
|
|
171
|
+
}
|
|
172
|
+
tokens.push(metadataToken("rec", input.recurrence));
|
|
173
|
+
}
|
|
174
|
+
tokens.push("status:todo");
|
|
175
|
+
return tokens.join(" ");
|
|
176
|
+
}
|
|
177
|
+
function promptContentFor(input) {
|
|
178
|
+
if (input.promptFile !== undefined && input.description !== undefined) {
|
|
179
|
+
throw new Error("todo-txt: --prompt-file and --description are mutually exclusive");
|
|
180
|
+
}
|
|
181
|
+
if (input.promptFile !== undefined) {
|
|
182
|
+
return readFileSync(input.promptFile, "utf8");
|
|
183
|
+
}
|
|
184
|
+
if (input.description !== undefined) {
|
|
185
|
+
return input.description;
|
|
186
|
+
}
|
|
187
|
+
return `${input.title.trim()}\n`;
|
|
188
|
+
}
|
|
189
|
+
function appendTodoLine(todoPath, line) {
|
|
190
|
+
mkdirSync(path.dirname(todoPath), { recursive: true });
|
|
191
|
+
let separator = "";
|
|
192
|
+
try {
|
|
193
|
+
const current = readFileSync(todoPath, "utf8");
|
|
194
|
+
separator = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
appendFileSync(todoPath, `${separator}${line}\n`, "utf8");
|
|
202
|
+
}
|
|
203
|
+
function writePromptFile(promptPath, content) {
|
|
204
|
+
mkdirSync(path.dirname(promptPath), { recursive: true });
|
|
205
|
+
writeFileSync(promptPath, content.endsWith("\n") ? content : `${content}\n`, "utf8");
|
|
206
|
+
}
|
|
207
|
+
function shellQuote(value) {
|
|
208
|
+
return `'${value.replaceAll("'", String.raw `'\''`)}'`;
|
|
209
|
+
}
|
|
210
|
+
function configuredEditor() {
|
|
211
|
+
const visual = readEnvironmentVariable("VISUAL");
|
|
212
|
+
if (visual !== undefined && visual.trim().length > 0) {
|
|
213
|
+
return visual;
|
|
214
|
+
}
|
|
215
|
+
const editor = readEnvironmentVariable("EDITOR");
|
|
216
|
+
if (editor !== undefined && editor.trim().length > 0) {
|
|
217
|
+
return editor;
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
function openPromptEditor(promptPath) {
|
|
222
|
+
const editor = configuredEditor();
|
|
223
|
+
if (editor === undefined) {
|
|
224
|
+
throw new Error("todo-txt: --edit requires VISUAL or EDITOR to be set");
|
|
225
|
+
}
|
|
226
|
+
const result = spawnSync(`${editor} ${shellQuote(promptPath)}`, {
|
|
227
|
+
shell: true,
|
|
228
|
+
stdio: "inherit",
|
|
229
|
+
});
|
|
230
|
+
/* v8 ignore next 3 @preserve -- with shell:true editor launch failures report a nonzero status */
|
|
231
|
+
if (result.error !== undefined) {
|
|
232
|
+
throw result.error;
|
|
233
|
+
}
|
|
234
|
+
if (result.status !== 0) {
|
|
235
|
+
throw new Error(`todo-txt: editor exited with status ${result.status}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
62
238
|
export function createTodoTxtTaskSource(config, _context) {
|
|
63
239
|
const sourceName = config.name;
|
|
240
|
+
/* v8 ignore next @preserve -- Covered in source tests; full-suite V8 coverage remaps this line inconsistently. */
|
|
64
241
|
const { todoPath, tasksDir } = config;
|
|
65
|
-
function
|
|
242
|
+
function listTasks() {
|
|
66
243
|
const updatedAt = fileUpdatedAt(todoPath);
|
|
67
244
|
const { parsedAll } = readAndParseTodo(todoPath);
|
|
68
245
|
const issues = [];
|
|
@@ -90,6 +267,25 @@ export function createTodoTxtTaskSource(config, _context) {
|
|
|
90
267
|
}
|
|
91
268
|
return issues;
|
|
92
269
|
}
|
|
270
|
+
function getTask(naturalId) {
|
|
271
|
+
const canonicalId = toCanonicalId(sourceName, naturalId);
|
|
272
|
+
const updatedAt = fileUpdatedAt(todoPath);
|
|
273
|
+
const { parsedAll } = readAndParseTodo(todoPath);
|
|
274
|
+
const index = parsedAll.findIndex((parsed) => parsed !== null &&
|
|
275
|
+
toCanonicalId(sourceName, getMetadataFirst(parsed, "id") ?? "") === canonicalId);
|
|
276
|
+
if (index === -1) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
return (buildIssue({
|
|
280
|
+
parsedIndex: index,
|
|
281
|
+
parsedAll,
|
|
282
|
+
sourceName,
|
|
283
|
+
todoPath,
|
|
284
|
+
tasksDir,
|
|
285
|
+
defaultRepository: config.defaultRepository,
|
|
286
|
+
updatedAt,
|
|
287
|
+
}) ?? null);
|
|
288
|
+
}
|
|
93
289
|
return {
|
|
94
290
|
name: sourceName,
|
|
95
291
|
async verify() {
|
|
@@ -98,26 +294,39 @@ export function createTodoTxtTaskSource(config, _context) {
|
|
|
98
294
|
throw new Error(`todo-txt source "${sourceName}" verification failed:\n${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
99
295
|
}
|
|
100
296
|
},
|
|
297
|
+
async listTasks() {
|
|
298
|
+
return listTasks();
|
|
299
|
+
},
|
|
300
|
+
async getTask(naturalId) {
|
|
301
|
+
return getTask(naturalId);
|
|
302
|
+
},
|
|
303
|
+
async createTask(input) {
|
|
304
|
+
return await withLock(`${todoPath}.lock`, () => {
|
|
305
|
+
const { parsedAll } = readAndParseTodo(todoPath);
|
|
306
|
+
const id = input.id ?? nextGeneratedId(config, parsedAll);
|
|
307
|
+
assertCreateId(id);
|
|
308
|
+
assertNewId(id, parsedAll);
|
|
309
|
+
const promptPath = path.join(tasksDir, `${id}.md`);
|
|
310
|
+
const promptContent = promptContentFor(input);
|
|
311
|
+
const line = buildTodoLine(id, input);
|
|
312
|
+
writePromptFile(promptPath, promptContent);
|
|
313
|
+
appendTodoLine(todoPath, line);
|
|
314
|
+
if (input.edit) {
|
|
315
|
+
openPromptEditor(promptPath);
|
|
316
|
+
}
|
|
317
|
+
const task = getTask(id);
|
|
318
|
+
/* v8 ignore next 3 @preserve -- createTask just appended this id and getTask reads the same file */
|
|
319
|
+
if (task === null) {
|
|
320
|
+
throw new Error(`todo-txt: created task "${id}" could not be read back`);
|
|
321
|
+
}
|
|
322
|
+
return task;
|
|
323
|
+
});
|
|
324
|
+
},
|
|
101
325
|
async fetch() {
|
|
102
|
-
return
|
|
326
|
+
return listTasks();
|
|
103
327
|
},
|
|
104
328
|
async resolveOne(naturalId) {
|
|
105
|
-
|
|
106
|
-
const updatedAt = fileUpdatedAt(todoPath);
|
|
107
|
-
const { parsedAll } = readAndParseTodo(todoPath);
|
|
108
|
-
const index = parsedAll.findIndex((p) => p !== null && toCanonicalId(sourceName, getMetadataFirst(p, "id") ?? "") === canonicalId);
|
|
109
|
-
if (index === -1) {
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
112
|
-
return buildIssue({
|
|
113
|
-
parsedIndex: index,
|
|
114
|
-
parsedAll,
|
|
115
|
-
sourceName,
|
|
116
|
-
todoPath,
|
|
117
|
-
tasksDir,
|
|
118
|
-
defaultRepository: config.defaultRepository,
|
|
119
|
-
updatedAt,
|
|
120
|
-
});
|
|
329
|
+
return getTask(naturalId) ?? undefined;
|
|
121
330
|
},
|
|
122
331
|
async markInProgress(issue) {
|
|
123
332
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- TodoTxtTaskSource always writes TodoTxtSourceRef
|
|
@@ -10,6 +10,7 @@ export interface UpdateOptions {
|
|
|
10
10
|
ref: TodoTxtSourceRef;
|
|
11
11
|
now?: Date;
|
|
12
12
|
}
|
|
13
|
+
export declare function withLock<T>(lockPath: string, fn: () => T | Promise<T>): Promise<T>;
|
|
13
14
|
type StatusMutation = "in-progress" | "in-review" | "done";
|
|
14
15
|
export declare function updateTaskStatus(options: UpdateOptions, newStatus: StatusMutation): Promise<RecurResult | undefined>;
|
|
15
16
|
export declare function copyPromptFile(oldPath: string, newPath: string): void;
|
|
@@ -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;
|
|
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;AAwFD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA0C7E"}
|
|
@@ -133,7 +133,7 @@ function atomicWrite(filePath, content) {
|
|
|
133
133
|
writeFileSync(tmpPath, content, "utf8");
|
|
134
134
|
renameSync(tmpPath, filePath);
|
|
135
135
|
}
|
|
136
|
-
async function withLock(lockPath, fn) {
|
|
136
|
+
export async function withLock(lockPath, fn) {
|
|
137
137
|
await acquireLock(lockPath);
|
|
138
138
|
try {
|
|
139
139
|
// return await is required here so the finally block runs while the lock is held
|
package/dist/lib/board.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Board composer — fans `verify` / `
|
|
2
|
+
* Board composer — fans `verify` / `listTasks` / `getTask` / `markInProgress` /
|
|
3
3
|
* `markInReview` across N `TaskSource` adapters. Even a single-source config
|
|
4
4
|
* goes through this; the moment we skip the wrapper we grow Linear assumptions
|
|
5
5
|
* back into consumers.
|
package/dist/lib/board.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Board composer — fans `verify` / `
|
|
2
|
+
* Board composer — fans `verify` / `listTasks` / `getTask` / `markInProgress` /
|
|
3
3
|
* `markInReview` across N `TaskSource` adapters. Even a single-source config
|
|
4
4
|
* goes through this; the moment we skip the wrapper we grow Linear assumptions
|
|
5
5
|
* back into consumers.
|
|
@@ -9,7 +9,7 @@ async function callVerify(source) {
|
|
|
9
9
|
await source.verify();
|
|
10
10
|
}
|
|
11
11
|
async function callFetch(source) {
|
|
12
|
-
return await source.
|
|
12
|
+
return await source.listTasks();
|
|
13
13
|
}
|
|
14
14
|
async function callFetchParentSkips(source) {
|
|
15
15
|
if (source.fetchParentSkips !== undefined) {
|
|
@@ -18,7 +18,7 @@ async function callFetchParentSkips(source) {
|
|
|
18
18
|
return [];
|
|
19
19
|
}
|
|
20
20
|
async function callResolveOne(source, naturalId) {
|
|
21
|
-
return await source.
|
|
21
|
+
return (await source.getTask(naturalId)) ?? undefined;
|
|
22
22
|
}
|
|
23
23
|
export function createBoard(sources) {
|
|
24
24
|
const byName = new Map();
|