@nickmeriano/task 0.4.2 → 0.6.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 +77 -9
- package/dist/cli.js +290 -19
- package/dist/cli.js.map +1 -1
- package/dist/file-store.d.ts +113 -0
- package/dist/file-store.d.ts.map +1 -0
- package/dist/file-store.js +604 -0
- package/dist/file-store.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +30 -7
- package/dist/server.js.map +1 -1
- package/dist/store.d.ts +69 -16
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +169 -39
- package/dist/store.js.map +1 -1
- package/dist/store.test.d.ts +12 -0
- package/dist/store.test.d.ts.map +1 -0
- package/dist/store.test.js +252 -0
- package/dist/store.test.js.map +1 -0
- package/dist/ticket-doc.d.ts +57 -0
- package/dist/ticket-doc.d.ts.map +1 -0
- package/dist/ticket-doc.js +197 -0
- package/dist/ticket-doc.js.map +1 -0
- package/dist/types.d.ts +35 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
- package/skill/SKILL.md +40 -9
- package/src/cli.ts +304 -22
- package/src/file-store.ts +693 -0
- package/src/index.ts +30 -4
- package/src/server.ts +31 -11
- package/src/store.test.ts +305 -0
- package/src/store.ts +210 -49
- package/src/ticket-doc.ts +226 -0
- package/src/types.ts +35 -3
- package/ui/dist/assets/index-CXW8uT5f.css +1 -0
- package/ui/dist/assets/{index-Dm3ToURf.js → index-oJzomUDL.js} +67 -67
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-DXFbw9bM.css +0 -1
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { commentStem, parseComment, parseTicket, serializeComment, serializeTicket, } from "./ticket-doc.js";
|
|
4
|
+
import { CONFIG_FILE, DB_FILE, POSITION_GAP, TASK_DIR, TaskStore, derivePrefix, } from "./store.js";
|
|
5
|
+
export const TICKETS_DIR = "tickets";
|
|
6
|
+
/** Finished tickets moved out of the hot path — same per-ticket layout. */
|
|
7
|
+
export const ARCHIVE_DIR = "archive";
|
|
8
|
+
const TICKET_FILE = "ticket.md";
|
|
9
|
+
const COMMENTS_DIR = "comments";
|
|
10
|
+
function now() {
|
|
11
|
+
return new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Write-then-rename, so a reader (another CLI, the serve watcher, a crashed
|
|
15
|
+
* process) never sees half a ticket.
|
|
16
|
+
*/
|
|
17
|
+
function writeAtomic(path, text) {
|
|
18
|
+
const tmp = join(dirname(path), `.${basename(path)}.${process.pid}.tmp`);
|
|
19
|
+
writeFileSync(tmp, text);
|
|
20
|
+
renameSync(tmp, path);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The canonical persistence layer: one directory per ticket under
|
|
24
|
+
* `.task/tickets/`, holding a frontmatter+markdown `ticket.md` and one file
|
|
25
|
+
* per comment. Everything is plain text committed to git, which is the point —
|
|
26
|
+
* ticket edits diff, review, and merge like code, and two branches touching
|
|
27
|
+
* different tickets (or adding comments to the same one) merge cleanly by
|
|
28
|
+
* construction.
|
|
29
|
+
*
|
|
30
|
+
* There is no database and no cache: boards are dozens of tickets, and
|
|
31
|
+
* re-reading a handful of small files per operation is cheaper than opening
|
|
32
|
+
* SQLite ever was. If boards outgrow that, a derived (gitignored) index can be
|
|
33
|
+
* added without changing the format — the files stay the source of truth.
|
|
34
|
+
*/
|
|
35
|
+
export class FileStore {
|
|
36
|
+
root;
|
|
37
|
+
taskDir;
|
|
38
|
+
config;
|
|
39
|
+
ticketsDir;
|
|
40
|
+
archiveDir;
|
|
41
|
+
constructor(root) {
|
|
42
|
+
this.root = root;
|
|
43
|
+
this.taskDir = join(root, TASK_DIR);
|
|
44
|
+
this.config = JSON.parse(readFileSync(join(this.taskDir, CONFIG_FILE), "utf8"));
|
|
45
|
+
this.ticketsDir = join(this.taskDir, TICKETS_DIR);
|
|
46
|
+
this.archiveDir = join(this.taskDir, ARCHIVE_DIR);
|
|
47
|
+
}
|
|
48
|
+
close() { }
|
|
49
|
+
displayId(number) {
|
|
50
|
+
return `${this.config.prefix}-${number}`;
|
|
51
|
+
}
|
|
52
|
+
/** Accepts "PHONE-12", "phone-12" or "12". */
|
|
53
|
+
parseId(ref) {
|
|
54
|
+
const match = /^(?:[A-Za-z0-9]+-)?(\d+)$/.exec(ref.trim());
|
|
55
|
+
if (!match)
|
|
56
|
+
throw new Error(`invalid task id: ${ref}`);
|
|
57
|
+
return Number(match[1]);
|
|
58
|
+
}
|
|
59
|
+
// ── Reading ────────────────────────────────────────────────────────────────
|
|
60
|
+
ticketPath(number) {
|
|
61
|
+
return join(this.ticketsDir, String(number), TICKET_FILE);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A ticket's comments live wherever the ticket does — the whole directory
|
|
65
|
+
* moves on archive, so an archived ticket's discussion stays readable.
|
|
66
|
+
*/
|
|
67
|
+
commentsPath(number) {
|
|
68
|
+
const home = existsSync(join(this.archiveDir, String(number)))
|
|
69
|
+
? this.archiveDir
|
|
70
|
+
: this.ticketsDir;
|
|
71
|
+
return join(home, String(number), COMMENTS_DIR);
|
|
72
|
+
}
|
|
73
|
+
readDir(dir) {
|
|
74
|
+
if (!existsSync(dir))
|
|
75
|
+
return [];
|
|
76
|
+
const tickets = [];
|
|
77
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
78
|
+
if (!entry.isDirectory() || !/^\d+$/.test(entry.name))
|
|
79
|
+
continue;
|
|
80
|
+
const path = join(dir, entry.name, TICKET_FILE);
|
|
81
|
+
if (!existsSync(path))
|
|
82
|
+
continue;
|
|
83
|
+
tickets.push({ number: Number(entry.name), doc: parseTicket(readFileSync(path, "utf8"), path) });
|
|
84
|
+
}
|
|
85
|
+
tickets.sort((a, b) => a.number - b.number);
|
|
86
|
+
return tickets;
|
|
87
|
+
}
|
|
88
|
+
/** Every live ticket on the board, freshly parsed — the files are the state. */
|
|
89
|
+
readAll() {
|
|
90
|
+
return this.readDir(this.ticketsDir);
|
|
91
|
+
}
|
|
92
|
+
readOne(number) {
|
|
93
|
+
const path = this.ticketPath(number);
|
|
94
|
+
if (!existsSync(path))
|
|
95
|
+
return null;
|
|
96
|
+
return { number, doc: parseTicket(readFileSync(path, "utf8"), path) };
|
|
97
|
+
}
|
|
98
|
+
/** Cheap presence check — the archive's numbers, without parsing anything. */
|
|
99
|
+
archivedNumbers() {
|
|
100
|
+
if (!existsSync(this.archiveDir))
|
|
101
|
+
return [];
|
|
102
|
+
return readdirSync(this.archiveDir)
|
|
103
|
+
.filter((name) => /^\d+$/.test(name))
|
|
104
|
+
.map(Number);
|
|
105
|
+
}
|
|
106
|
+
isArchived(number) {
|
|
107
|
+
return existsSync(join(this.archiveDir, String(number), TICKET_FILE));
|
|
108
|
+
}
|
|
109
|
+
/** The error every write path throws instead of touching the archive. */
|
|
110
|
+
assertNotArchived(number) {
|
|
111
|
+
if (this.isArchived(number)) {
|
|
112
|
+
const id = this.displayId(number);
|
|
113
|
+
throw new Error(`${id} is archived — run \`task unarchive ${id}\` first`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
writeTicket(ticket) {
|
|
117
|
+
mkdirSync(dirname(this.ticketPath(ticket.number)), { recursive: true });
|
|
118
|
+
writeAtomic(this.ticketPath(ticket.number), serializeTicket(ticket.doc));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Only `blocked_by` is stored (on the blocked ticket); the `blocks` side is
|
|
122
|
+
* derived here, so the two views can never disagree.
|
|
123
|
+
*/
|
|
124
|
+
toTask(ticket, all) {
|
|
125
|
+
const blocks = all
|
|
126
|
+
.filter((t) => t.doc.blockedBy.includes(ticket.number))
|
|
127
|
+
.map((t) => t.number);
|
|
128
|
+
return {
|
|
129
|
+
id: this.displayId(ticket.number),
|
|
130
|
+
number: ticket.number,
|
|
131
|
+
title: ticket.doc.title,
|
|
132
|
+
description: ticket.doc.description,
|
|
133
|
+
status: ticket.doc.status,
|
|
134
|
+
tags: ticket.doc.tags,
|
|
135
|
+
milestone: ticket.doc.milestone,
|
|
136
|
+
needsHuman: ticket.doc.needsHuman,
|
|
137
|
+
blocks,
|
|
138
|
+
blockedBy: [...ticket.doc.blockedBy].sort((a, b) => a - b),
|
|
139
|
+
prs: ticket.doc.prs,
|
|
140
|
+
position: ticket.doc.position,
|
|
141
|
+
createdAt: ticket.doc.createdAt,
|
|
142
|
+
updatedAt: ticket.doc.updatedAt,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
applyFilter(tickets, filter) {
|
|
146
|
+
if (filter.statuses?.length) {
|
|
147
|
+
const wanted = new Set(filter.statuses);
|
|
148
|
+
tickets = tickets.filter((t) => wanted.has(t.doc.status));
|
|
149
|
+
}
|
|
150
|
+
if (filter.milestone) {
|
|
151
|
+
tickets = tickets.filter((t) => t.doc.milestone === filter.milestone);
|
|
152
|
+
}
|
|
153
|
+
if (filter.needsHuman !== undefined) {
|
|
154
|
+
tickets = tickets.filter((t) => t.doc.needsHuman === filter.needsHuman);
|
|
155
|
+
}
|
|
156
|
+
if (filter.tags?.length) {
|
|
157
|
+
const wanted = new Set(filter.tags);
|
|
158
|
+
tickets = tickets.filter((t) => t.doc.tags.some((tag) => wanted.has(tag)));
|
|
159
|
+
}
|
|
160
|
+
return tickets;
|
|
161
|
+
}
|
|
162
|
+
list(filter = {}) {
|
|
163
|
+
if (filter.archived) {
|
|
164
|
+
// Relations are derived across both sets so an archived ticket still
|
|
165
|
+
// shows what it blocked; `archived: true` is only ever set here, so the
|
|
166
|
+
// board's payloads carry no new field.
|
|
167
|
+
const archived = this.readDir(this.archiveDir);
|
|
168
|
+
const everything = [...this.readAll(), ...archived];
|
|
169
|
+
return this.applyFilter(archived, filter)
|
|
170
|
+
.map((t) => ({ ...this.toTask(t, everything), archived: true }))
|
|
171
|
+
.sort((a, b) => a.position - b.position || a.number - b.number);
|
|
172
|
+
}
|
|
173
|
+
const all = this.readAll();
|
|
174
|
+
return this.applyFilter(all, filter)
|
|
175
|
+
.map((t) => this.toTask(t, all))
|
|
176
|
+
.sort((a, b) => a.position - b.position || a.number - b.number);
|
|
177
|
+
}
|
|
178
|
+
get(number) {
|
|
179
|
+
const all = this.readAll();
|
|
180
|
+
const ticket = all.find((t) => t.number === number);
|
|
181
|
+
if (ticket)
|
|
182
|
+
return this.toTask(ticket, all);
|
|
183
|
+
// `show` should reach the archive without ceremony — reads are safe.
|
|
184
|
+
const path = join(this.archiveDir, String(number), TICKET_FILE);
|
|
185
|
+
if (!existsSync(path))
|
|
186
|
+
return null;
|
|
187
|
+
const archived = { number, doc: parseTicket(readFileSync(path, "utf8"), path) };
|
|
188
|
+
return { ...this.toTask(archived, [...all, archived]), archived: true };
|
|
189
|
+
}
|
|
190
|
+
// ── Writing ────────────────────────────────────────────────────────────────
|
|
191
|
+
create(input) {
|
|
192
|
+
const all = this.readAll();
|
|
193
|
+
// Archived numbers stay reserved — a new ticket must never take a number
|
|
194
|
+
// that old comments or PR titles still point at.
|
|
195
|
+
const taken = [...all.map((t) => t.number), ...this.archivedNumbers()];
|
|
196
|
+
const number = taken.reduce((max, n) => Math.max(max, n), 0) + 1;
|
|
197
|
+
const status = input.status ?? "todo";
|
|
198
|
+
for (const target of [...(input.blocks ?? []), ...(input.blockedBy ?? [])]) {
|
|
199
|
+
if (!all.some((t) => t.number === target)) {
|
|
200
|
+
throw new Error(`no such task: ${this.displayId(target)}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// New tasks land at the bottom of their column.
|
|
204
|
+
const column = all.filter((t) => t.doc.status === status);
|
|
205
|
+
const bottom = column.length ? Math.max(...column.map((t) => t.doc.position)) : 0;
|
|
206
|
+
const timestamp = now();
|
|
207
|
+
const ticket = {
|
|
208
|
+
number,
|
|
209
|
+
doc: {
|
|
210
|
+
title: input.title,
|
|
211
|
+
description: input.description ?? "",
|
|
212
|
+
status,
|
|
213
|
+
tags: input.tags ?? [],
|
|
214
|
+
milestone: input.milestone ?? null,
|
|
215
|
+
needsHuman: input.needsHuman ?? false,
|
|
216
|
+
blockedBy: [...new Set(input.blockedBy ?? [])].sort((a, b) => a - b),
|
|
217
|
+
prs: input.prs ?? [],
|
|
218
|
+
position: bottom + POSITION_GAP,
|
|
219
|
+
createdAt: timestamp,
|
|
220
|
+
updatedAt: timestamp,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
this.writeTicket(ticket);
|
|
224
|
+
if (input.blocks !== undefined) {
|
|
225
|
+
this.reconcileBlocks(number, [...all, ticket], input.blocks, timestamp);
|
|
226
|
+
}
|
|
227
|
+
return this.get(number);
|
|
228
|
+
}
|
|
229
|
+
update(number, patch) {
|
|
230
|
+
const all = this.readAll();
|
|
231
|
+
const ticket = all.find((t) => t.number === number);
|
|
232
|
+
if (!ticket) {
|
|
233
|
+
this.assertNotArchived(number);
|
|
234
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
235
|
+
}
|
|
236
|
+
// Validate link targets before any file is written, so a bad target
|
|
237
|
+
// rejects the whole patch — the transactional behavior the SQLite store
|
|
238
|
+
// got for free.
|
|
239
|
+
for (const target of [...(patch.blocks ?? []), ...(patch.blockedBy ?? [])]) {
|
|
240
|
+
if (target === number) {
|
|
241
|
+
throw new Error(`a task can't block itself: ${this.displayId(number)}`);
|
|
242
|
+
}
|
|
243
|
+
if (!all.some((t) => t.number === target)) {
|
|
244
|
+
this.assertNotArchived(target);
|
|
245
|
+
throw new Error(`no such task: ${this.displayId(target)}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const timestamp = now();
|
|
249
|
+
let linksChanged = false;
|
|
250
|
+
if (patch.blocks !== undefined) {
|
|
251
|
+
linksChanged = this.reconcileBlocks(number, all, patch.blocks, timestamp);
|
|
252
|
+
}
|
|
253
|
+
if (patch.blockedBy !== undefined) {
|
|
254
|
+
const wanted = [...new Set(patch.blockedBy)].sort((a, b) => a - b);
|
|
255
|
+
const current = ticket.doc.blockedBy;
|
|
256
|
+
const touched = [
|
|
257
|
+
...current.filter((n) => !wanted.includes(n)),
|
|
258
|
+
...wanted.filter((n) => !current.includes(n)),
|
|
259
|
+
];
|
|
260
|
+
if (touched.length > 0) {
|
|
261
|
+
linksChanged = true;
|
|
262
|
+
ticket.doc.blockedBy = wanted;
|
|
263
|
+
// The other end of every added or removed link gets its `updated`
|
|
264
|
+
// bumped too — its derived `blocks` view just changed. A dangling
|
|
265
|
+
// reference (hand-edit pointing at a deleted ticket) has no other end.
|
|
266
|
+
for (const n of touched) {
|
|
267
|
+
const other = all.find((t) => t.number === n);
|
|
268
|
+
if (!other)
|
|
269
|
+
continue;
|
|
270
|
+
other.doc.updatedAt = timestamp;
|
|
271
|
+
this.writeTicket(other);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const doc = ticket.doc;
|
|
276
|
+
let fieldsChanged = false;
|
|
277
|
+
const set = (key, value) => {
|
|
278
|
+
doc[key] = value;
|
|
279
|
+
fieldsChanged = true;
|
|
280
|
+
};
|
|
281
|
+
if (patch.title !== undefined)
|
|
282
|
+
set("title", patch.title);
|
|
283
|
+
if (patch.description !== undefined)
|
|
284
|
+
set("description", patch.description);
|
|
285
|
+
if (patch.tags !== undefined)
|
|
286
|
+
set("tags", patch.tags);
|
|
287
|
+
if (patch.milestone !== undefined)
|
|
288
|
+
set("milestone", patch.milestone);
|
|
289
|
+
if (patch.needsHuman !== undefined)
|
|
290
|
+
set("needsHuman", patch.needsHuman);
|
|
291
|
+
if (patch.prs !== undefined)
|
|
292
|
+
set("prs", patch.prs);
|
|
293
|
+
if (patch.status !== undefined) {
|
|
294
|
+
const previous = doc.status;
|
|
295
|
+
set("status", patch.status);
|
|
296
|
+
if (patch.position === undefined && patch.status !== previous) {
|
|
297
|
+
// Moved columns without an explicit slot → land on top, where the
|
|
298
|
+
// freshest movement is visible (Linear's behavior).
|
|
299
|
+
const column = all.filter((t) => t.number !== number && t.doc.status === patch.status);
|
|
300
|
+
const top = column.length ? Math.min(...column.map((t) => t.doc.position)) : 0;
|
|
301
|
+
set("position", top - POSITION_GAP);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (patch.position !== undefined)
|
|
305
|
+
set("position", patch.position);
|
|
306
|
+
if (!fieldsChanged && !linksChanged)
|
|
307
|
+
return this.toTask(ticket, all);
|
|
308
|
+
if (fieldsChanged || linksChanged)
|
|
309
|
+
doc.updatedAt = timestamp;
|
|
310
|
+
this.writeTicket(ticket);
|
|
311
|
+
return this.get(number);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Make every other ticket's `blocked_by` agree with "this task blocks
|
|
315
|
+
* exactly `targets`". Returns whether anything changed; every touched ticket
|
|
316
|
+
* (and, via the caller, this one) gets its `updated` bumped.
|
|
317
|
+
*/
|
|
318
|
+
reconcileBlocks(number, all, targets, timestamp) {
|
|
319
|
+
const wanted = new Set(targets);
|
|
320
|
+
if (wanted.has(number)) {
|
|
321
|
+
throw new Error(`a task can't block itself: ${this.displayId(number)}`);
|
|
322
|
+
}
|
|
323
|
+
let changed = false;
|
|
324
|
+
for (const other of all) {
|
|
325
|
+
if (other.number === number)
|
|
326
|
+
continue;
|
|
327
|
+
const has = other.doc.blockedBy.includes(number);
|
|
328
|
+
const should = wanted.has(other.number);
|
|
329
|
+
if (has === should)
|
|
330
|
+
continue;
|
|
331
|
+
other.doc.blockedBy = should
|
|
332
|
+
? [...other.doc.blockedBy, number].sort((a, b) => a - b)
|
|
333
|
+
: other.doc.blockedBy.filter((n) => n !== number);
|
|
334
|
+
other.doc.updatedAt = timestamp;
|
|
335
|
+
this.writeTicket(other);
|
|
336
|
+
changed = true;
|
|
337
|
+
}
|
|
338
|
+
if (changed) {
|
|
339
|
+
const self = all.find((t) => t.number === number);
|
|
340
|
+
if (self) {
|
|
341
|
+
self.doc.updatedAt = timestamp;
|
|
342
|
+
this.writeTicket(self);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return changed;
|
|
346
|
+
}
|
|
347
|
+
/** `task link A --blocks B` and friends — additive, unlike the patch form. */
|
|
348
|
+
link(number, relation, target) {
|
|
349
|
+
const task = this.get(number);
|
|
350
|
+
if (!task)
|
|
351
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
352
|
+
const current = relation === "blocks" ? task.blocks : task.blockedBy;
|
|
353
|
+
const patch = relation === "blocks"
|
|
354
|
+
? { blocks: [...current, target] }
|
|
355
|
+
: { blockedBy: [...current, target] };
|
|
356
|
+
return this.update(number, patch);
|
|
357
|
+
}
|
|
358
|
+
unlink(number, relation, target) {
|
|
359
|
+
const task = this.get(number);
|
|
360
|
+
if (!task)
|
|
361
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
362
|
+
const current = relation === "blocks" ? task.blocks : task.blockedBy;
|
|
363
|
+
const kept = current.filter((n) => n !== target);
|
|
364
|
+
const patch = relation === "blocks" ? { blocks: kept } : { blockedBy: kept };
|
|
365
|
+
return this.update(number, patch);
|
|
366
|
+
}
|
|
367
|
+
delete(number) {
|
|
368
|
+
if (!this.readOne(number)) {
|
|
369
|
+
this.assertNotArchived(number);
|
|
370
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
371
|
+
}
|
|
372
|
+
rmSync(join(this.ticketsDir, String(number)), { recursive: true });
|
|
373
|
+
// Links pointing at the deleted ticket go with it — the same cascade the
|
|
374
|
+
// relation table had, so quiet on the other tickets' `updated`.
|
|
375
|
+
for (const other of this.readAll()) {
|
|
376
|
+
if (!other.doc.blockedBy.includes(number))
|
|
377
|
+
continue;
|
|
378
|
+
other.doc.blockedBy = other.doc.blockedBy.filter((n) => n !== number);
|
|
379
|
+
this.writeTicket(other);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ── Archive ────────────────────────────────────────────────────────────────
|
|
383
|
+
/**
|
|
384
|
+
* Move a finished ticket's whole directory to `.task/archive/` — one rename,
|
|
385
|
+
* which git records as a move, so history follows the ticket. Archived
|
|
386
|
+
* tickets leave every hot path (`list`, the board, link derivation) but stay
|
|
387
|
+
* readable via `get`/`comments` and keep their number reserved forever.
|
|
388
|
+
* Links other tickets hold on this one are left in place: they're
|
|
389
|
+
* dangling-tolerant everywhere, and unarchiving puts them back in force.
|
|
390
|
+
*/
|
|
391
|
+
archive(number) {
|
|
392
|
+
const ticket = this.readOne(number);
|
|
393
|
+
if (!ticket) {
|
|
394
|
+
if (this.isArchived(number)) {
|
|
395
|
+
throw new Error(`${this.displayId(number)} is already archived`);
|
|
396
|
+
}
|
|
397
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
398
|
+
}
|
|
399
|
+
if (ticket.doc.status !== "done" && ticket.doc.status !== "canceled") {
|
|
400
|
+
throw new Error(`${this.displayId(number)} is ${ticket.doc.status} — only done or canceled tickets can be archived`);
|
|
401
|
+
}
|
|
402
|
+
mkdirSync(this.archiveDir, { recursive: true });
|
|
403
|
+
renameSync(join(this.ticketsDir, String(number)), join(this.archiveDir, String(number)));
|
|
404
|
+
return this.get(number);
|
|
405
|
+
}
|
|
406
|
+
unarchive(number) {
|
|
407
|
+
if (!this.isArchived(number)) {
|
|
408
|
+
throw new Error(this.readOne(number)
|
|
409
|
+
? `${this.displayId(number)} isn't archived`
|
|
410
|
+
: `no such task: ${this.displayId(number)}`);
|
|
411
|
+
}
|
|
412
|
+
// A checkout where everything is archived has no tickets/ at all — git
|
|
413
|
+
// doesn't keep empty directories.
|
|
414
|
+
mkdirSync(this.ticketsDir, { recursive: true });
|
|
415
|
+
renameSync(join(this.archiveDir, String(number)), join(this.ticketsDir, String(number)));
|
|
416
|
+
return this.get(number);
|
|
417
|
+
}
|
|
418
|
+
// ── Comments ───────────────────────────────────────────────────────────────
|
|
419
|
+
/**
|
|
420
|
+
* One file per comment, append-only: two writers commenting concurrently on
|
|
421
|
+
* the same ticket produce two files and merge without a conflict — which is
|
|
422
|
+
* why they aren't lines inside `ticket.md`.
|
|
423
|
+
*/
|
|
424
|
+
comments(number) {
|
|
425
|
+
const dir = this.commentsPath(number);
|
|
426
|
+
if (!existsSync(dir))
|
|
427
|
+
return [];
|
|
428
|
+
const comments = [];
|
|
429
|
+
for (const entry of readdirSync(dir)) {
|
|
430
|
+
if (!entry.endsWith(".md") || entry.startsWith("."))
|
|
431
|
+
continue;
|
|
432
|
+
const path = join(dir, entry);
|
|
433
|
+
const doc = parseComment(readFileSync(path, "utf8"), path);
|
|
434
|
+
comments.push({
|
|
435
|
+
id: entry.slice(0, -3),
|
|
436
|
+
taskId: this.displayId(number),
|
|
437
|
+
author: doc.author,
|
|
438
|
+
body: doc.body,
|
|
439
|
+
createdAt: doc.createdAt,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
comments.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
443
|
+
return comments;
|
|
444
|
+
}
|
|
445
|
+
addComment(number, body, author = "") {
|
|
446
|
+
if (!this.readOne(number)) {
|
|
447
|
+
this.assertNotArchived(number);
|
|
448
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
449
|
+
}
|
|
450
|
+
const createdAt = now();
|
|
451
|
+
const dir = this.commentsPath(number);
|
|
452
|
+
mkdirSync(dir, { recursive: true });
|
|
453
|
+
const stem = commentStem(createdAt, author);
|
|
454
|
+
let id = stem;
|
|
455
|
+
for (let n = 2; existsSync(join(dir, `${id}.md`)); n++)
|
|
456
|
+
id = `${stem}-${n}`;
|
|
457
|
+
writeAtomic(join(dir, `${id}.md`), serializeComment({ author, createdAt, body }));
|
|
458
|
+
return { id, taskId: this.displayId(number), author, body, createdAt };
|
|
459
|
+
}
|
|
460
|
+
deleteComment(number, commentId) {
|
|
461
|
+
// Resolve the id against the directory listing rather than building a path
|
|
462
|
+
// from it — the id arrived over HTTP and must not be able to point
|
|
463
|
+
// anywhere but at an actual comment of this task.
|
|
464
|
+
const existing = this.comments(number).find((c) => c.id === commentId);
|
|
465
|
+
if (!existing) {
|
|
466
|
+
throw new Error(`no such comment on ${this.displayId(number)}: ${commentId}`);
|
|
467
|
+
}
|
|
468
|
+
rmSync(join(this.commentsPath(number), `${commentId}.md`));
|
|
469
|
+
return existing;
|
|
470
|
+
}
|
|
471
|
+
commentCounts() {
|
|
472
|
+
const counts = new Map();
|
|
473
|
+
for (const ticket of this.readAll()) {
|
|
474
|
+
const dir = this.commentsPath(ticket.number);
|
|
475
|
+
if (!existsSync(dir))
|
|
476
|
+
continue;
|
|
477
|
+
const count = readdirSync(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).length;
|
|
478
|
+
if (count > 0)
|
|
479
|
+
counts.set(ticket.number, count);
|
|
480
|
+
}
|
|
481
|
+
return counts;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// ── Opening, initializing, migrating ─────────────────────────────────────────
|
|
485
|
+
/** What `.task/.gitignore` says on a text-canonical board. */
|
|
486
|
+
const GITIGNORE = `# Board state is the text files in tickets/ — the database (if one is still
|
|
487
|
+
# around from before \`task migrate\`) is derived/legacy and stays out of git.
|
|
488
|
+
${DB_FILE}
|
|
489
|
+
*.db-journal
|
|
490
|
+
*.db-wal
|
|
491
|
+
*.db-shm
|
|
492
|
+
*.tmp
|
|
493
|
+
`;
|
|
494
|
+
/**
|
|
495
|
+
* The board in `root`, on whichever backend it uses: text files when the
|
|
496
|
+
* `tickets/` tree exists or the config says version ≥ 2, the legacy SQLite
|
|
497
|
+
* store otherwise. Old boards keep working untouched until `task migrate`.
|
|
498
|
+
*/
|
|
499
|
+
export function openBoard(root) {
|
|
500
|
+
const taskDir = join(root, TASK_DIR);
|
|
501
|
+
if (existsSync(join(taskDir, TICKETS_DIR)))
|
|
502
|
+
return new FileStore(root);
|
|
503
|
+
const config = JSON.parse(readFileSync(join(taskDir, CONFIG_FILE), "utf8"));
|
|
504
|
+
if ((config.version ?? 1) >= 2)
|
|
505
|
+
return new FileStore(root);
|
|
506
|
+
return new TaskStore(root);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Create `.task/` in `root`: config and a .gitignore. New boards are
|
|
510
|
+
* text-canonical from the start — tickets appear under `tickets/` as they're
|
|
511
|
+
* created, and everything in `.task/` except the ignores belongs in git.
|
|
512
|
+
*/
|
|
513
|
+
export function initProject(root, options) {
|
|
514
|
+
const taskDir = join(root, TASK_DIR);
|
|
515
|
+
if (existsSync(join(taskDir, CONFIG_FILE))) {
|
|
516
|
+
throw new Error(`already initialized: ${join(taskDir, CONFIG_FILE)} exists`);
|
|
517
|
+
}
|
|
518
|
+
mkdirSync(taskDir, { recursive: true });
|
|
519
|
+
const config = {
|
|
520
|
+
name: options.name,
|
|
521
|
+
prefix: options.prefix ?? derivePrefix(options.name),
|
|
522
|
+
version: 2,
|
|
523
|
+
};
|
|
524
|
+
writeFileSync(join(taskDir, CONFIG_FILE), `${JSON.stringify(config, null, 2)}\n`);
|
|
525
|
+
writeFileSync(join(taskDir, ".gitignore"), GITIGNORE);
|
|
526
|
+
return new FileStore(root);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* `task migrate` — export every ticket and comment from a legacy `tasks.db`
|
|
530
|
+
* into the text layout, flip the config to version 2, and gitignore the
|
|
531
|
+
* database. The database file itself is left on disk untouched, as a backup;
|
|
532
|
+
* it just stops being the state.
|
|
533
|
+
*/
|
|
534
|
+
export function migrateBoard(root) {
|
|
535
|
+
const taskDir = join(root, TASK_DIR);
|
|
536
|
+
if (existsSync(join(taskDir, TICKETS_DIR))) {
|
|
537
|
+
throw new Error("already migrated — .task/tickets/ exists");
|
|
538
|
+
}
|
|
539
|
+
if (!existsSync(join(taskDir, DB_FILE))) {
|
|
540
|
+
throw new Error(`nothing to migrate — no ${DB_FILE} in .task/`);
|
|
541
|
+
}
|
|
542
|
+
const legacy = new TaskStore(root);
|
|
543
|
+
let comments = 0;
|
|
544
|
+
try {
|
|
545
|
+
const tasks = legacy.list({});
|
|
546
|
+
for (const task of tasks) {
|
|
547
|
+
const dir = join(taskDir, TICKETS_DIR, String(task.number));
|
|
548
|
+
mkdirSync(dir, { recursive: true });
|
|
549
|
+
writeFileSync(join(dir, TICKET_FILE), serializeTicket({
|
|
550
|
+
title: task.title,
|
|
551
|
+
description: task.description,
|
|
552
|
+
status: task.status,
|
|
553
|
+
tags: task.tags,
|
|
554
|
+
milestone: task.milestone,
|
|
555
|
+
needsHuman: task.needsHuman,
|
|
556
|
+
blockedBy: task.blockedBy,
|
|
557
|
+
prs: task.prs,
|
|
558
|
+
position: task.position,
|
|
559
|
+
createdAt: task.createdAt,
|
|
560
|
+
updatedAt: task.updatedAt,
|
|
561
|
+
}));
|
|
562
|
+
const taskComments = legacy.comments(task.number);
|
|
563
|
+
if (taskComments.length === 0)
|
|
564
|
+
continue;
|
|
565
|
+
const commentsDir = join(dir, COMMENTS_DIR);
|
|
566
|
+
mkdirSync(commentsDir);
|
|
567
|
+
let previous = null;
|
|
568
|
+
for (const comment of taskComments) {
|
|
569
|
+
// `comments()` sorts by (createdAt, id), so ids must sort in legacy
|
|
570
|
+
// (rowid) order wherever timestamps tie — and a fresh stem for a
|
|
571
|
+
// different author can sort *before* the previous one. Chain off the
|
|
572
|
+
// previous id instead: the collision suffix below then appends `-2`,
|
|
573
|
+
// which sorts after. The frontmatter stays authoritative for the
|
|
574
|
+
// author; the filename is only an ordering key.
|
|
575
|
+
let stem = commentStem(comment.createdAt, comment.author);
|
|
576
|
+
if (previous &&
|
|
577
|
+
comment.createdAt === previous.createdAt &&
|
|
578
|
+
stem.localeCompare(previous.id) < 0) {
|
|
579
|
+
stem = previous.id;
|
|
580
|
+
}
|
|
581
|
+
let id = stem;
|
|
582
|
+
for (let n = 2; existsSync(join(commentsDir, `${id}.md`)); n++)
|
|
583
|
+
id = `${stem}-${n}`;
|
|
584
|
+
previous = { createdAt: comment.createdAt, id };
|
|
585
|
+
writeFileSync(join(commentsDir, `${id}.md`), serializeComment({
|
|
586
|
+
author: comment.author,
|
|
587
|
+
createdAt: comment.createdAt,
|
|
588
|
+
body: comment.body,
|
|
589
|
+
}));
|
|
590
|
+
comments++;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// Preserve whatever else the config carries; only the version flips.
|
|
594
|
+
const config = JSON.parse(readFileSync(join(taskDir, CONFIG_FILE), "utf8"));
|
|
595
|
+
config.version = 2;
|
|
596
|
+
writeFileSync(join(taskDir, CONFIG_FILE), `${JSON.stringify(config, null, 2)}\n`);
|
|
597
|
+
writeFileSync(join(taskDir, ".gitignore"), GITIGNORE);
|
|
598
|
+
return { tasks: tasks.length, comments };
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
legacy.close();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
//# sourceMappingURL=file-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export { TaskStore,
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
1
|
+
export { TaskStore, boardConfig, derivePrefix, findBoards, findBoardsByPrefix, findRoot, findScopeRoot, type BoardRef, type Store, } from "./store.ts";
|
|
2
|
+
export { ARCHIVE_DIR, FileStore, TICKETS_DIR, initProject, migrateBoard, openBoard, } from "./file-store.ts";
|
|
3
|
+
export { parseTicket, serializeTicket, parseComment, serializeComment, type TicketDoc, type CommentDoc, } from "./ticket-doc.ts";
|
|
4
|
+
export { createTaskServer } from "./server.ts";
|
|
5
|
+
export { resolveAuthor, ANONYMOUS, type Author, type AuthorSource } from "./author.ts";
|
|
6
|
+
export * from "./types.ts";
|
|
5
7
|
//# sourceMappingURL=index.d.ts.map
|