@hasna/todos 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +125 -68
- package/dist/index.js +117 -65
- package/dist/mcp/index.js +76 -26
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2154,6 +2154,13 @@ function ensureTableMigrations(db) {
|
|
|
2154
2154
|
db.exec(MIGRATIONS[4]);
|
|
2155
2155
|
}
|
|
2156
2156
|
} catch {}
|
|
2157
|
+
try {
|
|
2158
|
+
db.query("SELECT task_prefix FROM projects LIMIT 0").get();
|
|
2159
|
+
} catch {
|
|
2160
|
+
try {
|
|
2161
|
+
db.exec(MIGRATIONS[5]);
|
|
2162
|
+
} catch {}
|
|
2163
|
+
}
|
|
2157
2164
|
}
|
|
2158
2165
|
function backfillTaskTags(db) {
|
|
2159
2166
|
try {
|
|
@@ -2358,6 +2365,15 @@ var init_database = __esm(() => {
|
|
|
2358
2365
|
CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id);
|
|
2359
2366
|
|
|
2360
2367
|
INSERT OR IGNORE INTO _migrations (id) VALUES (5);
|
|
2368
|
+
`,
|
|
2369
|
+
`
|
|
2370
|
+
ALTER TABLE projects ADD COLUMN task_prefix TEXT;
|
|
2371
|
+
ALTER TABLE projects ADD COLUMN task_counter INTEGER NOT NULL DEFAULT 0;
|
|
2372
|
+
|
|
2373
|
+
ALTER TABLE tasks ADD COLUMN short_id TEXT;
|
|
2374
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL;
|
|
2375
|
+
|
|
2376
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
2361
2377
|
`
|
|
2362
2378
|
];
|
|
2363
2379
|
});
|
|
@@ -2431,6 +2447,99 @@ var init_types = __esm(() => {
|
|
|
2431
2447
|
};
|
|
2432
2448
|
});
|
|
2433
2449
|
|
|
2450
|
+
// src/db/projects.ts
|
|
2451
|
+
function slugify(name) {
|
|
2452
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2453
|
+
}
|
|
2454
|
+
function generatePrefix(name, db) {
|
|
2455
|
+
const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
|
|
2456
|
+
let prefix;
|
|
2457
|
+
if (words.length >= 3) {
|
|
2458
|
+
prefix = words.slice(0, 3).map((w) => w[0].toUpperCase()).join("");
|
|
2459
|
+
} else if (words.length === 2) {
|
|
2460
|
+
prefix = (words[0].slice(0, 2) + words[1][0]).toUpperCase();
|
|
2461
|
+
} else {
|
|
2462
|
+
prefix = words[0].slice(0, 3).toUpperCase();
|
|
2463
|
+
}
|
|
2464
|
+
let candidate = prefix;
|
|
2465
|
+
let suffix = 1;
|
|
2466
|
+
while (true) {
|
|
2467
|
+
const existing = db.query("SELECT id FROM projects WHERE task_prefix = ?").get(candidate);
|
|
2468
|
+
if (!existing)
|
|
2469
|
+
return candidate;
|
|
2470
|
+
suffix++;
|
|
2471
|
+
candidate = `${prefix}${suffix}`;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
function createProject(input, db) {
|
|
2475
|
+
const d = db || getDatabase();
|
|
2476
|
+
const id = uuid();
|
|
2477
|
+
const timestamp = now();
|
|
2478
|
+
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
2479
|
+
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
2480
|
+
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
|
|
2481
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
|
|
2482
|
+
return getProject(id, d);
|
|
2483
|
+
}
|
|
2484
|
+
function getProject(id, db) {
|
|
2485
|
+
const d = db || getDatabase();
|
|
2486
|
+
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
2487
|
+
return row;
|
|
2488
|
+
}
|
|
2489
|
+
function getProjectByPath(path, db) {
|
|
2490
|
+
const d = db || getDatabase();
|
|
2491
|
+
const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
2492
|
+
return row;
|
|
2493
|
+
}
|
|
2494
|
+
function listProjects(db) {
|
|
2495
|
+
const d = db || getDatabase();
|
|
2496
|
+
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
2497
|
+
}
|
|
2498
|
+
function updateProject(id, input, db) {
|
|
2499
|
+
const d = db || getDatabase();
|
|
2500
|
+
const project = getProject(id, d);
|
|
2501
|
+
if (!project)
|
|
2502
|
+
throw new ProjectNotFoundError(id);
|
|
2503
|
+
const sets = ["updated_at = ?"];
|
|
2504
|
+
const params = [now()];
|
|
2505
|
+
if (input.name !== undefined) {
|
|
2506
|
+
sets.push("name = ?");
|
|
2507
|
+
params.push(input.name);
|
|
2508
|
+
}
|
|
2509
|
+
if (input.description !== undefined) {
|
|
2510
|
+
sets.push("description = ?");
|
|
2511
|
+
params.push(input.description);
|
|
2512
|
+
}
|
|
2513
|
+
if (input.task_list_id !== undefined) {
|
|
2514
|
+
sets.push("task_list_id = ?");
|
|
2515
|
+
params.push(input.task_list_id);
|
|
2516
|
+
}
|
|
2517
|
+
params.push(id);
|
|
2518
|
+
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
2519
|
+
return getProject(id, d);
|
|
2520
|
+
}
|
|
2521
|
+
function nextTaskShortId(projectId, db) {
|
|
2522
|
+
const d = db || getDatabase();
|
|
2523
|
+
const project = getProject(projectId, d);
|
|
2524
|
+
if (!project || !project.task_prefix)
|
|
2525
|
+
return null;
|
|
2526
|
+
d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
|
|
2527
|
+
const updated = getProject(projectId, d);
|
|
2528
|
+
const padded = String(updated.task_counter).padStart(5, "0");
|
|
2529
|
+
return `${updated.task_prefix}-${padded}`;
|
|
2530
|
+
}
|
|
2531
|
+
function ensureProject(name, path, db) {
|
|
2532
|
+
const d = db || getDatabase();
|
|
2533
|
+
const existing = getProjectByPath(path, d);
|
|
2534
|
+
if (existing)
|
|
2535
|
+
return existing;
|
|
2536
|
+
return createProject({ name, path }, d);
|
|
2537
|
+
}
|
|
2538
|
+
var init_projects = __esm(() => {
|
|
2539
|
+
init_types();
|
|
2540
|
+
init_database();
|
|
2541
|
+
});
|
|
2542
|
+
|
|
2434
2543
|
// src/db/tasks.ts
|
|
2435
2544
|
function rowToTask(row) {
|
|
2436
2545
|
return {
|
|
@@ -2459,14 +2568,17 @@ function createTask(input, db) {
|
|
|
2459
2568
|
const id = uuid();
|
|
2460
2569
|
const timestamp = now();
|
|
2461
2570
|
const tags = input.tags || [];
|
|
2462
|
-
|
|
2463
|
-
|
|
2571
|
+
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
2572
|
+
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
2573
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at)
|
|
2574
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
|
|
2464
2575
|
id,
|
|
2576
|
+
shortId,
|
|
2465
2577
|
input.project_id || null,
|
|
2466
2578
|
input.parent_id || null,
|
|
2467
2579
|
input.plan_id || null,
|
|
2468
2580
|
input.task_list_id || null,
|
|
2469
|
-
|
|
2581
|
+
title,
|
|
2470
2582
|
input.description || null,
|
|
2471
2583
|
input.status || "pending",
|
|
2472
2584
|
input.priority || "medium",
|
|
@@ -2757,68 +2869,7 @@ function wouldCreateCycle(taskId, dependsOn, db) {
|
|
|
2757
2869
|
var init_tasks = __esm(() => {
|
|
2758
2870
|
init_types();
|
|
2759
2871
|
init_database();
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
// src/db/projects.ts
|
|
2763
|
-
function slugify(name) {
|
|
2764
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2765
|
-
}
|
|
2766
|
-
function createProject(input, db) {
|
|
2767
|
-
const d = db || getDatabase();
|
|
2768
|
-
const id = uuid();
|
|
2769
|
-
const timestamp = now();
|
|
2770
|
-
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
2771
|
-
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, created_at, updated_at)
|
|
2772
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, timestamp, timestamp]);
|
|
2773
|
-
return getProject(id, d);
|
|
2774
|
-
}
|
|
2775
|
-
function getProject(id, db) {
|
|
2776
|
-
const d = db || getDatabase();
|
|
2777
|
-
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
2778
|
-
return row;
|
|
2779
|
-
}
|
|
2780
|
-
function getProjectByPath(path, db) {
|
|
2781
|
-
const d = db || getDatabase();
|
|
2782
|
-
const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
2783
|
-
return row;
|
|
2784
|
-
}
|
|
2785
|
-
function listProjects(db) {
|
|
2786
|
-
const d = db || getDatabase();
|
|
2787
|
-
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
2788
|
-
}
|
|
2789
|
-
function updateProject(id, input, db) {
|
|
2790
|
-
const d = db || getDatabase();
|
|
2791
|
-
const project = getProject(id, d);
|
|
2792
|
-
if (!project)
|
|
2793
|
-
throw new ProjectNotFoundError(id);
|
|
2794
|
-
const sets = ["updated_at = ?"];
|
|
2795
|
-
const params = [now()];
|
|
2796
|
-
if (input.name !== undefined) {
|
|
2797
|
-
sets.push("name = ?");
|
|
2798
|
-
params.push(input.name);
|
|
2799
|
-
}
|
|
2800
|
-
if (input.description !== undefined) {
|
|
2801
|
-
sets.push("description = ?");
|
|
2802
|
-
params.push(input.description);
|
|
2803
|
-
}
|
|
2804
|
-
if (input.task_list_id !== undefined) {
|
|
2805
|
-
sets.push("task_list_id = ?");
|
|
2806
|
-
params.push(input.task_list_id);
|
|
2807
|
-
}
|
|
2808
|
-
params.push(id);
|
|
2809
|
-
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
2810
|
-
return getProject(id, d);
|
|
2811
|
-
}
|
|
2812
|
-
function ensureProject(name, path, db) {
|
|
2813
|
-
const d = db || getDatabase();
|
|
2814
|
-
const existing = getProjectByPath(path, d);
|
|
2815
|
-
if (existing)
|
|
2816
|
-
return existing;
|
|
2817
|
-
return createProject({ name, path }, d);
|
|
2818
|
-
}
|
|
2819
|
-
var init_projects = __esm(() => {
|
|
2820
|
-
init_types();
|
|
2821
|
-
init_database();
|
|
2872
|
+
init_projects();
|
|
2822
2873
|
});
|
|
2823
2874
|
|
|
2824
2875
|
// src/db/agents.ts
|
|
@@ -9565,9 +9616,11 @@ function formatTaskLine(t) {
|
|
|
9565
9616
|
return `${chalk.dim(t.id.slice(0, 8))} ${statusFn(t.status.padEnd(11))} ${priorityFn(t.priority.padEnd(8))} ${t.title}${assigned}${lock}${tags}${plan}`;
|
|
9566
9617
|
}
|
|
9567
9618
|
program2.name("todos").description("Universal task management for AI coding agents").version(getPackageVersion()).option("--project <path>", "Project path").option("--json", "Output as JSON").option("--agent <name>", "Agent name").option("--session <id>", "Session ID");
|
|
9568
|
-
program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("--tags <tags>", "Comma-separated tags").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").action((title, opts) => {
|
|
9619
|
+
program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").action((title, opts) => {
|
|
9569
9620
|
const globalOpts = program2.opts();
|
|
9570
9621
|
const projectId = autoProject(globalOpts);
|
|
9622
|
+
opts.tags = opts.tags || opts.tag;
|
|
9623
|
+
opts.list = opts.list || opts.taskList;
|
|
9571
9624
|
const taskListId = opts.list ? (() => {
|
|
9572
9625
|
const db = getDatabase();
|
|
9573
9626
|
const id = resolvePartialId(db, "task_lists", opts.list);
|
|
@@ -9607,8 +9660,10 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
|
|
|
9607
9660
|
console.log(formatTaskLine(task));
|
|
9608
9661
|
}
|
|
9609
9662
|
});
|
|
9610
|
-
program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").action((opts) => {
|
|
9663
|
+
program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").option("--task-list <id>", "Filter by task list ID (alias for --list)").action((opts) => {
|
|
9611
9664
|
const globalOpts = program2.opts();
|
|
9665
|
+
opts.tags = opts.tags || opts.tag;
|
|
9666
|
+
opts.list = opts.list || opts.taskList;
|
|
9612
9667
|
const projectId = autoProject(globalOpts);
|
|
9613
9668
|
const filter = {};
|
|
9614
9669
|
if (projectId)
|
|
@@ -9720,8 +9775,10 @@ program2.command("show <id>").description("Show full task details").action((id)
|
|
|
9720
9775
|
}
|
|
9721
9776
|
}
|
|
9722
9777
|
});
|
|
9723
|
-
program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--list <id>", "Move to a task list").action((id, opts) => {
|
|
9778
|
+
program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list").option("--task-list <id>", "Move to a task list (alias for --list)").action((id, opts) => {
|
|
9724
9779
|
const globalOpts = program2.opts();
|
|
9780
|
+
opts.tags = opts.tags || opts.tag;
|
|
9781
|
+
opts.list = opts.list || opts.taskList;
|
|
9725
9782
|
const resolvedId = resolveTaskId(id);
|
|
9726
9783
|
const current = getTask(resolvedId);
|
|
9727
9784
|
if (!current) {
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,15 @@ var MIGRATIONS = [
|
|
|
194
194
|
CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id);
|
|
195
195
|
|
|
196
196
|
INSERT OR IGNORE INTO _migrations (id) VALUES (5);
|
|
197
|
+
`,
|
|
198
|
+
`
|
|
199
|
+
ALTER TABLE projects ADD COLUMN task_prefix TEXT;
|
|
200
|
+
ALTER TABLE projects ADD COLUMN task_counter INTEGER NOT NULL DEFAULT 0;
|
|
201
|
+
|
|
202
|
+
ALTER TABLE tasks ADD COLUMN short_id TEXT;
|
|
203
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL;
|
|
204
|
+
|
|
205
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
197
206
|
`
|
|
198
207
|
];
|
|
199
208
|
var _db = null;
|
|
@@ -231,6 +240,13 @@ function ensureTableMigrations(db) {
|
|
|
231
240
|
db.exec(MIGRATIONS[4]);
|
|
232
241
|
}
|
|
233
242
|
} catch {}
|
|
243
|
+
try {
|
|
244
|
+
db.query("SELECT task_prefix FROM projects LIMIT 0").get();
|
|
245
|
+
} catch {
|
|
246
|
+
try {
|
|
247
|
+
db.exec(MIGRATIONS[5]);
|
|
248
|
+
} catch {}
|
|
249
|
+
}
|
|
234
250
|
}
|
|
235
251
|
function backfillTaskTags(db) {
|
|
236
252
|
try {
|
|
@@ -401,6 +417,100 @@ class DependencyCycleError extends Error {
|
|
|
401
417
|
}
|
|
402
418
|
}
|
|
403
419
|
|
|
420
|
+
// src/db/projects.ts
|
|
421
|
+
function slugify(name) {
|
|
422
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
423
|
+
}
|
|
424
|
+
function generatePrefix(name, db) {
|
|
425
|
+
const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
|
|
426
|
+
let prefix;
|
|
427
|
+
if (words.length >= 3) {
|
|
428
|
+
prefix = words.slice(0, 3).map((w) => w[0].toUpperCase()).join("");
|
|
429
|
+
} else if (words.length === 2) {
|
|
430
|
+
prefix = (words[0].slice(0, 2) + words[1][0]).toUpperCase();
|
|
431
|
+
} else {
|
|
432
|
+
prefix = words[0].slice(0, 3).toUpperCase();
|
|
433
|
+
}
|
|
434
|
+
let candidate = prefix;
|
|
435
|
+
let suffix = 1;
|
|
436
|
+
while (true) {
|
|
437
|
+
const existing = db.query("SELECT id FROM projects WHERE task_prefix = ?").get(candidate);
|
|
438
|
+
if (!existing)
|
|
439
|
+
return candidate;
|
|
440
|
+
suffix++;
|
|
441
|
+
candidate = `${prefix}${suffix}`;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function createProject(input, db) {
|
|
445
|
+
const d = db || getDatabase();
|
|
446
|
+
const id = uuid();
|
|
447
|
+
const timestamp = now();
|
|
448
|
+
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
449
|
+
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
450
|
+
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
|
|
451
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
|
|
452
|
+
return getProject(id, d);
|
|
453
|
+
}
|
|
454
|
+
function getProject(id, db) {
|
|
455
|
+
const d = db || getDatabase();
|
|
456
|
+
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
457
|
+
return row;
|
|
458
|
+
}
|
|
459
|
+
function getProjectByPath(path, db) {
|
|
460
|
+
const d = db || getDatabase();
|
|
461
|
+
const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
462
|
+
return row;
|
|
463
|
+
}
|
|
464
|
+
function listProjects(db) {
|
|
465
|
+
const d = db || getDatabase();
|
|
466
|
+
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
467
|
+
}
|
|
468
|
+
function updateProject(id, input, db) {
|
|
469
|
+
const d = db || getDatabase();
|
|
470
|
+
const project = getProject(id, d);
|
|
471
|
+
if (!project)
|
|
472
|
+
throw new ProjectNotFoundError(id);
|
|
473
|
+
const sets = ["updated_at = ?"];
|
|
474
|
+
const params = [now()];
|
|
475
|
+
if (input.name !== undefined) {
|
|
476
|
+
sets.push("name = ?");
|
|
477
|
+
params.push(input.name);
|
|
478
|
+
}
|
|
479
|
+
if (input.description !== undefined) {
|
|
480
|
+
sets.push("description = ?");
|
|
481
|
+
params.push(input.description);
|
|
482
|
+
}
|
|
483
|
+
if (input.task_list_id !== undefined) {
|
|
484
|
+
sets.push("task_list_id = ?");
|
|
485
|
+
params.push(input.task_list_id);
|
|
486
|
+
}
|
|
487
|
+
params.push(id);
|
|
488
|
+
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
489
|
+
return getProject(id, d);
|
|
490
|
+
}
|
|
491
|
+
function deleteProject(id, db) {
|
|
492
|
+
const d = db || getDatabase();
|
|
493
|
+
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
494
|
+
return result.changes > 0;
|
|
495
|
+
}
|
|
496
|
+
function nextTaskShortId(projectId, db) {
|
|
497
|
+
const d = db || getDatabase();
|
|
498
|
+
const project = getProject(projectId, d);
|
|
499
|
+
if (!project || !project.task_prefix)
|
|
500
|
+
return null;
|
|
501
|
+
d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
|
|
502
|
+
const updated = getProject(projectId, d);
|
|
503
|
+
const padded = String(updated.task_counter).padStart(5, "0");
|
|
504
|
+
return `${updated.task_prefix}-${padded}`;
|
|
505
|
+
}
|
|
506
|
+
function ensureProject(name, path, db) {
|
|
507
|
+
const d = db || getDatabase();
|
|
508
|
+
const existing = getProjectByPath(path, d);
|
|
509
|
+
if (existing)
|
|
510
|
+
return existing;
|
|
511
|
+
return createProject({ name, path }, d);
|
|
512
|
+
}
|
|
513
|
+
|
|
404
514
|
// src/db/tasks.ts
|
|
405
515
|
function rowToTask(row) {
|
|
406
516
|
return {
|
|
@@ -429,14 +539,17 @@ function createTask(input, db) {
|
|
|
429
539
|
const id = uuid();
|
|
430
540
|
const timestamp = now();
|
|
431
541
|
const tags = input.tags || [];
|
|
432
|
-
|
|
433
|
-
|
|
542
|
+
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
543
|
+
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
544
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at)
|
|
545
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
|
|
434
546
|
id,
|
|
547
|
+
shortId,
|
|
435
548
|
input.project_id || null,
|
|
436
549
|
input.parent_id || null,
|
|
437
550
|
input.plan_id || null,
|
|
438
551
|
input.task_list_id || null,
|
|
439
|
-
|
|
552
|
+
title,
|
|
440
553
|
input.description || null,
|
|
441
554
|
input.status || "pending",
|
|
442
555
|
input.priority || "medium",
|
|
@@ -732,68 +845,6 @@ function wouldCreateCycle(taskId, dependsOn, db) {
|
|
|
732
845
|
}
|
|
733
846
|
return false;
|
|
734
847
|
}
|
|
735
|
-
// src/db/projects.ts
|
|
736
|
-
function slugify(name) {
|
|
737
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
738
|
-
}
|
|
739
|
-
function createProject(input, db) {
|
|
740
|
-
const d = db || getDatabase();
|
|
741
|
-
const id = uuid();
|
|
742
|
-
const timestamp = now();
|
|
743
|
-
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
744
|
-
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, created_at, updated_at)
|
|
745
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, timestamp, timestamp]);
|
|
746
|
-
return getProject(id, d);
|
|
747
|
-
}
|
|
748
|
-
function getProject(id, db) {
|
|
749
|
-
const d = db || getDatabase();
|
|
750
|
-
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
751
|
-
return row;
|
|
752
|
-
}
|
|
753
|
-
function getProjectByPath(path, db) {
|
|
754
|
-
const d = db || getDatabase();
|
|
755
|
-
const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
756
|
-
return row;
|
|
757
|
-
}
|
|
758
|
-
function listProjects(db) {
|
|
759
|
-
const d = db || getDatabase();
|
|
760
|
-
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
761
|
-
}
|
|
762
|
-
function updateProject(id, input, db) {
|
|
763
|
-
const d = db || getDatabase();
|
|
764
|
-
const project = getProject(id, d);
|
|
765
|
-
if (!project)
|
|
766
|
-
throw new ProjectNotFoundError(id);
|
|
767
|
-
const sets = ["updated_at = ?"];
|
|
768
|
-
const params = [now()];
|
|
769
|
-
if (input.name !== undefined) {
|
|
770
|
-
sets.push("name = ?");
|
|
771
|
-
params.push(input.name);
|
|
772
|
-
}
|
|
773
|
-
if (input.description !== undefined) {
|
|
774
|
-
sets.push("description = ?");
|
|
775
|
-
params.push(input.description);
|
|
776
|
-
}
|
|
777
|
-
if (input.task_list_id !== undefined) {
|
|
778
|
-
sets.push("task_list_id = ?");
|
|
779
|
-
params.push(input.task_list_id);
|
|
780
|
-
}
|
|
781
|
-
params.push(id);
|
|
782
|
-
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
783
|
-
return getProject(id, d);
|
|
784
|
-
}
|
|
785
|
-
function deleteProject(id, db) {
|
|
786
|
-
const d = db || getDatabase();
|
|
787
|
-
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
788
|
-
return result.changes > 0;
|
|
789
|
-
}
|
|
790
|
-
function ensureProject(name, path, db) {
|
|
791
|
-
const d = db || getDatabase();
|
|
792
|
-
const existing = getProjectByPath(path, d);
|
|
793
|
-
if (existing)
|
|
794
|
-
return existing;
|
|
795
|
-
return createProject({ name, path }, d);
|
|
796
|
-
}
|
|
797
848
|
// src/db/plans.ts
|
|
798
849
|
function createPlan(input, db) {
|
|
799
850
|
const d = db || getDatabase();
|
|
@@ -1711,6 +1762,7 @@ export {
|
|
|
1711
1762
|
removeDependency,
|
|
1712
1763
|
registerAgent,
|
|
1713
1764
|
now,
|
|
1765
|
+
nextTaskShortId,
|
|
1714
1766
|
lockTask,
|
|
1715
1767
|
loadConfig,
|
|
1716
1768
|
listTasks,
|
package/dist/mcp/index.js
CHANGED
|
@@ -4244,6 +4244,15 @@ var MIGRATIONS = [
|
|
|
4244
4244
|
CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id);
|
|
4245
4245
|
|
|
4246
4246
|
INSERT OR IGNORE INTO _migrations (id) VALUES (5);
|
|
4247
|
+
`,
|
|
4248
|
+
`
|
|
4249
|
+
ALTER TABLE projects ADD COLUMN task_prefix TEXT;
|
|
4250
|
+
ALTER TABLE projects ADD COLUMN task_counter INTEGER NOT NULL DEFAULT 0;
|
|
4251
|
+
|
|
4252
|
+
ALTER TABLE tasks ADD COLUMN short_id TEXT;
|
|
4253
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL;
|
|
4254
|
+
|
|
4255
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
4247
4256
|
`
|
|
4248
4257
|
];
|
|
4249
4258
|
var _db = null;
|
|
@@ -4281,6 +4290,13 @@ function ensureTableMigrations(db) {
|
|
|
4281
4290
|
db.exec(MIGRATIONS[4]);
|
|
4282
4291
|
}
|
|
4283
4292
|
} catch {}
|
|
4293
|
+
try {
|
|
4294
|
+
db.query("SELECT task_prefix FROM projects LIMIT 0").get();
|
|
4295
|
+
} catch {
|
|
4296
|
+
try {
|
|
4297
|
+
db.exec(MIGRATIONS[5]);
|
|
4298
|
+
} catch {}
|
|
4299
|
+
}
|
|
4284
4300
|
}
|
|
4285
4301
|
function backfillTaskTags(db) {
|
|
4286
4302
|
try {
|
|
@@ -4347,6 +4363,60 @@ function resolvePartialId(db, table, partialId) {
|
|
|
4347
4363
|
return null;
|
|
4348
4364
|
}
|
|
4349
4365
|
|
|
4366
|
+
// src/db/projects.ts
|
|
4367
|
+
function slugify(name) {
|
|
4368
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4369
|
+
}
|
|
4370
|
+
function generatePrefix(name, db) {
|
|
4371
|
+
const words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
|
|
4372
|
+
let prefix;
|
|
4373
|
+
if (words.length >= 3) {
|
|
4374
|
+
prefix = words.slice(0, 3).map((w) => w[0].toUpperCase()).join("");
|
|
4375
|
+
} else if (words.length === 2) {
|
|
4376
|
+
prefix = (words[0].slice(0, 2) + words[1][0]).toUpperCase();
|
|
4377
|
+
} else {
|
|
4378
|
+
prefix = words[0].slice(0, 3).toUpperCase();
|
|
4379
|
+
}
|
|
4380
|
+
let candidate = prefix;
|
|
4381
|
+
let suffix = 1;
|
|
4382
|
+
while (true) {
|
|
4383
|
+
const existing = db.query("SELECT id FROM projects WHERE task_prefix = ?").get(candidate);
|
|
4384
|
+
if (!existing)
|
|
4385
|
+
return candidate;
|
|
4386
|
+
suffix++;
|
|
4387
|
+
candidate = `${prefix}${suffix}`;
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
function createProject(input, db) {
|
|
4391
|
+
const d = db || getDatabase();
|
|
4392
|
+
const id = uuid();
|
|
4393
|
+
const timestamp = now();
|
|
4394
|
+
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
4395
|
+
const taskPrefix = input.task_prefix || generatePrefix(input.name, d);
|
|
4396
|
+
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, task_prefix, task_counter, created_at, updated_at)
|
|
4397
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, taskPrefix, timestamp, timestamp]);
|
|
4398
|
+
return getProject(id, d);
|
|
4399
|
+
}
|
|
4400
|
+
function getProject(id, db) {
|
|
4401
|
+
const d = db || getDatabase();
|
|
4402
|
+
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
4403
|
+
return row;
|
|
4404
|
+
}
|
|
4405
|
+
function listProjects(db) {
|
|
4406
|
+
const d = db || getDatabase();
|
|
4407
|
+
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
4408
|
+
}
|
|
4409
|
+
function nextTaskShortId(projectId, db) {
|
|
4410
|
+
const d = db || getDatabase();
|
|
4411
|
+
const project = getProject(projectId, d);
|
|
4412
|
+
if (!project || !project.task_prefix)
|
|
4413
|
+
return null;
|
|
4414
|
+
d.run("UPDATE projects SET task_counter = task_counter + 1, updated_at = ? WHERE id = ?", [now(), projectId]);
|
|
4415
|
+
const updated = getProject(projectId, d);
|
|
4416
|
+
const padded = String(updated.task_counter).padStart(5, "0");
|
|
4417
|
+
return `${updated.task_prefix}-${padded}`;
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4350
4420
|
// src/db/tasks.ts
|
|
4351
4421
|
function rowToTask(row) {
|
|
4352
4422
|
return {
|
|
@@ -4375,14 +4445,17 @@ function createTask(input, db) {
|
|
|
4375
4445
|
const id = uuid();
|
|
4376
4446
|
const timestamp = now();
|
|
4377
4447
|
const tags = input.tags || [];
|
|
4378
|
-
|
|
4379
|
-
|
|
4448
|
+
const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
|
|
4449
|
+
const title = shortId ? `${shortId}: ${input.title}` : input.title;
|
|
4450
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at)
|
|
4451
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`, [
|
|
4380
4452
|
id,
|
|
4453
|
+
shortId,
|
|
4381
4454
|
input.project_id || null,
|
|
4382
4455
|
input.parent_id || null,
|
|
4383
4456
|
input.plan_id || null,
|
|
4384
4457
|
input.task_list_id || null,
|
|
4385
|
-
|
|
4458
|
+
title,
|
|
4386
4459
|
input.description || null,
|
|
4387
4460
|
input.status || "pending",
|
|
4388
4461
|
input.priority || "medium",
|
|
@@ -4695,29 +4768,6 @@ function getComment(id, db) {
|
|
|
4695
4768
|
return d.query("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
4696
4769
|
}
|
|
4697
4770
|
|
|
4698
|
-
// src/db/projects.ts
|
|
4699
|
-
function slugify(name) {
|
|
4700
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4701
|
-
}
|
|
4702
|
-
function createProject(input, db) {
|
|
4703
|
-
const d = db || getDatabase();
|
|
4704
|
-
const id = uuid();
|
|
4705
|
-
const timestamp = now();
|
|
4706
|
-
const taskListId = input.task_list_id ?? `todos-${slugify(input.name)}`;
|
|
4707
|
-
d.run(`INSERT INTO projects (id, name, path, description, task_list_id, created_at, updated_at)
|
|
4708
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, input.name, input.path, input.description || null, taskListId, timestamp, timestamp]);
|
|
4709
|
-
return getProject(id, d);
|
|
4710
|
-
}
|
|
4711
|
-
function getProject(id, db) {
|
|
4712
|
-
const d = db || getDatabase();
|
|
4713
|
-
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
4714
|
-
return row;
|
|
4715
|
-
}
|
|
4716
|
-
function listProjects(db) {
|
|
4717
|
-
const d = db || getDatabase();
|
|
4718
|
-
return d.query("SELECT * FROM projects ORDER BY name").all();
|
|
4719
|
-
}
|
|
4720
|
-
|
|
4721
4771
|
// src/db/plans.ts
|
|
4722
4772
|
function createPlan(input, db) {
|
|
4723
4773
|
const d = db || getDatabase();
|