@nickmeriano/task 0.1.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/LICENSE +21 -0
- package/README.md +68 -0
- package/dist/author.d.ts +17 -0
- package/dist/author.d.ts.map +1 -0
- package/dist/author.js +52 -0
- package/dist/author.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +415 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +292 -0
- package/dist/server.js.map +1 -0
- package/dist/store.d.ts +63 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +346 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +81 -0
- package/skill/SKILL.md +109 -0
- package/src/author.ts +57 -0
- package/src/cli.ts +419 -0
- package/src/index.ts +6 -0
- package/src/server.ts +311 -0
- package/src/store.ts +418 -0
- package/src/types.ts +75 -0
- package/ui/dist/assets/index-BopXdeSy.js +229 -0
- package/ui/dist/assets/index-Cl_P2tLU.css +1 -0
- package/ui/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/ui/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/ui/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/ui/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/ui/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/ui/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/ui/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/ui/dist/icon.svg +4 -0
- package/ui/dist/index.html +21 -0
package/dist/store.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
4
|
+
export const TASK_DIR = ".task";
|
|
5
|
+
export const DB_FILE = "tasks.db";
|
|
6
|
+
export const CONFIG_FILE = "config.json";
|
|
7
|
+
/** Spacing between adjacent board positions — leaves room to drop between. */
|
|
8
|
+
const POSITION_GAP = 1024;
|
|
9
|
+
/**
|
|
10
|
+
* Walk up from `from` looking for a `.task/config.json`, the way git finds its
|
|
11
|
+
* `.git`. Returns the directory that *contains* `.task`, or null.
|
|
12
|
+
*/
|
|
13
|
+
export function findRoot(from) {
|
|
14
|
+
let dir = from;
|
|
15
|
+
for (;;) {
|
|
16
|
+
if (existsSync(join(dir, TASK_DIR, CONFIG_FILE)))
|
|
17
|
+
return dir;
|
|
18
|
+
const parent = dirname(dir);
|
|
19
|
+
if (parent === dir)
|
|
20
|
+
return null;
|
|
21
|
+
dir = parent;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Directories that never contain a board worth serving. */
|
|
25
|
+
const SKIP_DIRS = new Set(["node_modules", "dist", "build", "out", "coverage", "target"]);
|
|
26
|
+
/**
|
|
27
|
+
* The multi-board complement of `findRoot`: walk *down* from `serveRoot`
|
|
28
|
+
* collecting every directory that holds a `.task/config.json`, so one
|
|
29
|
+
* `task serve` at a monorepo root can serve every nested board. Boards are
|
|
30
|
+
* identified by relative path — unique where names need not be. Dot-dirs,
|
|
31
|
+
* dependency/build dirs and symlinks are skipped; unreadable dirs are ignored.
|
|
32
|
+
*/
|
|
33
|
+
export function findBoards(serveRoot, maxDepth = 6) {
|
|
34
|
+
const boards = [];
|
|
35
|
+
let level = [serveRoot];
|
|
36
|
+
for (let depth = 0; depth <= maxDepth && level.length > 0; depth++) {
|
|
37
|
+
const next = [];
|
|
38
|
+
for (const dir of level) {
|
|
39
|
+
if (existsSync(join(dir, TASK_DIR, CONFIG_FILE))) {
|
|
40
|
+
const rel = relative(serveRoot, dir).split(sep).join("/");
|
|
41
|
+
boards.push({ id: rel === "" ? "." : rel, root: dir });
|
|
42
|
+
}
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
// isDirectory() is false for symlinks — that's the cycle/escape guard.
|
|
52
|
+
if (!entry.isDirectory())
|
|
53
|
+
continue;
|
|
54
|
+
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name))
|
|
55
|
+
continue;
|
|
56
|
+
next.push(join(dir, entry.name));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
level = next;
|
|
60
|
+
}
|
|
61
|
+
// "." first, then lexicographic: the default board is deterministic.
|
|
62
|
+
boards.sort((a, b) => (a.id === "." ? -1 : b.id === "." ? 1 : a.id < b.id ? -1 : 1));
|
|
63
|
+
return boards;
|
|
64
|
+
}
|
|
65
|
+
/** Derive an id prefix from a project name: "phone agent" → "PHONE". */
|
|
66
|
+
export function derivePrefix(name) {
|
|
67
|
+
const word = name.toUpperCase().replace(/[^A-Z0-9]+/g, " ").trim().split(" ")[0];
|
|
68
|
+
return (word || "TASK").slice(0, 10);
|
|
69
|
+
}
|
|
70
|
+
function now() {
|
|
71
|
+
return new Date().toISOString();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The whole persistence layer: one SQLite database inside `.task/`, opened
|
|
75
|
+
* synchronously via node:sqlite (built into Node ≥22.13 — zero dependencies,
|
|
76
|
+
* nothing to compile, safe for `npx`).
|
|
77
|
+
*/
|
|
78
|
+
export class TaskStore {
|
|
79
|
+
root;
|
|
80
|
+
taskDir;
|
|
81
|
+
config;
|
|
82
|
+
db;
|
|
83
|
+
constructor(root) {
|
|
84
|
+
this.root = root;
|
|
85
|
+
this.taskDir = join(root, TASK_DIR);
|
|
86
|
+
this.config = JSON.parse(readFileSync(join(this.taskDir, CONFIG_FILE), "utf8"));
|
|
87
|
+
this.db = new DatabaseSync(join(this.taskDir, DB_FILE));
|
|
88
|
+
// Multiple writers (CLI + serve + agents) are expected; wait for locks
|
|
89
|
+
// instead of failing fast with SQLITE_BUSY.
|
|
90
|
+
this.db.exec("PRAGMA busy_timeout = 3000");
|
|
91
|
+
this.db.exec("PRAGMA foreign_keys = ON");
|
|
92
|
+
migrate(this.db);
|
|
93
|
+
}
|
|
94
|
+
close() {
|
|
95
|
+
this.db.close();
|
|
96
|
+
}
|
|
97
|
+
displayId(number) {
|
|
98
|
+
return `${this.config.prefix}-${number}`;
|
|
99
|
+
}
|
|
100
|
+
/** Accepts "PHONE-12", "phone-12" or "12". */
|
|
101
|
+
parseId(ref) {
|
|
102
|
+
const match = /^(?:[A-Za-z0-9]+-)?(\d+)$/.exec(ref.trim());
|
|
103
|
+
if (!match)
|
|
104
|
+
throw new Error(`invalid task id: ${ref}`);
|
|
105
|
+
return Number(match[1]);
|
|
106
|
+
}
|
|
107
|
+
toTask(row) {
|
|
108
|
+
return {
|
|
109
|
+
id: this.displayId(row.id),
|
|
110
|
+
number: row.id,
|
|
111
|
+
title: row.title,
|
|
112
|
+
description: row.description,
|
|
113
|
+
status: row.status,
|
|
114
|
+
tags: JSON.parse(row.tags),
|
|
115
|
+
milestone: row.milestone,
|
|
116
|
+
needsHuman: row.needs_human !== 0,
|
|
117
|
+
position: row.position,
|
|
118
|
+
createdAt: row.created_at,
|
|
119
|
+
updatedAt: row.updated_at,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
toComment(row) {
|
|
123
|
+
return {
|
|
124
|
+
id: row.id,
|
|
125
|
+
taskId: this.displayId(row.task_id),
|
|
126
|
+
author: row.author,
|
|
127
|
+
body: row.body,
|
|
128
|
+
createdAt: row.created_at,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
list(filter = {}) {
|
|
132
|
+
const where = [];
|
|
133
|
+
const params = [];
|
|
134
|
+
if (filter.statuses?.length) {
|
|
135
|
+
where.push(`status IN (${filter.statuses.map(() => "?").join(", ")})`);
|
|
136
|
+
params.push(...filter.statuses);
|
|
137
|
+
}
|
|
138
|
+
if (filter.milestone) {
|
|
139
|
+
where.push("milestone = ?");
|
|
140
|
+
params.push(filter.milestone);
|
|
141
|
+
}
|
|
142
|
+
if (filter.needsHuman !== undefined) {
|
|
143
|
+
where.push("needs_human = ?");
|
|
144
|
+
params.push(filter.needsHuman ? 1 : 0);
|
|
145
|
+
}
|
|
146
|
+
const sql = `SELECT * FROM tasks${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY position, id`;
|
|
147
|
+
let rows = this.db.prepare(sql).all(...params);
|
|
148
|
+
if (filter.tags?.length) {
|
|
149
|
+
// Tags are a JSON blob, not a table — match them in JS, any-of.
|
|
150
|
+
const wanted = new Set(filter.tags);
|
|
151
|
+
rows = rows.filter((r) => JSON.parse(r.tags).some((t) => wanted.has(t)));
|
|
152
|
+
}
|
|
153
|
+
return rows.map((r) => this.toTask(r));
|
|
154
|
+
}
|
|
155
|
+
get(number) {
|
|
156
|
+
const row = this.db.prepare("SELECT * FROM tasks WHERE id = ?").get(number);
|
|
157
|
+
return row ? this.toTask(row) : null;
|
|
158
|
+
}
|
|
159
|
+
create(input) {
|
|
160
|
+
const status = input.status ?? "todo";
|
|
161
|
+
// New tasks land at the bottom of their column.
|
|
162
|
+
const max = this.db
|
|
163
|
+
.prepare("SELECT MAX(position) AS max FROM tasks WHERE status = ?")
|
|
164
|
+
.get(status);
|
|
165
|
+
const timestamp = now();
|
|
166
|
+
const result = this.db
|
|
167
|
+
.prepare(`INSERT INTO tasks (title, description, status, tags, milestone, needs_human, position, created_at, updated_at)
|
|
168
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
169
|
+
.run(input.title, input.description ?? "", status, JSON.stringify(input.tags ?? []), input.milestone ?? null, input.needsHuman ? 1 : 0, (max.max ?? 0) + POSITION_GAP, timestamp, timestamp);
|
|
170
|
+
return this.get(Number(result.lastInsertRowid));
|
|
171
|
+
}
|
|
172
|
+
update(number, patch) {
|
|
173
|
+
const existing = this.get(number);
|
|
174
|
+
if (!existing)
|
|
175
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
176
|
+
const sets = [];
|
|
177
|
+
const params = [];
|
|
178
|
+
const set = (column, value) => {
|
|
179
|
+
sets.push(`${column} = ?`);
|
|
180
|
+
params.push(value);
|
|
181
|
+
};
|
|
182
|
+
if (patch.title !== undefined)
|
|
183
|
+
set("title", patch.title);
|
|
184
|
+
if (patch.description !== undefined)
|
|
185
|
+
set("description", patch.description);
|
|
186
|
+
if (patch.tags !== undefined)
|
|
187
|
+
set("tags", JSON.stringify(patch.tags));
|
|
188
|
+
if (patch.milestone !== undefined)
|
|
189
|
+
set("milestone", patch.milestone);
|
|
190
|
+
if (patch.needsHuman !== undefined)
|
|
191
|
+
set("needs_human", patch.needsHuman ? 1 : 0);
|
|
192
|
+
if (patch.status !== undefined) {
|
|
193
|
+
set("status", patch.status);
|
|
194
|
+
if (patch.position === undefined && patch.status !== existing.status) {
|
|
195
|
+
// Moved columns without an explicit slot → land on top, where the
|
|
196
|
+
// freshest movement is visible (Linear's behavior).
|
|
197
|
+
const min = this.db
|
|
198
|
+
.prepare("SELECT MIN(position) AS min FROM tasks WHERE status = ?")
|
|
199
|
+
.get(patch.status);
|
|
200
|
+
set("position", (min.min ?? 0) - POSITION_GAP);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (patch.position !== undefined)
|
|
204
|
+
set("position", patch.position);
|
|
205
|
+
if (sets.length === 0)
|
|
206
|
+
return existing;
|
|
207
|
+
set("updated_at", now());
|
|
208
|
+
params.push(number);
|
|
209
|
+
this.db.prepare(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
210
|
+
return this.get(number);
|
|
211
|
+
}
|
|
212
|
+
delete(number) {
|
|
213
|
+
const result = this.db.prepare("DELETE FROM tasks WHERE id = ?").run(number);
|
|
214
|
+
if (result.changes === 0)
|
|
215
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
216
|
+
}
|
|
217
|
+
comments(number) {
|
|
218
|
+
const rows = this.db
|
|
219
|
+
.prepare("SELECT * FROM comments WHERE task_id = ? ORDER BY id")
|
|
220
|
+
.all(number);
|
|
221
|
+
return rows.map((r) => this.toComment(r));
|
|
222
|
+
}
|
|
223
|
+
addComment(number, body, author = "") {
|
|
224
|
+
if (!this.get(number))
|
|
225
|
+
throw new Error(`no such task: ${this.displayId(number)}`);
|
|
226
|
+
const result = this.db
|
|
227
|
+
.prepare("INSERT INTO comments (task_id, author, body, created_at) VALUES (?, ?, ?, ?)")
|
|
228
|
+
.run(number, author, body, now());
|
|
229
|
+
const row = this.db
|
|
230
|
+
.prepare("SELECT * FROM comments WHERE id = ?")
|
|
231
|
+
.get(Number(result.lastInsertRowid));
|
|
232
|
+
return this.toComment(row);
|
|
233
|
+
}
|
|
234
|
+
deleteComment(number, commentId) {
|
|
235
|
+
const row = this.db
|
|
236
|
+
.prepare("SELECT * FROM comments WHERE id = ? AND task_id = ?")
|
|
237
|
+
.get(commentId, number);
|
|
238
|
+
if (!row)
|
|
239
|
+
throw new Error(`no such comment on ${this.displayId(number)}: ${commentId}`);
|
|
240
|
+
this.db.prepare("DELETE FROM comments WHERE id = ?").run(commentId);
|
|
241
|
+
return this.toComment(row);
|
|
242
|
+
}
|
|
243
|
+
commentCounts() {
|
|
244
|
+
const rows = this.db
|
|
245
|
+
.prepare("SELECT task_id, COUNT(*) AS count FROM comments GROUP BY task_id")
|
|
246
|
+
.all();
|
|
247
|
+
return new Map(rows.map((r) => [r.task_id, r.count]));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Schema history, one entry per version, applied in order from whatever
|
|
252
|
+
* `PRAGMA user_version` the file is at. `.task/tasks.db` is committed to git,
|
|
253
|
+
* so an existing database is the normal case, not the exception: every step
|
|
254
|
+
* has to be safe to run against a file someone else's checkout wrote.
|
|
255
|
+
*
|
|
256
|
+
* There is one linear path — a fresh database runs *every* step, so it lands
|
|
257
|
+
* on a schema byte-identical to a migrated one, and there's no second
|
|
258
|
+
* definition of "current" to keep in sync.
|
|
259
|
+
*/
|
|
260
|
+
const MIGRATIONS = [
|
|
261
|
+
// v1 — the original schema. `IF NOT EXISTS` is load-bearing: user_version 0
|
|
262
|
+
// means "fresh *or* written before versioning existed", and this is the step
|
|
263
|
+
// that makes both of those safe to start from.
|
|
264
|
+
//
|
|
265
|
+
// The odd indentation is deliberate and load-bearing: SQLite stores the
|
|
266
|
+
// CREATE statement *verbatim* in sqlite_master, so keeping this byte-for-byte
|
|
267
|
+
// as it originally shipped is what lets a freshly-created database and a
|
|
268
|
+
// migrated one end up with an identical schema rather than one that only
|
|
269
|
+
// matches in structure.
|
|
270
|
+
`
|
|
271
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
272
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
273
|
+
title TEXT NOT NULL,
|
|
274
|
+
description TEXT NOT NULL DEFAULT '',
|
|
275
|
+
status TEXT NOT NULL DEFAULT 'todo',
|
|
276
|
+
priority TEXT NOT NULL DEFAULT 'none',
|
|
277
|
+
assignee TEXT,
|
|
278
|
+
labels TEXT NOT NULL DEFAULT '[]',
|
|
279
|
+
position REAL NOT NULL DEFAULT 0,
|
|
280
|
+
created_at TEXT NOT NULL,
|
|
281
|
+
updated_at TEXT NOT NULL
|
|
282
|
+
);
|
|
283
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
284
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
285
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
286
|
+
author TEXT NOT NULL DEFAULT '',
|
|
287
|
+
body TEXT NOT NULL,
|
|
288
|
+
created_at TEXT NOT NULL
|
|
289
|
+
);
|
|
290
|
+
`,
|
|
291
|
+
// v2 — labels become tags, priority/assignee give way to milestone and a
|
|
292
|
+
// needs-human flag. RENAME COLUMN (SQLite 3.25+) and DROP COLUMN (3.35+) are
|
|
293
|
+
// both available on Node ≥ 22.13, which bundles 3.49.
|
|
294
|
+
//
|
|
295
|
+
// If DROP COLUMN ever weren't available the escape hatch is the usual table
|
|
296
|
+
// rebuild — but note it would need `PRAGMA foreign_keys = OFF` issued
|
|
297
|
+
// *outside* the transaction (the pragma is a no-op inside one), because
|
|
298
|
+
// comments.task_id references tasks(id).
|
|
299
|
+
`ALTER TABLE tasks RENAME COLUMN labels TO tags;
|
|
300
|
+
ALTER TABLE tasks ADD COLUMN milestone TEXT;
|
|
301
|
+
ALTER TABLE tasks ADD COLUMN needs_human INTEGER NOT NULL DEFAULT 0;
|
|
302
|
+
ALTER TABLE tasks DROP COLUMN priority;
|
|
303
|
+
ALTER TABLE tasks DROP COLUMN assignee;`,
|
|
304
|
+
];
|
|
305
|
+
/**
|
|
306
|
+
* Bring `db` up to the newest schema. DDL and `PRAGMA user_version` are both
|
|
307
|
+
* transactional in SQLite, so a failure halfway leaves the file exactly as it
|
|
308
|
+
* was — schema *and* version.
|
|
309
|
+
*/
|
|
310
|
+
function migrate(db) {
|
|
311
|
+
const { user_version: version } = db.prepare("PRAGMA user_version").get();
|
|
312
|
+
if (version >= MIGRATIONS.length)
|
|
313
|
+
return;
|
|
314
|
+
db.exec("BEGIN");
|
|
315
|
+
try {
|
|
316
|
+
for (const step of MIGRATIONS.slice(version))
|
|
317
|
+
db.exec(step);
|
|
318
|
+
db.exec(`PRAGMA user_version = ${MIGRATIONS.length}`);
|
|
319
|
+
db.exec("COMMIT");
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
db.exec("ROLLBACK");
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Create `.task/` in `root`: config, an empty database, and a .gitignore for
|
|
328
|
+
* SQLite's transient sidecar files (the database itself is meant to be
|
|
329
|
+
* committed — that's the point).
|
|
330
|
+
*/
|
|
331
|
+
export function initProject(root, options) {
|
|
332
|
+
const taskDir = join(root, TASK_DIR);
|
|
333
|
+
if (existsSync(join(taskDir, CONFIG_FILE))) {
|
|
334
|
+
throw new Error(`already initialized: ${join(taskDir, CONFIG_FILE)} exists`);
|
|
335
|
+
}
|
|
336
|
+
mkdirSync(taskDir, { recursive: true });
|
|
337
|
+
const config = {
|
|
338
|
+
name: options.name,
|
|
339
|
+
prefix: options.prefix ?? derivePrefix(options.name),
|
|
340
|
+
version: 1,
|
|
341
|
+
};
|
|
342
|
+
writeFileSync(join(taskDir, CONFIG_FILE), `${JSON.stringify(config, null, 2)}\n`);
|
|
343
|
+
writeFileSync(join(taskDir, ".gitignore"), "# SQLite transients — tasks.db itself is committed.\n*.db-journal\n*.db-wal\n*.db-shm\n");
|
|
344
|
+
return new TaskStore(root);
|
|
345
|
+
}
|
|
346
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** The five workflow states, in board order. Fixed on purpose — a task manager
|
|
2
|
+
* you can configure is a task manager you have to configure. */
|
|
3
|
+
export declare const STATUSES: readonly ["backlog", "todo", "in_progress", "done", "canceled"];
|
|
4
|
+
export type Status = (typeof STATUSES)[number];
|
|
5
|
+
export interface Task {
|
|
6
|
+
/** Display id — `<prefix>-<number>`, e.g. "PHONE-12". */
|
|
7
|
+
id: string;
|
|
8
|
+
number: number;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
status: Status;
|
|
12
|
+
tags: string[];
|
|
13
|
+
milestone: string | null;
|
|
14
|
+
/** This ticket can't be finished by an agent alone. */
|
|
15
|
+
needsHuman: boolean;
|
|
16
|
+
/** Sort key within a status column; smaller sorts first. */
|
|
17
|
+
position: number;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
export interface Comment {
|
|
22
|
+
id: number;
|
|
23
|
+
taskId: string;
|
|
24
|
+
author: string;
|
|
25
|
+
body: string;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ProjectConfig {
|
|
29
|
+
name: string;
|
|
30
|
+
/** Uppercase id prefix, e.g. "PHONE" → PHONE-1, PHONE-2, … */
|
|
31
|
+
prefix: string;
|
|
32
|
+
version: number;
|
|
33
|
+
}
|
|
34
|
+
export interface TaskFilter {
|
|
35
|
+
statuses?: Status[];
|
|
36
|
+
/** Any-of: a task matches if it carries at least one of these tags. */
|
|
37
|
+
tags?: string[];
|
|
38
|
+
milestone?: string;
|
|
39
|
+
/** undefined = don't filter on it at all. */
|
|
40
|
+
needsHuman?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface TaskInput {
|
|
43
|
+
title: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
status?: Status;
|
|
46
|
+
tags?: string[];
|
|
47
|
+
milestone?: string | null;
|
|
48
|
+
needsHuman?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface TaskPatch {
|
|
51
|
+
title?: string;
|
|
52
|
+
description?: string;
|
|
53
|
+
status?: Status;
|
|
54
|
+
tags?: string[];
|
|
55
|
+
milestone?: string | null;
|
|
56
|
+
needsHuman?: boolean;
|
|
57
|
+
/** Explicit board position (used by drag-and-drop in the UI). */
|
|
58
|
+
position?: number;
|
|
59
|
+
}
|
|
60
|
+
export declare function isStatus(value: string): value is Status;
|
|
61
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;gEACgE;AAChE,eAAO,MAAM,QAAQ,iEAMX,CAAA;AACV,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAA;AAE9C,MAAM,WAAW,IAAI;IACnB,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAA;IACnB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,MAAM,CAEvD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The five workflow states, in board order. Fixed on purpose — a task manager
|
|
2
|
+
* you can configure is a task manager you have to configure. */
|
|
3
|
+
export const STATUSES = [
|
|
4
|
+
"backlog",
|
|
5
|
+
"todo",
|
|
6
|
+
"in_progress",
|
|
7
|
+
"done",
|
|
8
|
+
"canceled",
|
|
9
|
+
];
|
|
10
|
+
export function isStatus(value) {
|
|
11
|
+
return STATUSES.includes(value);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;gEACgE;AAChE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,SAAS;IACT,MAAM;IACN,aAAa;IACb,MAAM;IACN,UAAU;CACF,CAAA;AAgEV,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAQ,QAA8B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AACxD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nickmeriano/task",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A task manager that lives in your repo — SQLite in .task/, a zero-dependency CLI for humans and agents, and a live kanban/table UI via `task serve`.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://task.nickmeriano.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/nicmeriano/nickmeriano.com.git",
|
|
11
|
+
"directory": "projects/task/cli"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"task": "./dist/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"task",
|
|
24
|
+
"tasks",
|
|
25
|
+
"todo",
|
|
26
|
+
"kanban",
|
|
27
|
+
"cli",
|
|
28
|
+
"sqlite",
|
|
29
|
+
"agents",
|
|
30
|
+
"local-first",
|
|
31
|
+
"monorepo"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"ui/dist",
|
|
40
|
+
"skill"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -p tsconfig.build.json && vite build ui",
|
|
44
|
+
"prepack": "pnpm build",
|
|
45
|
+
"dev": "vite ui",
|
|
46
|
+
"lint": "eslint .",
|
|
47
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p ui"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22.13"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@fontsource-variable/inter": "^5.3.0",
|
|
54
|
+
"@tailwindcss/vite": "^4.2.0",
|
|
55
|
+
"@tanstack/react-query": "^5.90.5",
|
|
56
|
+
"@tanstack/react-router": "^1.170.18",
|
|
57
|
+
"@tiptap/core": "^3.29.2",
|
|
58
|
+
"@tiptap/extension-list": "^3.29.2",
|
|
59
|
+
"@tiptap/extensions": "^3.29.2",
|
|
60
|
+
"@tiptap/markdown": "^3.29.2",
|
|
61
|
+
"@tiptap/pm": "^3.29.2",
|
|
62
|
+
"@tiptap/react": "^3.29.2",
|
|
63
|
+
"@tiptap/starter-kit": "^3.29.2",
|
|
64
|
+
"@types/node": "^22.19.11",
|
|
65
|
+
"@types/react": "^19.2.7",
|
|
66
|
+
"@types/react-dom": "^19.2.3",
|
|
67
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
68
|
+
"class-variance-authority": "^0.7.1",
|
|
69
|
+
"clsx": "^2.1.1",
|
|
70
|
+
"lucide-react": "^1.26.0",
|
|
71
|
+
"react": "^19.2.0",
|
|
72
|
+
"react-aria-components": "^1.19.0",
|
|
73
|
+
"react-dom": "^19.2.0",
|
|
74
|
+
"shadcn": "^4.14.0",
|
|
75
|
+
"tailwind-merge": "^3.6.0",
|
|
76
|
+
"tailwindcss": "^4.2.0",
|
|
77
|
+
"tw-animate-css": "^1.4.0",
|
|
78
|
+
"typescript": "~5.9.3",
|
|
79
|
+
"vite": "^7.3.1"
|
|
80
|
+
}
|
|
81
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tasks
|
|
3
|
+
description: >-
|
|
4
|
+
Work with this repository's built-in task manager (the `task` CLI — a .task/
|
|
5
|
+
directory with a SQLite database, from @nickmeriano/task). Use this
|
|
6
|
+
skill whenever the user mentions tasks, tickets, issues, todos, backlog,
|
|
7
|
+
kanban, board, "what's next", "what should I work on", or asks you to plan,
|
|
8
|
+
track, or report progress on work — and also on your own initiative: when you
|
|
9
|
+
start a multi-step piece of work in a repo that has a .task/ directory, track
|
|
10
|
+
it here instead of only in your internal todo list, so the user can watch
|
|
11
|
+
progress live on the board and the plan survives your session.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# tasks — the repo's task manager
|
|
15
|
+
|
|
16
|
+
This repository tracks its work in `.task/` at the project root: a SQLite
|
|
17
|
+
database driven by the `task` CLI. Tasks live in the repo, next to the code
|
|
18
|
+
they describe — when you update a task, the user sees it instantly (the board
|
|
19
|
+
UI at `task serve` updates in realtime), and the state is committed with the
|
|
20
|
+
code, so it survives sessions and travels with branches.
|
|
21
|
+
|
|
22
|
+
Run `task` from anywhere in the repo; it walks up to find `.task/` like git
|
|
23
|
+
finds `.git`. If the CLI isn't on PATH, use `npx @nickmeriano/task` instead.
|
|
24
|
+
|
|
25
|
+
## The one rule that matters
|
|
26
|
+
|
|
27
|
+
Always pass `--json` when you need to read a result — it prints stable,
|
|
28
|
+
machine-readable output. Human-format output is for the user's terminal, not
|
|
29
|
+
for parsing.
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
task list [--status s1,s2] [--tag a,b] [--milestone m] [--needs-human] [--all] --json
|
|
35
|
+
task show <id> --json # full task + comments
|
|
36
|
+
task add "Title" [--description text] [--status s] [--tags a,b]
|
|
37
|
+
[--milestone m] [--needs-human] --json
|
|
38
|
+
task update <id> [--title t] [--description text] [--status s] [--tags a,b]
|
|
39
|
+
[--milestone m] [--needs-human | --no-needs-human] --json
|
|
40
|
+
task start <id> # → in_progress
|
|
41
|
+
task done <id> # → done
|
|
42
|
+
task move <id> <status> # any status change
|
|
43
|
+
task comment <id> "text" --author claude
|
|
44
|
+
task delete <id>
|
|
45
|
+
task whoami # who comments are attributed to
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- `<id>` is `PREFIX-12` or just `12`.
|
|
49
|
+
- Statuses: `backlog` `todo` `in_progress` `done` `canceled`.
|
|
50
|
+
- `--tag a,b` matches a task carrying *either* tag, not both.
|
|
51
|
+
- Clear a field by passing it empty: `--tags ""`, `--milestone ""`.
|
|
52
|
+
- `task list` hides done/canceled by default; `--all` shows everything.
|
|
53
|
+
|
|
54
|
+
## How to work with the board
|
|
55
|
+
|
|
56
|
+
**Starting work.** Before picking up a request, check `task list --json` —
|
|
57
|
+
the work may already be tracked. If it is, `task start <id>` so the board
|
|
58
|
+
shows it moving. If it isn't and the work is more than a quick edit, create a
|
|
59
|
+
task first: the user watching the board should be able to tell what you're
|
|
60
|
+
doing without reading your transcript.
|
|
61
|
+
|
|
62
|
+
**While working.** Leave short progress comments at real milestones —
|
|
63
|
+
`task comment 12 "found the root cause: the debounce swallows the last event" --author claude`.
|
|
64
|
+
Comment when something changes understanding, not on a timer. If you discover
|
|
65
|
+
new work you're not going to do now (a bug, a refactor, a follow-up), don't
|
|
66
|
+
let it evaporate: `task add` it to `backlog` or `todo` with enough description
|
|
67
|
+
that someone else could pick it up cold.
|
|
68
|
+
|
|
69
|
+
**Finishing.** `task done <id>` only when the work is actually done — code
|
|
70
|
+
written, checks passing. If you're stopping partway, leave it `in_progress`
|
|
71
|
+
with a comment saying exactly where you stopped and what's left. If the work
|
|
72
|
+
turned out to be unnecessary, `task move <id> canceled` with a comment saying
|
|
73
|
+
why — a canceled task with a reason beats a deleted one.
|
|
74
|
+
|
|
75
|
+
**When you can't finish it alone.** Some tickets need a person: a credential
|
|
76
|
+
only they can issue, a design call that isn't yours to make, an action against
|
|
77
|
+
production. Mark those `--needs-human`, comment saying exactly what you need,
|
|
78
|
+
and leave the task `in_progress` — not `done`. Moving it to `done` because
|
|
79
|
+
your part is finished hides the one thing the board exists to surface. Clear
|
|
80
|
+
the flag with `--no-needs-human` once the person has unblocked it.
|
|
81
|
+
|
|
82
|
+
**Attribution.** Pass `--author claude` on your comments so the user can tell
|
|
83
|
+
your updates from theirs. Their own comments are attributed automatically from
|
|
84
|
+
`git config user.name` — there's nothing to configure, and `task whoami` shows
|
|
85
|
+
who the CLI thinks you are.
|
|
86
|
+
|
|
87
|
+
## Writing good tasks
|
|
88
|
+
|
|
89
|
+
Titles are imperative and specific: "Fix retry loop dropping the last
|
|
90
|
+
webhook", not "webhook bug". Descriptions carry the context you'd want if you
|
|
91
|
+
picked the task up cold: the file paths involved, the constraint that makes it
|
|
92
|
+
non-obvious, the definition of done. Tags are lowercase single words
|
|
93
|
+
(`api`, `infra`, `ui`, `bug`) — reuse the repo's existing tags
|
|
94
|
+
(`task list --all --json` shows what's in use) before inventing new ones. A
|
|
95
|
+
milestone is the one release or push a task belongs to; there's at most one,
|
|
96
|
+
and the same rule applies — join an existing one rather than coining a variant.
|
|
97
|
+
|
|
98
|
+
## The UI
|
|
99
|
+
|
|
100
|
+
`task serve` starts the board (kanban + table) at `http://localhost:4400`.
|
|
101
|
+
It updates live as you run CLI commands — no refresh needed. Offer to start
|
|
102
|
+
it when the user wants to see the state of the work; don't start it unasked
|
|
103
|
+
in a session where nobody will look at it.
|
|
104
|
+
|
|
105
|
+
In a monorepo, `task serve` run at the root serves every nested board (each
|
|
106
|
+
package's `.task/`) from one server — the header becomes a board switcher.
|
|
107
|
+
This changes nothing for the other commands: they still operate on the
|
|
108
|
+
nearest `.task/` walking up from the current directory, so `cd` into the
|
|
109
|
+
package whose board you mean before running them.
|