@hasna/todos 0.9.18 → 0.9.19

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 CHANGED
@@ -2138,29 +2138,20 @@ function runMigrations(db) {
2138
2138
  const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
2139
2139
  const currentLevel = result?.max_id ?? 0;
2140
2140
  for (let i = currentLevel;i < MIGRATIONS.length; i++) {
2141
- db.exec(MIGRATIONS[i]);
2141
+ try {
2142
+ db.exec(MIGRATIONS[i]);
2143
+ } catch {}
2142
2144
  }
2143
2145
  } catch {
2144
2146
  for (const migration of MIGRATIONS) {
2145
- db.exec(migration);
2147
+ try {
2148
+ db.exec(migration);
2149
+ } catch {}
2146
2150
  }
2147
2151
  }
2148
- ensureTableMigrations(db);
2152
+ ensureSchema(db);
2149
2153
  }
2150
- function ensureTableMigrations(db) {
2151
- try {
2152
- const hasAgents = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='agents'").get();
2153
- if (!hasAgents) {
2154
- db.exec(MIGRATIONS[4]);
2155
- }
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
- }
2154
+ function ensureSchema(db) {
2164
2155
  const ensureColumn = (table, column, type) => {
2165
2156
  try {
2166
2157
  db.query(`SELECT ${column} FROM ${table} LIMIT 0`).get();
@@ -2170,6 +2161,76 @@ function ensureTableMigrations(db) {
2170
2161
  } catch {}
2171
2162
  }
2172
2163
  };
2164
+ const ensureTable = (name, sql) => {
2165
+ try {
2166
+ const exists = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
2167
+ if (!exists)
2168
+ db.exec(sql);
2169
+ } catch {}
2170
+ };
2171
+ const ensureIndex = (sql) => {
2172
+ try {
2173
+ db.exec(sql);
2174
+ } catch {}
2175
+ };
2176
+ ensureTable("agents", `
2177
+ CREATE TABLE agents (
2178
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT,
2179
+ role TEXT DEFAULT 'agent', permissions TEXT DEFAULT '["*"]',
2180
+ metadata TEXT DEFAULT '{}',
2181
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2182
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
2183
+ )`);
2184
+ ensureTable("task_lists", `
2185
+ CREATE TABLE task_lists (
2186
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2187
+ slug TEXT NOT NULL, name TEXT NOT NULL, description TEXT,
2188
+ metadata TEXT DEFAULT '{}',
2189
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2190
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
2191
+ UNIQUE(project_id, slug)
2192
+ )`);
2193
+ ensureTable("plans", `
2194
+ CREATE TABLE plans (
2195
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
2196
+ task_list_id TEXT, agent_id TEXT,
2197
+ name TEXT NOT NULL, description TEXT,
2198
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
2199
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
2200
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
2201
+ )`);
2202
+ ensureTable("task_tags", `
2203
+ CREATE TABLE task_tags (
2204
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2205
+ tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
2206
+ )`);
2207
+ ensureTable("task_history", `
2208
+ CREATE TABLE task_history (
2209
+ id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
2210
+ action TEXT NOT NULL, field TEXT, old_value TEXT, new_value TEXT, agent_id TEXT,
2211
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2212
+ )`);
2213
+ ensureTable("webhooks", `
2214
+ CREATE TABLE webhooks (
2215
+ id TEXT PRIMARY KEY, url TEXT NOT NULL, events TEXT NOT NULL DEFAULT '[]',
2216
+ secret TEXT, active INTEGER NOT NULL DEFAULT 1,
2217
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2218
+ )`);
2219
+ ensureTable("task_templates", `
2220
+ CREATE TABLE task_templates (
2221
+ id TEXT PRIMARY KEY, name TEXT NOT NULL, title_pattern TEXT NOT NULL,
2222
+ description TEXT, priority TEXT DEFAULT 'medium', tags TEXT DEFAULT '[]',
2223
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
2224
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
2225
+ metadata TEXT DEFAULT '{}',
2226
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
2227
+ )`);
2228
+ ensureColumn("projects", "task_list_id", "TEXT");
2229
+ ensureColumn("projects", "task_prefix", "TEXT");
2230
+ ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
2231
+ ensureColumn("tasks", "plan_id", "TEXT REFERENCES plans(id) ON DELETE SET NULL");
2232
+ ensureColumn("tasks", "task_list_id", "TEXT REFERENCES task_lists(id) ON DELETE SET NULL");
2233
+ ensureColumn("tasks", "short_id", "TEXT");
2173
2234
  ensureColumn("tasks", "due_at", "TEXT");
2174
2235
  ensureColumn("tasks", "estimated_minutes", "INTEGER");
2175
2236
  ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
@@ -2179,6 +2240,21 @@ function ensureTableMigrations(db) {
2179
2240
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
2180
2241
  ensureColumn("plans", "task_list_id", "TEXT");
2181
2242
  ensureColumn("plans", "agent_id", "TEXT");
2243
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
2244
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id)");
2245
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_due_at ON tasks(due_at)");
2246
+ ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL");
2247
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name)");
2248
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_project ON task_lists(project_id)");
2249
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug)");
2250
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag)");
2251
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_task ON task_tags(task_id)");
2252
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
2253
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
2254
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
2255
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
2256
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
2257
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
2182
2258
  }
2183
2259
  function backfillTaskTags(db) {
2184
2260
  try {
package/dist/index.js CHANGED
@@ -285,29 +285,20 @@ function runMigrations(db) {
285
285
  const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
286
286
  const currentLevel = result?.max_id ?? 0;
287
287
  for (let i = currentLevel;i < MIGRATIONS.length; i++) {
288
- db.exec(MIGRATIONS[i]);
288
+ try {
289
+ db.exec(MIGRATIONS[i]);
290
+ } catch {}
289
291
  }
290
292
  } catch {
291
293
  for (const migration of MIGRATIONS) {
292
- db.exec(migration);
294
+ try {
295
+ db.exec(migration);
296
+ } catch {}
293
297
  }
294
298
  }
295
- ensureTableMigrations(db);
299
+ ensureSchema(db);
296
300
  }
297
- function ensureTableMigrations(db) {
298
- try {
299
- const hasAgents = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='agents'").get();
300
- if (!hasAgents) {
301
- db.exec(MIGRATIONS[4]);
302
- }
303
- } catch {}
304
- try {
305
- db.query("SELECT task_prefix FROM projects LIMIT 0").get();
306
- } catch {
307
- try {
308
- db.exec(MIGRATIONS[5]);
309
- } catch {}
310
- }
301
+ function ensureSchema(db) {
311
302
  const ensureColumn = (table, column, type) => {
312
303
  try {
313
304
  db.query(`SELECT ${column} FROM ${table} LIMIT 0`).get();
@@ -317,6 +308,76 @@ function ensureTableMigrations(db) {
317
308
  } catch {}
318
309
  }
319
310
  };
311
+ const ensureTable = (name, sql) => {
312
+ try {
313
+ const exists = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
314
+ if (!exists)
315
+ db.exec(sql);
316
+ } catch {}
317
+ };
318
+ const ensureIndex = (sql) => {
319
+ try {
320
+ db.exec(sql);
321
+ } catch {}
322
+ };
323
+ ensureTable("agents", `
324
+ CREATE TABLE agents (
325
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT,
326
+ role TEXT DEFAULT 'agent', permissions TEXT DEFAULT '["*"]',
327
+ metadata TEXT DEFAULT '{}',
328
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
329
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
330
+ )`);
331
+ ensureTable("task_lists", `
332
+ CREATE TABLE task_lists (
333
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
334
+ slug TEXT NOT NULL, name TEXT NOT NULL, description TEXT,
335
+ metadata TEXT DEFAULT '{}',
336
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
337
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
338
+ UNIQUE(project_id, slug)
339
+ )`);
340
+ ensureTable("plans", `
341
+ CREATE TABLE plans (
342
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
343
+ task_list_id TEXT, agent_id TEXT,
344
+ name TEXT NOT NULL, description TEXT,
345
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
346
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
347
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
348
+ )`);
349
+ ensureTable("task_tags", `
350
+ CREATE TABLE task_tags (
351
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
352
+ tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
353
+ )`);
354
+ ensureTable("task_history", `
355
+ CREATE TABLE task_history (
356
+ id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
357
+ action TEXT NOT NULL, field TEXT, old_value TEXT, new_value TEXT, agent_id TEXT,
358
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
359
+ )`);
360
+ ensureTable("webhooks", `
361
+ CREATE TABLE webhooks (
362
+ id TEXT PRIMARY KEY, url TEXT NOT NULL, events TEXT NOT NULL DEFAULT '[]',
363
+ secret TEXT, active INTEGER NOT NULL DEFAULT 1,
364
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
365
+ )`);
366
+ ensureTable("task_templates", `
367
+ CREATE TABLE task_templates (
368
+ id TEXT PRIMARY KEY, name TEXT NOT NULL, title_pattern TEXT NOT NULL,
369
+ description TEXT, priority TEXT DEFAULT 'medium', tags TEXT DEFAULT '[]',
370
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
371
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
372
+ metadata TEXT DEFAULT '{}',
373
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
374
+ )`);
375
+ ensureColumn("projects", "task_list_id", "TEXT");
376
+ ensureColumn("projects", "task_prefix", "TEXT");
377
+ ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
378
+ ensureColumn("tasks", "plan_id", "TEXT REFERENCES plans(id) ON DELETE SET NULL");
379
+ ensureColumn("tasks", "task_list_id", "TEXT REFERENCES task_lists(id) ON DELETE SET NULL");
380
+ ensureColumn("tasks", "short_id", "TEXT");
320
381
  ensureColumn("tasks", "due_at", "TEXT");
321
382
  ensureColumn("tasks", "estimated_minutes", "INTEGER");
322
383
  ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
@@ -326,6 +387,21 @@ function ensureTableMigrations(db) {
326
387
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
327
388
  ensureColumn("plans", "task_list_id", "TEXT");
328
389
  ensureColumn("plans", "agent_id", "TEXT");
390
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
391
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id)");
392
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_due_at ON tasks(due_at)");
393
+ ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL");
394
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name)");
395
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_project ON task_lists(project_id)");
396
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug)");
397
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag)");
398
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_task ON task_tags(task_id)");
399
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
400
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
401
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
402
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
403
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
404
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
329
405
  }
330
406
  function backfillTaskTags(db) {
331
407
  try {
package/dist/mcp/index.js CHANGED
@@ -4346,29 +4346,20 @@ function runMigrations(db) {
4346
4346
  const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
4347
4347
  const currentLevel = result?.max_id ?? 0;
4348
4348
  for (let i = currentLevel;i < MIGRATIONS.length; i++) {
4349
- db.exec(MIGRATIONS[i]);
4349
+ try {
4350
+ db.exec(MIGRATIONS[i]);
4351
+ } catch {}
4350
4352
  }
4351
4353
  } catch {
4352
4354
  for (const migration of MIGRATIONS) {
4353
- db.exec(migration);
4355
+ try {
4356
+ db.exec(migration);
4357
+ } catch {}
4354
4358
  }
4355
4359
  }
4356
- ensureTableMigrations(db);
4360
+ ensureSchema(db);
4357
4361
  }
4358
- function ensureTableMigrations(db) {
4359
- try {
4360
- const hasAgents = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='agents'").get();
4361
- if (!hasAgents) {
4362
- db.exec(MIGRATIONS[4]);
4363
- }
4364
- } catch {}
4365
- try {
4366
- db.query("SELECT task_prefix FROM projects LIMIT 0").get();
4367
- } catch {
4368
- try {
4369
- db.exec(MIGRATIONS[5]);
4370
- } catch {}
4371
- }
4362
+ function ensureSchema(db) {
4372
4363
  const ensureColumn = (table, column, type) => {
4373
4364
  try {
4374
4365
  db.query(`SELECT ${column} FROM ${table} LIMIT 0`).get();
@@ -4378,6 +4369,76 @@ function ensureTableMigrations(db) {
4378
4369
  } catch {}
4379
4370
  }
4380
4371
  };
4372
+ const ensureTable = (name, sql) => {
4373
+ try {
4374
+ const exists = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
4375
+ if (!exists)
4376
+ db.exec(sql);
4377
+ } catch {}
4378
+ };
4379
+ const ensureIndex = (sql) => {
4380
+ try {
4381
+ db.exec(sql);
4382
+ } catch {}
4383
+ };
4384
+ ensureTable("agents", `
4385
+ CREATE TABLE agents (
4386
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT,
4387
+ role TEXT DEFAULT 'agent', permissions TEXT DEFAULT '["*"]',
4388
+ metadata TEXT DEFAULT '{}',
4389
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
4390
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
4391
+ )`);
4392
+ ensureTable("task_lists", `
4393
+ CREATE TABLE task_lists (
4394
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
4395
+ slug TEXT NOT NULL, name TEXT NOT NULL, description TEXT,
4396
+ metadata TEXT DEFAULT '{}',
4397
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
4398
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
4399
+ UNIQUE(project_id, slug)
4400
+ )`);
4401
+ ensureTable("plans", `
4402
+ CREATE TABLE plans (
4403
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
4404
+ task_list_id TEXT, agent_id TEXT,
4405
+ name TEXT NOT NULL, description TEXT,
4406
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
4407
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
4408
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
4409
+ )`);
4410
+ ensureTable("task_tags", `
4411
+ CREATE TABLE task_tags (
4412
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
4413
+ tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
4414
+ )`);
4415
+ ensureTable("task_history", `
4416
+ CREATE TABLE task_history (
4417
+ id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
4418
+ action TEXT NOT NULL, field TEXT, old_value TEXT, new_value TEXT, agent_id TEXT,
4419
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
4420
+ )`);
4421
+ ensureTable("webhooks", `
4422
+ CREATE TABLE webhooks (
4423
+ id TEXT PRIMARY KEY, url TEXT NOT NULL, events TEXT NOT NULL DEFAULT '[]',
4424
+ secret TEXT, active INTEGER NOT NULL DEFAULT 1,
4425
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
4426
+ )`);
4427
+ ensureTable("task_templates", `
4428
+ CREATE TABLE task_templates (
4429
+ id TEXT PRIMARY KEY, name TEXT NOT NULL, title_pattern TEXT NOT NULL,
4430
+ description TEXT, priority TEXT DEFAULT 'medium', tags TEXT DEFAULT '[]',
4431
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
4432
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
4433
+ metadata TEXT DEFAULT '{}',
4434
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
4435
+ )`);
4436
+ ensureColumn("projects", "task_list_id", "TEXT");
4437
+ ensureColumn("projects", "task_prefix", "TEXT");
4438
+ ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
4439
+ ensureColumn("tasks", "plan_id", "TEXT REFERENCES plans(id) ON DELETE SET NULL");
4440
+ ensureColumn("tasks", "task_list_id", "TEXT REFERENCES task_lists(id) ON DELETE SET NULL");
4441
+ ensureColumn("tasks", "short_id", "TEXT");
4381
4442
  ensureColumn("tasks", "due_at", "TEXT");
4382
4443
  ensureColumn("tasks", "estimated_minutes", "INTEGER");
4383
4444
  ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
@@ -4387,6 +4448,21 @@ function ensureTableMigrations(db) {
4387
4448
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
4388
4449
  ensureColumn("plans", "task_list_id", "TEXT");
4389
4450
  ensureColumn("plans", "agent_id", "TEXT");
4451
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
4452
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id)");
4453
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_due_at ON tasks(due_at)");
4454
+ ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL");
4455
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name)");
4456
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_project ON task_lists(project_id)");
4457
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug)");
4458
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag)");
4459
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_task ON task_tags(task_id)");
4460
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
4461
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
4462
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
4463
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
4464
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
4465
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
4390
4466
  }
4391
4467
  function backfillTaskTags(db) {
4392
4468
  try {
@@ -149,29 +149,20 @@ function runMigrations(db) {
149
149
  const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
150
150
  const currentLevel = result?.max_id ?? 0;
151
151
  for (let i = currentLevel;i < MIGRATIONS.length; i++) {
152
- db.exec(MIGRATIONS[i]);
152
+ try {
153
+ db.exec(MIGRATIONS[i]);
154
+ } catch {}
153
155
  }
154
156
  } catch {
155
157
  for (const migration of MIGRATIONS) {
156
- db.exec(migration);
158
+ try {
159
+ db.exec(migration);
160
+ } catch {}
157
161
  }
158
162
  }
159
- ensureTableMigrations(db);
163
+ ensureSchema(db);
160
164
  }
161
- function ensureTableMigrations(db) {
162
- try {
163
- const hasAgents = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='agents'").get();
164
- if (!hasAgents) {
165
- db.exec(MIGRATIONS[4]);
166
- }
167
- } catch {}
168
- try {
169
- db.query("SELECT task_prefix FROM projects LIMIT 0").get();
170
- } catch {
171
- try {
172
- db.exec(MIGRATIONS[5]);
173
- } catch {}
174
- }
165
+ function ensureSchema(db) {
175
166
  const ensureColumn = (table, column, type) => {
176
167
  try {
177
168
  db.query(`SELECT ${column} FROM ${table} LIMIT 0`).get();
@@ -181,6 +172,76 @@ function ensureTableMigrations(db) {
181
172
  } catch {}
182
173
  }
183
174
  };
175
+ const ensureTable = (name, sql) => {
176
+ try {
177
+ const exists = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
178
+ if (!exists)
179
+ db.exec(sql);
180
+ } catch {}
181
+ };
182
+ const ensureIndex = (sql) => {
183
+ try {
184
+ db.exec(sql);
185
+ } catch {}
186
+ };
187
+ ensureTable("agents", `
188
+ CREATE TABLE agents (
189
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT,
190
+ role TEXT DEFAULT 'agent', permissions TEXT DEFAULT '["*"]',
191
+ metadata TEXT DEFAULT '{}',
192
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
193
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
194
+ )`);
195
+ ensureTable("task_lists", `
196
+ CREATE TABLE task_lists (
197
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
198
+ slug TEXT NOT NULL, name TEXT NOT NULL, description TEXT,
199
+ metadata TEXT DEFAULT '{}',
200
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
201
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
202
+ UNIQUE(project_id, slug)
203
+ )`);
204
+ ensureTable("plans", `
205
+ CREATE TABLE plans (
206
+ id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
207
+ task_list_id TEXT, agent_id TEXT,
208
+ name TEXT NOT NULL, description TEXT,
209
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
210
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
211
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
212
+ )`);
213
+ ensureTable("task_tags", `
214
+ CREATE TABLE task_tags (
215
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
216
+ tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
217
+ )`);
218
+ ensureTable("task_history", `
219
+ CREATE TABLE task_history (
220
+ id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
221
+ action TEXT NOT NULL, field TEXT, old_value TEXT, new_value TEXT, agent_id TEXT,
222
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
223
+ )`);
224
+ ensureTable("webhooks", `
225
+ CREATE TABLE webhooks (
226
+ id TEXT PRIMARY KEY, url TEXT NOT NULL, events TEXT NOT NULL DEFAULT '[]',
227
+ secret TEXT, active INTEGER NOT NULL DEFAULT 1,
228
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
229
+ )`);
230
+ ensureTable("task_templates", `
231
+ CREATE TABLE task_templates (
232
+ id TEXT PRIMARY KEY, name TEXT NOT NULL, title_pattern TEXT NOT NULL,
233
+ description TEXT, priority TEXT DEFAULT 'medium', tags TEXT DEFAULT '[]',
234
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
235
+ plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
236
+ metadata TEXT DEFAULT '{}',
237
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
238
+ )`);
239
+ ensureColumn("projects", "task_list_id", "TEXT");
240
+ ensureColumn("projects", "task_prefix", "TEXT");
241
+ ensureColumn("projects", "task_counter", "INTEGER NOT NULL DEFAULT 0");
242
+ ensureColumn("tasks", "plan_id", "TEXT REFERENCES plans(id) ON DELETE SET NULL");
243
+ ensureColumn("tasks", "task_list_id", "TEXT REFERENCES task_lists(id) ON DELETE SET NULL");
244
+ ensureColumn("tasks", "short_id", "TEXT");
184
245
  ensureColumn("tasks", "due_at", "TEXT");
185
246
  ensureColumn("tasks", "estimated_minutes", "INTEGER");
186
247
  ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
@@ -190,6 +251,21 @@ function ensureTableMigrations(db) {
190
251
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
191
252
  ensureColumn("plans", "task_list_id", "TEXT");
192
253
  ensureColumn("plans", "agent_id", "TEXT");
254
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id)");
255
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_task_list ON tasks(task_list_id)");
256
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_due_at ON tasks(due_at)");
257
+ ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_short_id ON tasks(short_id) WHERE short_id IS NOT NULL");
258
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name)");
259
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_project ON task_lists(project_id)");
260
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_lists_slug ON task_lists(slug)");
261
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag)");
262
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_tags_task ON task_tags(task_id)");
263
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
264
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
265
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
266
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
267
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
268
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
193
269
  }
194
270
  function backfillTaskTags(db) {
195
271
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.9.18",
3
+ "version": "0.9.19",
4
4
  "description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",