@hasna/todos 0.11.18 → 0.11.20
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 +392 -21
- package/dist/index.js +161 -9
- package/dist/mcp/index.js +257 -18
- package/dist/server/index.js +159 -4
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2260,6 +2260,18 @@ function ensureSchema(db) {
|
|
|
2260
2260
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2261
2261
|
UNIQUE(source_id, source_type, target_id, target_type, relation_type)
|
|
2262
2262
|
)`);
|
|
2263
|
+
ensureTable("project_machine_paths", `
|
|
2264
|
+
CREATE TABLE project_machine_paths (
|
|
2265
|
+
id TEXT PRIMARY KEY,
|
|
2266
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
2267
|
+
machine_id TEXT NOT NULL,
|
|
2268
|
+
path TEXT NOT NULL,
|
|
2269
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2270
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2271
|
+
UNIQUE(project_id, machine_id)
|
|
2272
|
+
)`);
|
|
2273
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id)");
|
|
2274
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id)");
|
|
2263
2275
|
ensureTable("machines", `
|
|
2264
2276
|
CREATE TABLE machines (
|
|
2265
2277
|
id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, hostname TEXT, platform TEXT,
|
|
@@ -2295,6 +2307,7 @@ function ensureSchema(db) {
|
|
|
2295
2307
|
ensureColumn("tasks", "max_retries", "INTEGER DEFAULT 3");
|
|
2296
2308
|
ensureColumn("tasks", "retry_after", "TEXT");
|
|
2297
2309
|
ensureColumn("tasks", "sla_minutes", "INTEGER");
|
|
2310
|
+
ensureColumn("tasks", "archived_at", "TEXT");
|
|
2298
2311
|
ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
|
|
2299
2312
|
ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
|
|
2300
2313
|
ensureColumn("agents", "reports_to", "TEXT");
|
|
@@ -3133,6 +3146,25 @@ var init_schema = __esm(() => {
|
|
|
3133
3146
|
CREATE INDEX IF NOT EXISTS idx_agents_machine ON agents(machine_id);
|
|
3134
3147
|
|
|
3135
3148
|
INSERT OR IGNORE INTO _migrations (id) VALUES (41);
|
|
3149
|
+
`,
|
|
3150
|
+
`
|
|
3151
|
+
CREATE TABLE IF NOT EXISTS project_machine_paths (
|
|
3152
|
+
id TEXT PRIMARY KEY,
|
|
3153
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
3154
|
+
machine_id TEXT NOT NULL,
|
|
3155
|
+
path TEXT NOT NULL,
|
|
3156
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3157
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3158
|
+
UNIQUE(project_id, machine_id)
|
|
3159
|
+
);
|
|
3160
|
+
CREATE INDEX IF NOT EXISTS idx_project_machine_paths_project ON project_machine_paths(project_id);
|
|
3161
|
+
CREATE INDEX IF NOT EXISTS idx_project_machine_paths_machine ON project_machine_paths(machine_id);
|
|
3162
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (42);
|
|
3163
|
+
`,
|
|
3164
|
+
`
|
|
3165
|
+
ALTER TABLE tasks ADD COLUMN archived_at TEXT;
|
|
3166
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_archived ON tasks(archived_at) WHERE archived_at IS NOT NULL;
|
|
3167
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (43);
|
|
3136
3168
|
`
|
|
3137
3169
|
];
|
|
3138
3170
|
});
|
|
@@ -3517,13 +3549,18 @@ var exports_projects = {};
|
|
|
3517
3549
|
__export(exports_projects, {
|
|
3518
3550
|
updateProject: () => updateProject,
|
|
3519
3551
|
slugify: () => slugify,
|
|
3552
|
+
setMachineLocalPath: () => setMachineLocalPath,
|
|
3553
|
+
renameProject: () => renameProject,
|
|
3520
3554
|
removeProjectSource: () => removeProjectSource,
|
|
3555
|
+
removeMachineLocalPath: () => removeMachineLocalPath,
|
|
3521
3556
|
nextTaskShortId: () => nextTaskShortId,
|
|
3522
3557
|
listProjects: () => listProjects,
|
|
3523
3558
|
listProjectSources: () => listProjectSources,
|
|
3559
|
+
listMachineLocalPaths: () => listMachineLocalPaths,
|
|
3524
3560
|
getProjectWithSources: () => getProjectWithSources,
|
|
3525
3561
|
getProjectByPath: () => getProjectByPath,
|
|
3526
3562
|
getProject: () => getProject,
|
|
3563
|
+
getMachineLocalPath: () => getMachineLocalPath,
|
|
3527
3564
|
ensureProject: () => ensureProject,
|
|
3528
3565
|
deleteProject: () => deleteProject,
|
|
3529
3566
|
createProject: () => createProject,
|
|
@@ -3569,8 +3606,15 @@ function getProject(id, db) {
|
|
|
3569
3606
|
}
|
|
3570
3607
|
function getProjectByPath(path, db) {
|
|
3571
3608
|
const d = db || getDatabase();
|
|
3572
|
-
|
|
3573
|
-
|
|
3609
|
+
try {
|
|
3610
|
+
const machineId = getMachineId(d);
|
|
3611
|
+
const machineRow = d.query(`SELECT p.* FROM projects p
|
|
3612
|
+
JOIN project_machine_paths pmp ON pmp.project_id = p.id
|
|
3613
|
+
WHERE pmp.machine_id = ? AND pmp.path = ?`).get(machineId, path);
|
|
3614
|
+
if (machineRow)
|
|
3615
|
+
return machineRow;
|
|
3616
|
+
} catch {}
|
|
3617
|
+
return d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
3574
3618
|
}
|
|
3575
3619
|
function listProjects(db) {
|
|
3576
3620
|
const d = db || getDatabase();
|
|
@@ -3595,10 +3639,40 @@ function updateProject(id, input, db) {
|
|
|
3595
3639
|
sets.push("task_list_id = ?");
|
|
3596
3640
|
params.push(input.task_list_id);
|
|
3597
3641
|
}
|
|
3642
|
+
if (input.path !== undefined) {
|
|
3643
|
+
sets.push("path = ?");
|
|
3644
|
+
params.push(input.path);
|
|
3645
|
+
}
|
|
3598
3646
|
params.push(id);
|
|
3599
3647
|
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3600
3648
|
return getProject(id, d);
|
|
3601
3649
|
}
|
|
3650
|
+
function renameProject(id, input, db) {
|
|
3651
|
+
const d = db || getDatabase();
|
|
3652
|
+
const project = getProject(id, d);
|
|
3653
|
+
if (!project)
|
|
3654
|
+
throw new ProjectNotFoundError(id);
|
|
3655
|
+
let taskListsUpdated = 0;
|
|
3656
|
+
const ts = now();
|
|
3657
|
+
if (input.new_slug !== undefined) {
|
|
3658
|
+
const normalised = input.new_slug.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "");
|
|
3659
|
+
if (!normalised)
|
|
3660
|
+
throw new Error("Invalid slug \u2014 must be non-empty kebab-case");
|
|
3661
|
+
const conflict = d.query("SELECT id FROM projects WHERE task_list_id = ? AND id != ?").get(normalised, id);
|
|
3662
|
+
if (conflict)
|
|
3663
|
+
throw new Error(`Slug "${normalised}" is already used by another project`);
|
|
3664
|
+
const oldSlug = project.task_list_id;
|
|
3665
|
+
d.run("UPDATE projects SET task_list_id = ?, updated_at = ? WHERE id = ?", [normalised, ts, id]);
|
|
3666
|
+
if (oldSlug) {
|
|
3667
|
+
const result = d.run("UPDATE task_lists SET slug = ?, name = COALESCE(?, name), updated_at = ? WHERE project_id = ? AND slug = ?", [normalised, input.name ?? null, ts, id, oldSlug]);
|
|
3668
|
+
taskListsUpdated = result.changes;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
if (input.name !== undefined) {
|
|
3672
|
+
d.run("UPDATE projects SET name = ?, updated_at = ? WHERE id = ?", [input.name, ts, id]);
|
|
3673
|
+
}
|
|
3674
|
+
return { project: getProject(id, d), task_lists_updated: taskListsUpdated };
|
|
3675
|
+
}
|
|
3602
3676
|
function deleteProject(id, db) {
|
|
3603
3677
|
const d = db || getDatabase();
|
|
3604
3678
|
const result = d.run("DELETE FROM projects WHERE id = ?", [id]);
|
|
@@ -3665,13 +3739,53 @@ function ensureProject(name, path, db) {
|
|
|
3665
3739
|
d.run("UPDATE projects SET task_prefix = ?, updated_at = ? WHERE id = ?", [prefix, now(), existing.id]);
|
|
3666
3740
|
return getProject(existing.id, d);
|
|
3667
3741
|
}
|
|
3742
|
+
setMachineLocalPath(existing.id, path, d);
|
|
3668
3743
|
return existing;
|
|
3669
3744
|
}
|
|
3670
|
-
|
|
3745
|
+
const project = createProject({ name, path }, d);
|
|
3746
|
+
setMachineLocalPath(project.id, path, d);
|
|
3747
|
+
return project;
|
|
3748
|
+
}
|
|
3749
|
+
function setMachineLocalPath(projectId, path, db) {
|
|
3750
|
+
const d = db || getDatabase();
|
|
3751
|
+
const machineId = getMachineId(d);
|
|
3752
|
+
const ts = now();
|
|
3753
|
+
const existing = d.query("SELECT * FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, machineId);
|
|
3754
|
+
if (existing) {
|
|
3755
|
+
if (existing.path !== path) {
|
|
3756
|
+
d.run("UPDATE project_machine_paths SET path = ?, updated_at = ? WHERE id = ?", [path, ts, existing.id]);
|
|
3757
|
+
}
|
|
3758
|
+
return d.query("SELECT * FROM project_machine_paths WHERE id = ?").get(existing.id);
|
|
3759
|
+
}
|
|
3760
|
+
const id = uuid();
|
|
3761
|
+
d.run("INSERT INTO project_machine_paths (id, project_id, machine_id, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", [id, projectId, machineId, path, ts, ts]);
|
|
3762
|
+
return d.query("SELECT * FROM project_machine_paths WHERE id = ?").get(id);
|
|
3763
|
+
}
|
|
3764
|
+
function getMachineLocalPath(projectId, db) {
|
|
3765
|
+
const d = db || getDatabase();
|
|
3766
|
+
try {
|
|
3767
|
+
const machineId = getMachineId(d);
|
|
3768
|
+
const row = d.query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(projectId, machineId);
|
|
3769
|
+
if (row)
|
|
3770
|
+
return row.path;
|
|
3771
|
+
} catch {}
|
|
3772
|
+
const project = getProject(projectId, d);
|
|
3773
|
+
return project?.path ?? null;
|
|
3774
|
+
}
|
|
3775
|
+
function listMachineLocalPaths(projectId, db) {
|
|
3776
|
+
const d = db || getDatabase();
|
|
3777
|
+
return d.query("SELECT * FROM project_machine_paths WHERE project_id = ? ORDER BY machine_id").all(projectId);
|
|
3778
|
+
}
|
|
3779
|
+
function removeMachineLocalPath(projectId, machineId, db) {
|
|
3780
|
+
const d = db || getDatabase();
|
|
3781
|
+
const mid = machineId ?? getMachineId(d);
|
|
3782
|
+
const result = d.run("DELETE FROM project_machine_paths WHERE project_id = ? AND machine_id = ?", [projectId, mid]);
|
|
3783
|
+
return result.changes > 0;
|
|
3671
3784
|
}
|
|
3672
3785
|
var init_projects = __esm(() => {
|
|
3673
3786
|
init_types();
|
|
3674
3787
|
init_database();
|
|
3788
|
+
init_machines();
|
|
3675
3789
|
});
|
|
3676
3790
|
|
|
3677
3791
|
// src/lib/sync-utils.ts
|
|
@@ -4658,6 +4772,7 @@ var exports_tasks = {};
|
|
|
4658
4772
|
__export(exports_tasks, {
|
|
4659
4773
|
updateTask: () => updateTask,
|
|
4660
4774
|
unlockTask: () => unlockTask,
|
|
4775
|
+
unarchiveTask: () => unarchiveTask,
|
|
4661
4776
|
stealTask: () => stealTask,
|
|
4662
4777
|
startTask: () => startTask,
|
|
4663
4778
|
setTaskStatus: () => setTaskStatus,
|
|
@@ -4692,6 +4807,7 @@ __export(exports_tasks, {
|
|
|
4692
4807
|
claimNextTask: () => claimNextTask,
|
|
4693
4808
|
bulkUpdateTasks: () => bulkUpdateTasks,
|
|
4694
4809
|
bulkCreateTasks: () => bulkCreateTasks,
|
|
4810
|
+
archiveTasks: () => archiveTasks,
|
|
4695
4811
|
addDependency: () => addDependency
|
|
4696
4812
|
});
|
|
4697
4813
|
function rowToTask(row) {
|
|
@@ -4884,6 +5000,9 @@ function listTasks(filter = {}, db) {
|
|
|
4884
5000
|
params.push(decoded.p, decoded.p, decoded.c, decoded.p, decoded.c, decoded.i);
|
|
4885
5001
|
} catch {}
|
|
4886
5002
|
}
|
|
5003
|
+
if (!filter.include_archived) {
|
|
5004
|
+
conditions.push("archived_at IS NULL");
|
|
5005
|
+
}
|
|
4887
5006
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
4888
5007
|
let limitClause = "";
|
|
4889
5008
|
if (filter.limit) {
|
|
@@ -5797,6 +5916,35 @@ function bulkUpdateTasks(taskIds, updates, db) {
|
|
|
5797
5916
|
tx();
|
|
5798
5917
|
return { updated, failed };
|
|
5799
5918
|
}
|
|
5919
|
+
function archiveTasks(options, db) {
|
|
5920
|
+
const d = db || getDatabase();
|
|
5921
|
+
const conditions = ["archived_at IS NULL"];
|
|
5922
|
+
const params = [];
|
|
5923
|
+
const statuses = options.status ?? ["completed", "failed", "cancelled"];
|
|
5924
|
+
conditions.push(`status IN (${statuses.map(() => "?").join(",")})`);
|
|
5925
|
+
params.push(...statuses);
|
|
5926
|
+
if (options.project_id) {
|
|
5927
|
+
conditions.push("project_id = ?");
|
|
5928
|
+
params.push(options.project_id);
|
|
5929
|
+
}
|
|
5930
|
+
if (options.task_list_id) {
|
|
5931
|
+
conditions.push("task_list_id = ?");
|
|
5932
|
+
params.push(options.task_list_id);
|
|
5933
|
+
}
|
|
5934
|
+
if (options.older_than_days !== undefined) {
|
|
5935
|
+
const cutoff = new Date(Date.now() - options.older_than_days * 86400000).toISOString();
|
|
5936
|
+
conditions.push("updated_at < ?");
|
|
5937
|
+
params.push(cutoff);
|
|
5938
|
+
}
|
|
5939
|
+
const ts = now();
|
|
5940
|
+
const result = d.run(`UPDATE tasks SET archived_at = ? WHERE ${conditions.join(" AND ")}`, [ts, ...params]);
|
|
5941
|
+
return { archived: result.changes };
|
|
5942
|
+
}
|
|
5943
|
+
function unarchiveTask(id, db) {
|
|
5944
|
+
const d = db || getDatabase();
|
|
5945
|
+
d.run("UPDATE tasks SET archived_at = NULL WHERE id = ?", [id]);
|
|
5946
|
+
return getTask(id, d);
|
|
5947
|
+
}
|
|
5800
5948
|
function getOverdueTasks(projectId, db) {
|
|
5801
5949
|
const d = db || getDatabase();
|
|
5802
5950
|
const nowStr = new Date().toISOString();
|
|
@@ -6331,21 +6479,25 @@ function escapeFtsQuery(q) {
|
|
|
6331
6479
|
return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
|
|
6332
6480
|
}
|
|
6333
6481
|
function searchTasks(options, projectId, taskListId, db) {
|
|
6334
|
-
const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
|
|
6482
|
+
const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
|
|
6335
6483
|
const d = db || getDatabase();
|
|
6336
6484
|
clearExpiredLocks(d);
|
|
6337
6485
|
const params = [];
|
|
6338
6486
|
let sql;
|
|
6339
|
-
|
|
6340
|
-
|
|
6487
|
+
const raw = opts.query?.trim() ?? "";
|
|
6488
|
+
const q = raw === "*" ? "" : raw;
|
|
6489
|
+
if (hasFts(d) && q) {
|
|
6490
|
+
const ftsQuery = escapeFtsQuery(q);
|
|
6341
6491
|
sql = `SELECT t.* FROM tasks t
|
|
6342
6492
|
INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
|
|
6343
6493
|
WHERE tasks_fts MATCH ?`;
|
|
6344
6494
|
params.push(ftsQuery);
|
|
6345
|
-
} else {
|
|
6346
|
-
const pattern = `%${
|
|
6495
|
+
} else if (q) {
|
|
6496
|
+
const pattern = `%${q}%`;
|
|
6347
6497
|
sql = `SELECT * FROM tasks t WHERE (t.title LIKE ? OR t.description LIKE ? OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?))`;
|
|
6348
6498
|
params.push(pattern, pattern, pattern);
|
|
6499
|
+
} else {
|
|
6500
|
+
sql = `SELECT * FROM tasks t WHERE 1=1`;
|
|
6349
6501
|
}
|
|
6350
6502
|
if (opts.project_id) {
|
|
6351
6503
|
sql += " AND t.project_id = ?";
|
|
@@ -6399,7 +6551,7 @@ function searchTasks(options, projectId, taskListId, db) {
|
|
|
6399
6551
|
} else if (opts.is_blocked === false) {
|
|
6400
6552
|
sql += " AND t.id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
|
|
6401
6553
|
}
|
|
6402
|
-
if (hasFts(d) &&
|
|
6554
|
+
if (hasFts(d) && q) {
|
|
6403
6555
|
sql += ` ORDER BY bm25(tasks_fts),
|
|
6404
6556
|
CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
|
|
6405
6557
|
t.created_at DESC`;
|
|
@@ -23771,12 +23923,17 @@ ${text}` }] };
|
|
|
23771
23923
|
if (!agent) {
|
|
23772
23924
|
return { content: [{ type: "text", text: `Agent not found: ${id || name}` }], isError: true };
|
|
23773
23925
|
}
|
|
23926
|
+
const oldName = agent.name;
|
|
23774
23927
|
const updated = updateAgent(agent.id, { name: new_name });
|
|
23928
|
+
const db = getDatabase();
|
|
23929
|
+
const tasksResult = db.run("UPDATE tasks SET assigned_to = ? WHERE assigned_to = ?", [new_name, oldName]);
|
|
23930
|
+
const taskNote = tasksResult.changes > 0 ? `
|
|
23931
|
+
Updated assigned_to on ${tasksResult.changes} task(s).` : "";
|
|
23775
23932
|
return {
|
|
23776
23933
|
content: [{
|
|
23777
23934
|
type: "text",
|
|
23778
|
-
text: `Agent renamed: ${
|
|
23779
|
-
ID: ${updated.id}`
|
|
23935
|
+
text: `Agent renamed: ${oldName} -> ${updated.name}
|
|
23936
|
+
ID: ${updated.id}${taskNote}`
|
|
23780
23937
|
}]
|
|
23781
23938
|
};
|
|
23782
23939
|
} catch (e) {
|
|
@@ -25996,6 +26153,8 @@ function formatTaskDetail(task, maxDescriptionChars) {
|
|
|
25996
26153
|
parts.push(`Project: ${task.project_id}`);
|
|
25997
26154
|
if (task.plan_id)
|
|
25998
26155
|
parts.push(`Plan: ${task.plan_id}`);
|
|
26156
|
+
if (task.due_at)
|
|
26157
|
+
parts.push(`Due: ${task.due_at.slice(0, 10)}`);
|
|
25999
26158
|
if (task.tags.length > 0)
|
|
26000
26159
|
parts.push(`Tags: ${task.tags.join(", ")}`);
|
|
26001
26160
|
if (task.recurrence_rule)
|
|
@@ -26097,7 +26256,8 @@ var init_mcp = __esm(() => {
|
|
|
26097
26256
|
reason: exports_external.string().optional().describe("Why this task exists \u2014 context for agents picking it up"),
|
|
26098
26257
|
spawned_from_session: exports_external.string().optional().describe("Session ID that created this task (for tracing task lineage)"),
|
|
26099
26258
|
assigned_from_project: exports_external.string().optional().describe("Override: project ID the assigning agent is working from. Auto-detected from agent focus if omitted."),
|
|
26100
|
-
task_type: exports_external.string().optional().describe("Task type: bug, feature, chore, improvement, docs, test, security, or any custom string")
|
|
26259
|
+
task_type: exports_external.string().optional().describe("Task type: bug, feature, chore, improvement, docs, test, security, or any custom string"),
|
|
26260
|
+
due_date: exports_external.string().optional().describe("Due date as YYYY-MM-DD (e.g. '2026-04-15'). Stored as end-of-day ISO timestamp.")
|
|
26101
26261
|
}, async (params) => {
|
|
26102
26262
|
try {
|
|
26103
26263
|
if (!params.agent_id) {
|
|
@@ -26112,6 +26272,10 @@ var init_mcp = __esm(() => {
|
|
|
26112
26272
|
resolved["plan_id"] = resolveId(resolved["plan_id"], "plans");
|
|
26113
26273
|
if (resolved["task_list_id"])
|
|
26114
26274
|
resolved["task_list_id"] = resolveId(resolved["task_list_id"], "task_lists");
|
|
26275
|
+
if (resolved["due_date"]) {
|
|
26276
|
+
resolved["due_at"] = `${resolved["due_date"]}T23:59:59.000Z`;
|
|
26277
|
+
delete resolved["due_date"];
|
|
26278
|
+
}
|
|
26115
26279
|
if (!resolved["assigned_from_project"]) {
|
|
26116
26280
|
const focus = getAgentFocus(params.agent_id);
|
|
26117
26281
|
if (focus?.project_id) {
|
|
@@ -26147,7 +26311,8 @@ var init_mcp = __esm(() => {
|
|
|
26147
26311
|
limit: exports_external.number().optional(),
|
|
26148
26312
|
offset: exports_external.number().optional(),
|
|
26149
26313
|
summary_only: exports_external.boolean().optional().describe("When true, return only id, short_id, title, status, priority \u2014 minimal tokens for navigation"),
|
|
26150
|
-
cursor: exports_external.string().optional().describe("Opaque cursor from a prior response for stable pagination. Use next_cursor from the previous page. Mutually exclusive with offset.")
|
|
26314
|
+
cursor: exports_external.string().optional().describe("Opaque cursor from a prior response for stable pagination. Use next_cursor from the previous page. Mutually exclusive with offset."),
|
|
26315
|
+
include_archived: exports_external.boolean().optional().describe("When true, include archived tasks. Default: false.")
|
|
26151
26316
|
}, async (params) => {
|
|
26152
26317
|
try {
|
|
26153
26318
|
const { due_today, overdue, summary_only, ...rest } = params;
|
|
@@ -26182,7 +26347,8 @@ var init_mcp = __esm(() => {
|
|
|
26182
26347
|
const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
|
|
26183
26348
|
const due = t.due_at ? ` due:${t.due_at.slice(0, 10)}` : "";
|
|
26184
26349
|
const recur = t.recurrence_rule ? " [\u21BB]" : "";
|
|
26185
|
-
|
|
26350
|
+
const list = t.task_list_id ? ` list:${t.task_list_id.slice(0, 8)}` : "";
|
|
26351
|
+
return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${list}${lock}${due}${recur}`;
|
|
26186
26352
|
}).join(`
|
|
26187
26353
|
`);
|
|
26188
26354
|
const currentOffset = resolved.offset || 0;
|
|
@@ -26269,7 +26435,7 @@ Checklist (${done}/${task.checklist.length}):`);
|
|
|
26269
26435
|
if (shouldRegisterTool("update_task")) {
|
|
26270
26436
|
server.tool("update_task", "Update task fields. Version required for optimistic locking.", {
|
|
26271
26437
|
id: exports_external.string(),
|
|
26272
|
-
version: exports_external.number(),
|
|
26438
|
+
version: exports_external.union([exports_external.number(), exports_external.string()]).transform((v) => Number(v)),
|
|
26273
26439
|
title: exports_external.string().optional(),
|
|
26274
26440
|
description: exports_external.string().optional(),
|
|
26275
26441
|
status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
|
|
@@ -26279,7 +26445,8 @@ Checklist (${done}/${task.checklist.length}):`);
|
|
|
26279
26445
|
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
26280
26446
|
plan_id: exports_external.string().optional(),
|
|
26281
26447
|
task_list_id: exports_external.string().optional(),
|
|
26282
|
-
task_type: exports_external.string().nullable().optional().describe("Task type: bug, feature, chore, improvement, docs, test, security, or custom. null to clear.")
|
|
26448
|
+
task_type: exports_external.string().nullable().optional().describe("Task type: bug, feature, chore, improvement, docs, test, security, or custom. null to clear."),
|
|
26449
|
+
due_date: exports_external.string().nullable().optional().describe("Due date as YYYY-MM-DD. null to clear. Stored as end-of-day ISO timestamp.")
|
|
26283
26450
|
}, async ({ id, ...rest }) => {
|
|
26284
26451
|
try {
|
|
26285
26452
|
const resolvedId = resolveId(id);
|
|
@@ -26288,6 +26455,10 @@ Checklist (${done}/${task.checklist.length}):`);
|
|
|
26288
26455
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
26289
26456
|
if (resolved.plan_id)
|
|
26290
26457
|
resolved.plan_id = resolveId(resolved.plan_id, "plans");
|
|
26458
|
+
if ("due_date" in resolved) {
|
|
26459
|
+
resolved["due_at"] = resolved["due_date"] ? `${resolved["due_date"]}T23:59:59.000Z` : null;
|
|
26460
|
+
delete resolved["due_date"];
|
|
26461
|
+
}
|
|
26291
26462
|
const task = updateTask(resolvedId, resolved);
|
|
26292
26463
|
return { content: [{ type: "text", text: `updated: ${formatTask(task)}` }] };
|
|
26293
26464
|
} catch (e) {
|
|
@@ -26523,6 +26694,29 @@ ${text}` }] };
|
|
|
26523
26694
|
}
|
|
26524
26695
|
});
|
|
26525
26696
|
}
|
|
26697
|
+
if (shouldRegisterTool("update_project")) {
|
|
26698
|
+
server.tool("update_project", "Update project fields: name, description, path, task_list_id. Use new_slug to rename the project slug (cascades to task_lists).", {
|
|
26699
|
+
id: exports_external.string().describe("Project ID"),
|
|
26700
|
+
name: exports_external.string().optional(),
|
|
26701
|
+
description: exports_external.string().optional(),
|
|
26702
|
+
path: exports_external.string().optional().describe("Update global path (use projects-path set for machine-local overrides)"),
|
|
26703
|
+
task_list_id: exports_external.string().optional(),
|
|
26704
|
+
new_slug: exports_external.string().optional().describe("Rename the project slug (kebab-case). Cascades to matching task_list slugs.")
|
|
26705
|
+
}, async ({ id, name, description, path, task_list_id, new_slug }) => {
|
|
26706
|
+
try {
|
|
26707
|
+
const resolvedId = resolveId(id, "projects");
|
|
26708
|
+
if (new_slug || name && !description && !path && !task_list_id) {
|
|
26709
|
+
const result = renameProject(resolvedId, { name, new_slug });
|
|
26710
|
+
const note = result.task_lists_updated > 0 ? ` (${result.task_lists_updated} task list(s) slug updated)` : "";
|
|
26711
|
+
return { content: [{ type: "text", text: `Project updated: ${result.project.id.slice(0, 8)} | ${result.project.name}${note}` }] };
|
|
26712
|
+
}
|
|
26713
|
+
const updated = updateProject(resolvedId, { name, description, path, task_list_id });
|
|
26714
|
+
return { content: [{ type: "text", text: `Project updated: ${updated.id.slice(0, 8)} | ${updated.name}${updated.path ? ` (${updated.path})` : ""}` }] };
|
|
26715
|
+
} catch (e) {
|
|
26716
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
26717
|
+
}
|
|
26718
|
+
});
|
|
26719
|
+
}
|
|
26526
26720
|
if (shouldRegisterTool("remove_project_source")) {
|
|
26527
26721
|
server.tool("remove_project_source", "Remove a data source from a project by source ID.", {
|
|
26528
26722
|
source_id: exports_external.string().describe("Source ID to remove")
|
|
@@ -26786,8 +26980,8 @@ ${text}` }] };
|
|
|
26786
26980
|
});
|
|
26787
26981
|
}
|
|
26788
26982
|
if (shouldRegisterTool("search_tasks")) {
|
|
26789
|
-
server.tool("search_tasks", "Full-text search across tasks with filters.", {
|
|
26790
|
-
query: exports_external.string(),
|
|
26983
|
+
server.tool("search_tasks", "Full-text search across tasks with filters. query is optional \u2014 if omitted, returns all tasks matching the given filters.", {
|
|
26984
|
+
query: exports_external.string().optional(),
|
|
26791
26985
|
project_id: exports_external.string().optional(),
|
|
26792
26986
|
task_list_id: exports_external.string().optional(),
|
|
26793
26987
|
status: exports_external.union([
|
|
@@ -26815,11 +27009,12 @@ ${text}` }] };
|
|
|
26815
27009
|
...filters
|
|
26816
27010
|
});
|
|
26817
27011
|
if (tasks.length === 0) {
|
|
26818
|
-
return { content: [{ type: "text", text: `No tasks matching "${query}".` }] };
|
|
27012
|
+
return { content: [{ type: "text", text: query ? `No tasks matching "${query}".` : "No tasks found." }] };
|
|
26819
27013
|
}
|
|
26820
27014
|
const text = tasks.map((t) => `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}`).join(`
|
|
26821
27015
|
`);
|
|
26822
|
-
|
|
27016
|
+
const label = query ? `${tasks.length} result(s) for "${query}"` : `${tasks.length} task(s)`;
|
|
27017
|
+
return { content: [{ type: "text", text: `${label}:
|
|
26823
27018
|
${text}` }] };
|
|
26824
27019
|
} catch (e) {
|
|
26825
27020
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -26915,9 +27110,15 @@ Description: ${list.description}` : ""}`
|
|
|
26915
27110
|
if (lists.length === 0) {
|
|
26916
27111
|
return { content: [{ type: "text", text: "No task lists found." }] };
|
|
26917
27112
|
}
|
|
27113
|
+
const db = getDatabase();
|
|
26918
27114
|
const text = lists.map((l) => {
|
|
26919
27115
|
const project = l.project_id ? ` (project: ${l.project_id.slice(0, 8)})` : "";
|
|
26920
|
-
|
|
27116
|
+
const counts = db.query(`SELECT COUNT(*) as total,
|
|
27117
|
+
SUM(CASE WHEN status IN ('pending','in_progress') THEN 1 ELSE 0 END) as active,
|
|
27118
|
+
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as done
|
|
27119
|
+
FROM tasks WHERE task_list_id = ? AND archived_at IS NULL`).get(l.id);
|
|
27120
|
+
const taskNote = ` [${counts.active} active, ${counts.done} done / ${counts.total} total]`;
|
|
27121
|
+
return `${l.id.slice(0, 8)} | ${l.name} [${l.slug}]${taskNote}${project}${l.description ? ` - ${l.description}` : ""}`;
|
|
26921
27122
|
}).join(`
|
|
26922
27123
|
`);
|
|
26923
27124
|
return { content: [{ type: "text", text: `${lists.length} task list(s):
|
|
@@ -27505,6 +27706,52 @@ In Progress:`);
|
|
|
27505
27706
|
}
|
|
27506
27707
|
});
|
|
27507
27708
|
}
|
|
27709
|
+
if (shouldRegisterTool("archive_completed")) {
|
|
27710
|
+
server.tool("archive_completed", "Archive completed/failed/cancelled tasks to reduce clutter. Archived tasks are hidden from list_tasks and search_tasks by default.", {
|
|
27711
|
+
project_id: exports_external.string().optional().describe("Scope to a project"),
|
|
27712
|
+
task_list_id: exports_external.string().optional().describe("Scope to a task list"),
|
|
27713
|
+
older_than_days: exports_external.number().optional().describe("Only archive tasks last updated more than N days ago. Default: all matching tasks."),
|
|
27714
|
+
status: exports_external.array(exports_external.enum(["completed", "failed", "cancelled"])).optional().describe("Statuses to archive. Default: completed, failed, cancelled."),
|
|
27715
|
+
dry_run: exports_external.boolean().optional().describe("Preview count without archiving. Default: false.")
|
|
27716
|
+
}, async ({ project_id, task_list_id, older_than_days, status, dry_run }) => {
|
|
27717
|
+
try {
|
|
27718
|
+
const filters = {};
|
|
27719
|
+
if (project_id)
|
|
27720
|
+
filters.project_id = resolveId(project_id, "projects");
|
|
27721
|
+
if (task_list_id)
|
|
27722
|
+
filters.task_list_id = resolveId(task_list_id, "task_lists");
|
|
27723
|
+
if (older_than_days !== undefined)
|
|
27724
|
+
filters.older_than_days = older_than_days;
|
|
27725
|
+
if (status)
|
|
27726
|
+
filters.status = status;
|
|
27727
|
+
if (dry_run) {
|
|
27728
|
+
const db = getDatabase();
|
|
27729
|
+
const statuses = status ?? ["completed", "failed", "cancelled"];
|
|
27730
|
+
const conditions = ["archived_at IS NULL", `status IN (${statuses.map(() => "?").join(",")})`];
|
|
27731
|
+
const params = [...statuses];
|
|
27732
|
+
if (filters.project_id) {
|
|
27733
|
+
conditions.push("project_id = ?");
|
|
27734
|
+
params.push(filters.project_id);
|
|
27735
|
+
}
|
|
27736
|
+
if (filters.task_list_id) {
|
|
27737
|
+
conditions.push("task_list_id = ?");
|
|
27738
|
+
params.push(filters.task_list_id);
|
|
27739
|
+
}
|
|
27740
|
+
if (older_than_days !== undefined) {
|
|
27741
|
+
const cutoff = new Date(Date.now() - older_than_days * 86400000).toISOString();
|
|
27742
|
+
conditions.push("updated_at < ?");
|
|
27743
|
+
params.push(cutoff);
|
|
27744
|
+
}
|
|
27745
|
+
const count = db.query(`SELECT COUNT(*) as c FROM tasks WHERE ${conditions.join(" AND ")}`).get(...params).c;
|
|
27746
|
+
return { content: [{ type: "text", text: `Dry run: would archive ${count} task(s).` }] };
|
|
27747
|
+
}
|
|
27748
|
+
const result = archiveTasks(filters);
|
|
27749
|
+
return { content: [{ type: "text", text: `Archived ${result.archived} task(s).` }] };
|
|
27750
|
+
} catch (e) {
|
|
27751
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
27752
|
+
}
|
|
27753
|
+
});
|
|
27754
|
+
}
|
|
27508
27755
|
if (shouldRegisterTool("bulk_update_tasks")) {
|
|
27509
27756
|
server.tool("bulk_update_tasks", "Update multiple tasks at once. Two modes: (1) task_ids + shared fields \u2014 apply the same changes to all; (2) updates array \u2014 per-task fields, each entry has id plus any fields to update.", {
|
|
27510
27757
|
task_ids: exports_external.array(exports_external.string()).optional().describe("Task IDs to update with the same fields (mode 1)"),
|
|
@@ -27617,6 +27864,26 @@ In Progress:`);
|
|
|
27617
27864
|
if (agent_id)
|
|
27618
27865
|
filters.agent_id = agent_id;
|
|
27619
27866
|
const stats = getTaskStats(Object.keys(filters).length > 0 ? filters : undefined);
|
|
27867
|
+
if (!task_list_id) {
|
|
27868
|
+
const db = getDatabase();
|
|
27869
|
+
const listRows = db.query(`SELECT tl.id, tl.name, tl.slug,
|
|
27870
|
+
COUNT(t.id) as total,
|
|
27871
|
+
SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END) as completed,
|
|
27872
|
+
SUM(CASE WHEN t.status IN ('pending','in_progress') THEN 1 ELSE 0 END) as active
|
|
27873
|
+
FROM task_lists tl
|
|
27874
|
+
LEFT JOIN tasks t ON t.task_list_id = tl.id ${filters.project_id ? "AND t.project_id = ?" : ""}
|
|
27875
|
+
${filters.project_id ? "WHERE tl.project_id = ?" : ""}
|
|
27876
|
+
GROUP BY tl.id ORDER BY tl.name`).all(...filters.project_id ? [filters.project_id, filters.project_id] : []);
|
|
27877
|
+
stats.by_task_list = listRows.map((r) => ({
|
|
27878
|
+
id: r.id.slice(0, 8),
|
|
27879
|
+
name: r.name,
|
|
27880
|
+
slug: r.slug,
|
|
27881
|
+
total: r.total,
|
|
27882
|
+
completed: r.completed,
|
|
27883
|
+
active: r.active,
|
|
27884
|
+
completion_rate: r.total > 0 ? Math.round(r.completed / r.total * 100) : 0
|
|
27885
|
+
}));
|
|
27886
|
+
}
|
|
27620
27887
|
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
27621
27888
|
} catch (e) {
|
|
27622
27889
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
@@ -33513,6 +33780,10 @@ program2.command("projects").description("List and manage projects").option("--a
|
|
|
33513
33780
|
} else {
|
|
33514
33781
|
project = createProject({ name, path: projectPath, task_list_id: opts.taskListId });
|
|
33515
33782
|
}
|
|
33783
|
+
try {
|
|
33784
|
+
const { setMachineLocalPath: setMachineLocalPath2 } = (init_projects(), __toCommonJS(exports_projects));
|
|
33785
|
+
setMachineLocalPath2(project.id, projectPath);
|
|
33786
|
+
} catch {}
|
|
33516
33787
|
if (globalOpts.json) {
|
|
33517
33788
|
output(project, true);
|
|
33518
33789
|
} else {
|
|
@@ -33538,6 +33809,106 @@ program2.command("projects").description("List and manage projects").option("--a
|
|
|
33538
33809
|
console.log(`${chalk3.dim(p.id.slice(0, 8))} ${chalk3.bold(p.name)} ${chalk3.dim(p.path)}${taskList}${p.description ? ` - ${p.description}` : ""}`);
|
|
33539
33810
|
}
|
|
33540
33811
|
});
|
|
33812
|
+
program2.command("project-rename <id-or-slug> <new-slug>").description("Rename a project slug. Cascades to matching task lists. Task prefixes (e.g. APP-00001) are unchanged.").option("--name <name>", "Also update the project display name").option("--json", "Output as JSON").action((idOrSlug, newSlug, opts) => {
|
|
33813
|
+
const globalOpts = program2.opts();
|
|
33814
|
+
const useJson = opts.json || globalOpts.json;
|
|
33815
|
+
try {
|
|
33816
|
+
const { renameProject: renameProject2 } = (init_projects(), __toCommonJS(exports_projects));
|
|
33817
|
+
const db = getDatabase();
|
|
33818
|
+
let resolvedId = resolvePartialId(db, "projects", idOrSlug);
|
|
33819
|
+
if (!resolvedId) {
|
|
33820
|
+
const bySlug = db.query("SELECT id FROM projects WHERE task_list_id = ?").get(idOrSlug);
|
|
33821
|
+
resolvedId = bySlug?.id ?? null;
|
|
33822
|
+
}
|
|
33823
|
+
if (!resolvedId) {
|
|
33824
|
+
console.error(chalk3.red(`Project not found: ${idOrSlug}`));
|
|
33825
|
+
process.exit(1);
|
|
33826
|
+
}
|
|
33827
|
+
const result = renameProject2(resolvedId, { name: opts.name, new_slug: newSlug });
|
|
33828
|
+
if (useJson) {
|
|
33829
|
+
output({ project: result.project, task_lists_updated: result.task_lists_updated }, true);
|
|
33830
|
+
} else {
|
|
33831
|
+
console.log(chalk3.green(`Project renamed: ${result.project.name} (slug: ${result.project.task_list_id})`));
|
|
33832
|
+
if (result.task_lists_updated > 0) {
|
|
33833
|
+
console.log(chalk3.dim(` Updated ${result.task_lists_updated} task list slug(s).`));
|
|
33834
|
+
}
|
|
33835
|
+
}
|
|
33836
|
+
} catch (e) {
|
|
33837
|
+
console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
|
|
33838
|
+
process.exit(1);
|
|
33839
|
+
}
|
|
33840
|
+
});
|
|
33841
|
+
var projectsPathCmd = program2.command("projects-path").description("Manage machine-local path overrides for projects");
|
|
33842
|
+
projectsPathCmd.command("set <project-id> <path>").description("Set the local path for a project on this machine").option("--json", "Output as JSON").action((projectId, projectPath, opts) => {
|
|
33843
|
+
const globalOpts = program2.opts();
|
|
33844
|
+
const useJson = opts.json || globalOpts.json;
|
|
33845
|
+
try {
|
|
33846
|
+
const { setMachineLocalPath: setMachineLocalPath2 } = (init_projects(), __toCommonJS(exports_projects));
|
|
33847
|
+
const db = getDatabase();
|
|
33848
|
+
const resolved = resolvePartialId(db, "projects", projectId);
|
|
33849
|
+
if (!resolved) {
|
|
33850
|
+
console.error(chalk3.red(`Project not found: ${projectId}`));
|
|
33851
|
+
process.exit(1);
|
|
33852
|
+
}
|
|
33853
|
+
const entry = setMachineLocalPath2(resolved, resolve4(projectPath));
|
|
33854
|
+
if (useJson) {
|
|
33855
|
+
output(entry, true);
|
|
33856
|
+
} else {
|
|
33857
|
+
console.log(chalk3.green(`Local path set: ${entry.path} (machine: ${entry.machine_id.slice(0, 8)})`));
|
|
33858
|
+
}
|
|
33859
|
+
} catch (e) {
|
|
33860
|
+
console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
|
|
33861
|
+
process.exit(1);
|
|
33862
|
+
}
|
|
33863
|
+
});
|
|
33864
|
+
projectsPathCmd.command("list <project-id>").description("List all machine path overrides for a project").option("--json", "Output as JSON").action((projectId, opts) => {
|
|
33865
|
+
const globalOpts = program2.opts();
|
|
33866
|
+
const useJson = opts.json || globalOpts.json;
|
|
33867
|
+
try {
|
|
33868
|
+
const { listMachineLocalPaths: listMachineLocalPaths2 } = (init_projects(), __toCommonJS(exports_projects));
|
|
33869
|
+
const db = getDatabase();
|
|
33870
|
+
const resolved = resolvePartialId(db, "projects", projectId);
|
|
33871
|
+
if (!resolved) {
|
|
33872
|
+
console.error(chalk3.red(`Project not found: ${projectId}`));
|
|
33873
|
+
process.exit(1);
|
|
33874
|
+
}
|
|
33875
|
+
const paths = listMachineLocalPaths2(resolved);
|
|
33876
|
+
if (useJson) {
|
|
33877
|
+
output(paths, true);
|
|
33878
|
+
return;
|
|
33879
|
+
}
|
|
33880
|
+
if (paths.length === 0) {
|
|
33881
|
+
console.log(chalk3.dim("No machine path overrides."));
|
|
33882
|
+
return;
|
|
33883
|
+
}
|
|
33884
|
+
for (const p of paths) {
|
|
33885
|
+
console.log(`${chalk3.dim(p.machine_id.slice(0, 8))} ${p.path} ${chalk3.dim(p.updated_at)}`);
|
|
33886
|
+
}
|
|
33887
|
+
} catch (e) {
|
|
33888
|
+
console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
|
|
33889
|
+
process.exit(1);
|
|
33890
|
+
}
|
|
33891
|
+
});
|
|
33892
|
+
projectsPathCmd.command("remove <project-id>").description("Remove the local path override for a project on this machine").option("--machine <id>", "Machine ID to remove override for (default: this machine)").action((projectId, opts) => {
|
|
33893
|
+
try {
|
|
33894
|
+
const { removeMachineLocalPath: removeMachineLocalPath2 } = (init_projects(), __toCommonJS(exports_projects));
|
|
33895
|
+
const db = getDatabase();
|
|
33896
|
+
const resolved = resolvePartialId(db, "projects", projectId);
|
|
33897
|
+
if (!resolved) {
|
|
33898
|
+
console.error(chalk3.red(`Project not found: ${projectId}`));
|
|
33899
|
+
process.exit(1);
|
|
33900
|
+
}
|
|
33901
|
+
const removed = removeMachineLocalPath2(resolved, opts.machine);
|
|
33902
|
+
if (removed) {
|
|
33903
|
+
console.log(chalk3.green("Machine path override removed."));
|
|
33904
|
+
} else {
|
|
33905
|
+
console.log(chalk3.dim("No override found to remove."));
|
|
33906
|
+
}
|
|
33907
|
+
} catch (e) {
|
|
33908
|
+
console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
|
|
33909
|
+
process.exit(1);
|
|
33910
|
+
}
|
|
33911
|
+
});
|
|
33541
33912
|
program2.command("extract <path>").description("Extract TODO/FIXME/HACK/BUG/XXX/NOTE comments from source files and create tasks").option("--dry-run", "Show extracted comments without creating tasks").option("--pattern <tags>", "Comma-separated tags to look for (default: TODO,FIXME,HACK,XXX,BUG,NOTE)").option("-t, --tags <tags>", "Extra comma-separated tags to add to created tasks").option("--assign <agent>", "Assign extracted tasks to an agent").option("--list <id>", "Task list ID").option("--ext <extensions>", "Comma-separated file extensions to scan (e.g. ts,py,go)").action((scanPath, opts) => {
|
|
33542
33913
|
try {
|
|
33543
33914
|
const globalOpts = program2.opts();
|