aitasks 1.4.0 → 1.4.2
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/index.js +282 -142
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1890,7 +1890,7 @@ var require_commander = __commonJS((exports) => {
|
|
|
1890
1890
|
var require_package = __commonJS((exports, module) => {
|
|
1891
1891
|
module.exports = {
|
|
1892
1892
|
name: "aitasks",
|
|
1893
|
-
version: "1.4.
|
|
1893
|
+
version: "1.4.2",
|
|
1894
1894
|
description: "CLI task management tool built for AI agents",
|
|
1895
1895
|
type: "module",
|
|
1896
1896
|
bin: {
|
|
@@ -29071,7 +29071,7 @@ import { join as join3 } from "path";
|
|
|
29071
29071
|
import { existsSync as existsSync3 } from "fs";
|
|
29072
29072
|
|
|
29073
29073
|
// src/db/schema.ts
|
|
29074
|
-
var SCHEMA_VERSION =
|
|
29074
|
+
var SCHEMA_VERSION = 4;
|
|
29075
29075
|
function runMigrations(db) {
|
|
29076
29076
|
const row = db.query(`SELECT value FROM meta WHERE key = 'schema_version'`).get();
|
|
29077
29077
|
const current = row ? parseInt(row.value, 10) : 1;
|
|
@@ -29085,6 +29085,12 @@ function runMigrations(db) {
|
|
|
29085
29085
|
db.exec(`UPDATE agents SET first_seen = last_seen WHERE first_seen IS NULL`);
|
|
29086
29086
|
db.exec(`UPDATE meta SET value = '3' WHERE key = 'schema_version'`);
|
|
29087
29087
|
}
|
|
29088
|
+
if (current < 4) {
|
|
29089
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_events_task_created ON events(task_id, created_at)`);
|
|
29090
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_agents_current_task ON agents(current_task)`);
|
|
29091
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_type ON tasks(type)`);
|
|
29092
|
+
db.exec(`UPDATE meta SET value = '4' WHERE key = 'schema_version'`);
|
|
29093
|
+
}
|
|
29088
29094
|
}
|
|
29089
29095
|
function initializeSchema(db) {
|
|
29090
29096
|
db.exec(`
|
|
@@ -29134,8 +29140,11 @@ function initializeSchema(db) {
|
|
|
29134
29140
|
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
|
|
29135
29141
|
CREATE INDEX IF NOT EXISTS idx_tasks_assigned_to ON tasks(assigned_to);
|
|
29136
29142
|
CREATE INDEX IF NOT EXISTS idx_tasks_parent_id ON tasks(parent_id);
|
|
29137
|
-
CREATE INDEX IF NOT EXISTS idx_events_task_id
|
|
29138
|
-
CREATE INDEX IF NOT EXISTS idx_events_created_at
|
|
29143
|
+
CREATE INDEX IF NOT EXISTS idx_events_task_id ON events(task_id);
|
|
29144
|
+
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at);
|
|
29145
|
+
CREATE INDEX IF NOT EXISTS idx_events_task_created ON events(task_id, created_at);
|
|
29146
|
+
CREATE INDEX IF NOT EXISTS idx_agents_current_task ON agents(current_task);
|
|
29147
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_type ON tasks(type);
|
|
29139
29148
|
`);
|
|
29140
29149
|
const insert = db.prepare(`INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)`);
|
|
29141
29150
|
insert.run("last_task_number", "0");
|
|
@@ -30214,29 +30223,82 @@ var be = async (s, n) => {
|
|
|
30214
30223
|
};
|
|
30215
30224
|
|
|
30216
30225
|
// src/models/event.ts
|
|
30217
|
-
|
|
30226
|
+
var _stmtDb = null;
|
|
30227
|
+
var _insertEvent = null;
|
|
30228
|
+
var _selectTaskEvents = null;
|
|
30229
|
+
var _selectAllEvents = null;
|
|
30230
|
+
var _selectLastReviewEvent = null;
|
|
30231
|
+
function stmts() {
|
|
30218
30232
|
const db = getDb();
|
|
30219
|
-
|
|
30220
|
-
|
|
30221
|
-
|
|
30222
|
-
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30226
|
-
|
|
30233
|
+
if (_stmtDb !== db) {
|
|
30234
|
+
_stmtDb = db;
|
|
30235
|
+
_insertEvent = db.query(`INSERT INTO events (task_id, agent_id, event_type, payload, created_at)
|
|
30236
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
30237
|
+
_selectTaskEvents = db.query("SELECT * FROM events WHERE task_id = ? ORDER BY created_at ASC");
|
|
30238
|
+
_selectAllEvents = db.query("SELECT * FROM events ORDER BY created_at DESC LIMIT ?");
|
|
30239
|
+
_selectLastReviewEvent = db.query(`SELECT * FROM events WHERE task_id = ? AND event_type = 'review_requested'
|
|
30240
|
+
ORDER BY created_at DESC LIMIT 1`);
|
|
30241
|
+
}
|
|
30242
|
+
return { _insertEvent, _selectTaskEvents, _selectAllEvents, _selectLastReviewEvent };
|
|
30243
|
+
}
|
|
30244
|
+
function parseEvent(r2) {
|
|
30245
|
+
return { ...r2, payload: JSON.parse(r2.payload) };
|
|
30246
|
+
}
|
|
30247
|
+
function logEvent(data) {
|
|
30248
|
+
stmts()._insertEvent.run(data.task_id, data.agent_id ?? null, data.event_type, JSON.stringify(data.payload ?? {}), Date.now());
|
|
30227
30249
|
}
|
|
30228
30250
|
function getTaskEvents(taskId) {
|
|
30229
|
-
const
|
|
30230
|
-
|
|
30231
|
-
|
|
30251
|
+
const rows = stmts()._selectTaskEvents.all(taskId);
|
|
30252
|
+
return rows.map(parseEvent);
|
|
30253
|
+
}
|
|
30254
|
+
function getLastReviewEvent(taskId) {
|
|
30255
|
+
const row = stmts()._selectLastReviewEvent.get(taskId);
|
|
30256
|
+
return row ? parseEvent(row) : null;
|
|
30232
30257
|
}
|
|
30233
30258
|
function getAllEvents(limit = 100) {
|
|
30234
|
-
const
|
|
30235
|
-
|
|
30236
|
-
return rows.map((r2) => ({ ...r2, payload: JSON.parse(r2.payload) }));
|
|
30259
|
+
const rows = stmts()._selectAllEvents.all(limit);
|
|
30260
|
+
return rows.map(parseEvent);
|
|
30237
30261
|
}
|
|
30238
30262
|
|
|
30239
30263
|
// src/models/task.ts
|
|
30264
|
+
var _stmtDb2 = null;
|
|
30265
|
+
var _getTaskStmt = null;
|
|
30266
|
+
var _getAllIdsStmt = null;
|
|
30267
|
+
var _getMetaStmt = null;
|
|
30268
|
+
var _updateMetaStmt = null;
|
|
30269
|
+
var _upsertAgentStmt = null;
|
|
30270
|
+
var _heartbeatStmt = null;
|
|
30271
|
+
var _releaseAgentStmt = null;
|
|
30272
|
+
var _subtaskCountStmt = null;
|
|
30273
|
+
function stmts2() {
|
|
30274
|
+
const db = getDb();
|
|
30275
|
+
if (_stmtDb2 !== db) {
|
|
30276
|
+
_stmtDb2 = db;
|
|
30277
|
+
_getTaskStmt = db.query("SELECT * FROM tasks WHERE id = ?");
|
|
30278
|
+
_getAllIdsStmt = db.query("SELECT id FROM tasks");
|
|
30279
|
+
_getMetaStmt = db.query(`SELECT value FROM meta WHERE key = 'last_task_number'`);
|
|
30280
|
+
_updateMetaStmt = db.query(`UPDATE meta SET value = ? WHERE key = 'last_task_number'`);
|
|
30281
|
+
_upsertAgentStmt = db.query(`INSERT INTO agents (id, first_seen, last_seen, current_task)
|
|
30282
|
+
VALUES (?, ?, ?, ?)
|
|
30283
|
+
ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen, current_task = excluded.current_task`);
|
|
30284
|
+
_heartbeatStmt = db.query(`INSERT INTO agents (id, first_seen, last_seen, current_task)
|
|
30285
|
+
VALUES (?, ?, ?, ?)
|
|
30286
|
+
ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen`);
|
|
30287
|
+
_releaseAgentStmt = db.query(`UPDATE agents SET current_task = NULL WHERE id = ?`);
|
|
30288
|
+
_subtaskCountStmt = db.query(`SELECT COUNT(*) as count FROM tasks WHERE parent_id = ?`);
|
|
30289
|
+
}
|
|
30290
|
+
return {
|
|
30291
|
+
db,
|
|
30292
|
+
getTask: _getTaskStmt,
|
|
30293
|
+
getAllIds: _getAllIdsStmt,
|
|
30294
|
+
getMeta: _getMetaStmt,
|
|
30295
|
+
updateMeta: _updateMetaStmt,
|
|
30296
|
+
upsertAgent: _upsertAgentStmt,
|
|
30297
|
+
heartbeat: _heartbeatStmt,
|
|
30298
|
+
releaseAgent: _releaseAgentStmt,
|
|
30299
|
+
subtaskCount: _subtaskCountStmt
|
|
30300
|
+
};
|
|
30301
|
+
}
|
|
30240
30302
|
function parseTask(row) {
|
|
30241
30303
|
return {
|
|
30242
30304
|
id: row.id,
|
|
@@ -30260,17 +30322,20 @@ function parseTask(row) {
|
|
|
30260
30322
|
};
|
|
30261
30323
|
}
|
|
30262
30324
|
function nextTaskId() {
|
|
30263
|
-
const
|
|
30264
|
-
const meta =
|
|
30325
|
+
const s = stmts2();
|
|
30326
|
+
const meta = s.getMeta.get();
|
|
30265
30327
|
const next = parseInt(meta.value, 10) + 1;
|
|
30266
|
-
|
|
30328
|
+
s.updateMeta.run(String(next));
|
|
30267
30329
|
return `TASK-${String(next).padStart(3, "0")}`;
|
|
30268
30330
|
}
|
|
30269
30331
|
function getTask(id) {
|
|
30270
|
-
const
|
|
30271
|
-
const row = db.query("SELECT * FROM tasks WHERE id = ?").get(id);
|
|
30332
|
+
const row = stmts2().getTask.get(id);
|
|
30272
30333
|
return row ? parseTask(row) : null;
|
|
30273
30334
|
}
|
|
30335
|
+
function getAllTaskIds() {
|
|
30336
|
+
const rows = stmts2().getAllIds.all();
|
|
30337
|
+
return rows.map((r2) => r2.id);
|
|
30338
|
+
}
|
|
30274
30339
|
var PRIORITY_ORDER = `CASE priority
|
|
30275
30340
|
WHEN 'critical' THEN 0
|
|
30276
30341
|
WHEN 'high' THEN 1
|
|
@@ -30284,7 +30349,7 @@ var STATUS_ORDER = `CASE status
|
|
|
30284
30349
|
WHEN 'backlog' THEN 4
|
|
30285
30350
|
WHEN 'done' THEN 5 END`;
|
|
30286
30351
|
function listTasks(filters = {}) {
|
|
30287
|
-
const db =
|
|
30352
|
+
const db = stmts2().db;
|
|
30288
30353
|
const conds = [];
|
|
30289
30354
|
const params = [];
|
|
30290
30355
|
if (filters.status) {
|
|
@@ -30329,8 +30394,25 @@ function listTasks(filters = {}) {
|
|
|
30329
30394
|
function getSubtasks(parentId) {
|
|
30330
30395
|
return listTasks({ parent_id: parentId });
|
|
30331
30396
|
}
|
|
30397
|
+
function searchTasks(terms, status) {
|
|
30398
|
+
const db = stmts2().db;
|
|
30399
|
+
const conds = [];
|
|
30400
|
+
const params = [];
|
|
30401
|
+
if (status) {
|
|
30402
|
+
conds.push("status = ?");
|
|
30403
|
+
params.push(status);
|
|
30404
|
+
}
|
|
30405
|
+
for (const term of terms) {
|
|
30406
|
+
conds.push(`(title LIKE ? OR description LIKE ? OR implementation_notes LIKE ? OR acceptance_criteria LIKE ?)`);
|
|
30407
|
+
const like = `%${term}%`;
|
|
30408
|
+
params.push(like, like, like, like);
|
|
30409
|
+
}
|
|
30410
|
+
const where = conds.length > 0 ? `WHERE ${conds.join(" AND ")}` : "";
|
|
30411
|
+
const rows = db.query(`SELECT * FROM tasks ${where}`).all(...params);
|
|
30412
|
+
return rows.map(parseTask);
|
|
30413
|
+
}
|
|
30332
30414
|
function createTask(data) {
|
|
30333
|
-
const db =
|
|
30415
|
+
const db = stmts2().db;
|
|
30334
30416
|
const id = nextTaskId();
|
|
30335
30417
|
const now = Date.now();
|
|
30336
30418
|
db.run(`INSERT INTO tasks
|
|
@@ -30361,7 +30443,7 @@ var JSON_FIELDS = new Set([
|
|
|
30361
30443
|
"metadata"
|
|
30362
30444
|
]);
|
|
30363
30445
|
function updateTask(id, updates) {
|
|
30364
|
-
const db =
|
|
30446
|
+
const db = stmts2().db;
|
|
30365
30447
|
const fields = ["updated_at = ?"];
|
|
30366
30448
|
const params = [Date.now()];
|
|
30367
30449
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -30371,8 +30453,8 @@ function updateTask(id, updates) {
|
|
|
30371
30453
|
params.push(JSON_FIELDS.has(key) ? JSON.stringify(value) : value);
|
|
30372
30454
|
}
|
|
30373
30455
|
params.push(id);
|
|
30374
|
-
db.
|
|
30375
|
-
return
|
|
30456
|
+
const row = db.query(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ? RETURNING *`).get(...params);
|
|
30457
|
+
return row ? parseTask(row) : null;
|
|
30376
30458
|
}
|
|
30377
30459
|
function addImplementationNote(taskId, note, agentId) {
|
|
30378
30460
|
const task = getTask(taskId);
|
|
@@ -30420,7 +30502,7 @@ function checkCriterion(taskId, index, evidence, agentId) {
|
|
|
30420
30502
|
return { task: updated };
|
|
30421
30503
|
}
|
|
30422
30504
|
function claimTask(taskId, agentId) {
|
|
30423
|
-
const
|
|
30505
|
+
const s = stmts2();
|
|
30424
30506
|
const task = getTask(taskId);
|
|
30425
30507
|
if (!task)
|
|
30426
30508
|
return { task: null, error: "Task not found" };
|
|
@@ -30435,14 +30517,14 @@ function claimTask(taskId, agentId) {
|
|
|
30435
30517
|
error: `Task is blocked by: ${task.blocked_by.join(", ")}. Complete those first.`
|
|
30436
30518
|
};
|
|
30437
30519
|
}
|
|
30438
|
-
|
|
30439
|
-
|
|
30440
|
-
ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen, current_task = excluded.current_task`, [agentId, Date.now(), Date.now(), taskId]);
|
|
30520
|
+
const now = Date.now();
|
|
30521
|
+
s.upsertAgent.run(agentId, now, now, taskId);
|
|
30441
30522
|
const updated = updateTask(taskId, { assigned_to: agentId, status: "ready" });
|
|
30442
30523
|
logEvent({ task_id: taskId, agent_id: agentId, event_type: "claimed", payload: {} });
|
|
30443
30524
|
return { task: updated };
|
|
30444
30525
|
}
|
|
30445
30526
|
function startTask(taskId, agentId) {
|
|
30527
|
+
const s = stmts2();
|
|
30446
30528
|
const task = getTask(taskId);
|
|
30447
30529
|
if (!task)
|
|
30448
30530
|
return { task: null, error: "Task not found" };
|
|
@@ -30451,11 +30533,9 @@ function startTask(taskId, agentId) {
|
|
|
30451
30533
|
if (task.assigned_to && task.assigned_to !== agentId) {
|
|
30452
30534
|
return { task: null, error: `Task is assigned to ${task.assigned_to}, not ${agentId}` };
|
|
30453
30535
|
}
|
|
30454
|
-
const db = getDb();
|
|
30455
30536
|
if (!task.assigned_to) {
|
|
30456
|
-
|
|
30457
|
-
|
|
30458
|
-
ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen, current_task = excluded.current_task`, [agentId, Date.now(), Date.now(), taskId]);
|
|
30537
|
+
const now = Date.now();
|
|
30538
|
+
s.upsertAgent.run(agentId, now, now, taskId);
|
|
30459
30539
|
}
|
|
30460
30540
|
const updated = updateTask(taskId, {
|
|
30461
30541
|
assigned_to: agentId,
|
|
@@ -30466,7 +30546,7 @@ function startTask(taskId, agentId) {
|
|
|
30466
30546
|
return { task: updated };
|
|
30467
30547
|
}
|
|
30468
30548
|
function completeTask(taskId, agentId) {
|
|
30469
|
-
const
|
|
30549
|
+
const s = stmts2();
|
|
30470
30550
|
const task = getTask(taskId);
|
|
30471
30551
|
if (!task)
|
|
30472
30552
|
return { task: null, error: "Task not found" };
|
|
@@ -30498,8 +30578,7 @@ function completeTask(taskId, agentId) {
|
|
|
30498
30578
|
` + ` A separate review sub-agent must run: aitasks done ${taskId} --agent <review-agent-id>`
|
|
30499
30579
|
};
|
|
30500
30580
|
}
|
|
30501
|
-
const
|
|
30502
|
-
const reviewEvent = [...events].reverse().find((e2) => e2.event_type === "review_requested");
|
|
30581
|
+
const reviewEvent = getLastReviewEvent(taskId);
|
|
30503
30582
|
if (reviewEvent?.agent_id && reviewEvent.agent_id === agentId) {
|
|
30504
30583
|
return {
|
|
30505
30584
|
task: null,
|
|
@@ -30508,69 +30587,87 @@ function completeTask(taskId, agentId) {
|
|
|
30508
30587
|
};
|
|
30509
30588
|
}
|
|
30510
30589
|
}
|
|
30511
|
-
const
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
|
|
30515
|
-
const
|
|
30516
|
-
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30523
|
-
|
|
30524
|
-
|
|
30525
|
-
|
|
30526
|
-
|
|
30590
|
+
const db = s.db;
|
|
30591
|
+
const doComplete = db.transaction(() => {
|
|
30592
|
+
const updated2 = updateTask(taskId, { status: "done", completed_at: Date.now() });
|
|
30593
|
+
logEvent({ task_id: taskId, agent_id: agentId, event_type: "completed", payload: {} });
|
|
30594
|
+
const pendingRows = db.query(`SELECT id, blocked_by FROM tasks
|
|
30595
|
+
WHERE status != 'done' AND blocked_by LIKE '%' || ? || '%'`).all(taskId);
|
|
30596
|
+
for (const row of pendingRows) {
|
|
30597
|
+
const blockedBy = JSON.parse(row.blocked_by);
|
|
30598
|
+
if (!blockedBy.includes(taskId))
|
|
30599
|
+
continue;
|
|
30600
|
+
const remaining = blockedBy.filter((id) => id !== taskId);
|
|
30601
|
+
const newStatus = remaining.length === 0 ? "ready" : "blocked";
|
|
30602
|
+
db.run(`UPDATE tasks SET blocked_by = ?, status = ?, updated_at = ? WHERE id = ?`, [JSON.stringify(remaining), newStatus, Date.now(), row.id]);
|
|
30603
|
+
if (newStatus === "ready") {
|
|
30604
|
+
logEvent({
|
|
30605
|
+
task_id: row.id,
|
|
30606
|
+
event_type: "auto_unblocked",
|
|
30607
|
+
payload: { unblocked_by: taskId }
|
|
30608
|
+
});
|
|
30609
|
+
}
|
|
30527
30610
|
}
|
|
30528
|
-
|
|
30529
|
-
|
|
30530
|
-
|
|
30611
|
+
if (agentId)
|
|
30612
|
+
s.releaseAgent.run(agentId);
|
|
30613
|
+
return updated2;
|
|
30614
|
+
});
|
|
30615
|
+
const updated = doComplete();
|
|
30531
30616
|
return { task: updated };
|
|
30532
30617
|
}
|
|
30533
30618
|
function blockTask(taskId, blockerIds, agentId) {
|
|
30619
|
+
const s = stmts2();
|
|
30620
|
+
const db = s.db;
|
|
30534
30621
|
const task = getTask(taskId);
|
|
30535
30622
|
if (!task)
|
|
30536
30623
|
return { task: null, error: "Task not found" };
|
|
30624
|
+
const placeholders = blockerIds.map(() => "?").join(",");
|
|
30625
|
+
const blockerRows = db.query(`SELECT * FROM tasks WHERE id IN (${placeholders})`).all(...blockerIds);
|
|
30626
|
+
const blockerMap = new Map(blockerRows.map((r2) => [r2.id, parseTask(r2)]));
|
|
30627
|
+
const allRows = db.query("SELECT * FROM tasks").all();
|
|
30628
|
+
const allTaskMap = new Map(allRows.map((r2) => [r2.id, parseTask(r2)]));
|
|
30537
30629
|
for (const bid of blockerIds) {
|
|
30538
|
-
const blocker =
|
|
30630
|
+
const blocker = blockerMap.get(bid);
|
|
30539
30631
|
if (!blocker)
|
|
30540
30632
|
return { task: null, error: `Blocker not found: ${bid}` };
|
|
30541
30633
|
if (blocker.status === "done") {
|
|
30542
30634
|
return { task: null, error: `Cannot block on completed task: ${bid}` };
|
|
30543
30635
|
}
|
|
30544
|
-
if (
|
|
30636
|
+
if (detectCycleFromMap(taskId, bid, allTaskMap)) {
|
|
30545
30637
|
return { task: null, error: `Circular dependency detected: ${taskId} \u2192 ${bid}` };
|
|
30546
30638
|
}
|
|
30547
30639
|
}
|
|
30548
30640
|
const newBlockedBy = [...new Set([...task.blocked_by, ...blockerIds])];
|
|
30549
|
-
|
|
30550
|
-
const
|
|
30551
|
-
|
|
30552
|
-
|
|
30553
|
-
|
|
30554
|
-
|
|
30555
|
-
|
|
30556
|
-
|
|
30557
|
-
|
|
30558
|
-
|
|
30641
|
+
const doBlock = db.transaction(() => {
|
|
30642
|
+
for (const bid of blockerIds) {
|
|
30643
|
+
const blocker = blockerMap.get(bid);
|
|
30644
|
+
const newBlocks = JSON.stringify([...new Set([...blocker.blocks, taskId])]);
|
|
30645
|
+
db.run(`UPDATE tasks SET blocks = ?, updated_at = ? WHERE id = ?`, [newBlocks, Date.now(), bid]);
|
|
30646
|
+
}
|
|
30647
|
+
const updated = updateTask(taskId, { blocked_by: newBlockedBy, status: "blocked" });
|
|
30648
|
+
logEvent({
|
|
30649
|
+
task_id: taskId,
|
|
30650
|
+
agent_id: agentId,
|
|
30651
|
+
event_type: "blocked",
|
|
30652
|
+
payload: { blocked_by: blockerIds }
|
|
30653
|
+
});
|
|
30654
|
+
return updated;
|
|
30559
30655
|
});
|
|
30560
|
-
return { task:
|
|
30656
|
+
return { task: doBlock() };
|
|
30561
30657
|
}
|
|
30562
|
-
function
|
|
30658
|
+
function detectCycleFromMap(taskId, candidateBlocker, taskMap, visited = new Set) {
|
|
30563
30659
|
if (candidateBlocker === taskId)
|
|
30564
30660
|
return true;
|
|
30565
30661
|
if (visited.has(candidateBlocker))
|
|
30566
30662
|
return false;
|
|
30567
30663
|
visited.add(candidateBlocker);
|
|
30568
|
-
const blocker =
|
|
30664
|
+
const blocker = taskMap.get(candidateBlocker);
|
|
30569
30665
|
if (!blocker)
|
|
30570
30666
|
return false;
|
|
30571
|
-
return blocker.blocked_by.some((id) =>
|
|
30667
|
+
return blocker.blocked_by.some((id) => detectCycleFromMap(taskId, id, taskMap, visited));
|
|
30572
30668
|
}
|
|
30573
30669
|
function unblockTask(taskId, fromId, agentId) {
|
|
30670
|
+
const db = stmts2().db;
|
|
30574
30671
|
const task = getTask(taskId);
|
|
30575
30672
|
if (!task)
|
|
30576
30673
|
return { task: null, error: "Task not found" };
|
|
@@ -30578,9 +30675,10 @@ function unblockTask(taskId, fromId, agentId) {
|
|
|
30578
30675
|
return { task: null, error: `${taskId} is not blocked by ${fromId}` };
|
|
30579
30676
|
}
|
|
30580
30677
|
const remaining = task.blocked_by.filter((id) => id !== fromId);
|
|
30581
|
-
const
|
|
30582
|
-
if (
|
|
30583
|
-
|
|
30678
|
+
const blockerRow = db.query("SELECT blocks FROM tasks WHERE id = ?").get(fromId);
|
|
30679
|
+
if (blockerRow) {
|
|
30680
|
+
const blocks = JSON.parse(blockerRow.blocks).filter((id) => id !== taskId);
|
|
30681
|
+
db.run(`UPDATE tasks SET blocks = ?, updated_at = ? WHERE id = ?`, [JSON.stringify(blocks), Date.now(), fromId]);
|
|
30584
30682
|
}
|
|
30585
30683
|
const newStatus = remaining.length === 0 ? "ready" : "blocked";
|
|
30586
30684
|
const updated = updateTask(taskId, { blocked_by: remaining, status: newStatus });
|
|
@@ -30605,18 +30703,27 @@ function rejectTask(taskId, reason, agentId) {
|
|
|
30605
30703
|
if (task.status !== "review") {
|
|
30606
30704
|
return { task: null, error: "Task must be in review status to reject" };
|
|
30607
30705
|
}
|
|
30608
|
-
|
|
30609
|
-
|
|
30706
|
+
const agent = agentId ?? "human";
|
|
30707
|
+
const entry = {
|
|
30708
|
+
timestamp: Date.now(),
|
|
30709
|
+
agent,
|
|
30710
|
+
note: `REVIEW REJECTED: ${reason}`
|
|
30711
|
+
};
|
|
30712
|
+
const updated = updateTask(taskId, {
|
|
30713
|
+
status: "in_progress",
|
|
30714
|
+
implementation_notes: [...task.implementation_notes, entry]
|
|
30715
|
+
});
|
|
30716
|
+
logEvent({ task_id: taskId, agent_id: agentId, event_type: "note_added", payload: { note: entry.note } });
|
|
30610
30717
|
logEvent({
|
|
30611
30718
|
task_id: taskId,
|
|
30612
30719
|
agent_id: agentId,
|
|
30613
30720
|
event_type: "rejected",
|
|
30614
30721
|
payload: { reason }
|
|
30615
30722
|
});
|
|
30616
|
-
return { task:
|
|
30723
|
+
return { task: updated };
|
|
30617
30724
|
}
|
|
30618
30725
|
function unclaimTask(taskId, agentId, reason) {
|
|
30619
|
-
const
|
|
30726
|
+
const s = stmts2();
|
|
30620
30727
|
const task = getTask(taskId);
|
|
30621
30728
|
if (!task)
|
|
30622
30729
|
return { task: null, error: "Task not found" };
|
|
@@ -30624,89 +30731,109 @@ function unclaimTask(taskId, agentId, reason) {
|
|
|
30624
30731
|
return { task: null, error: `Task is not assigned to ${agentId}` };
|
|
30625
30732
|
}
|
|
30626
30733
|
const backStatus = task.blocked_by.length > 0 ? "blocked" : "ready";
|
|
30627
|
-
|
|
30734
|
+
let updated;
|
|
30628
30735
|
if (reason) {
|
|
30629
|
-
|
|
30736
|
+
const entry = {
|
|
30737
|
+
timestamp: Date.now(),
|
|
30738
|
+
agent: agentId,
|
|
30739
|
+
note: `UNCLAIMED by ${agentId}: ${reason}`
|
|
30740
|
+
};
|
|
30741
|
+
updated = updateTask(taskId, {
|
|
30742
|
+
assigned_to: null,
|
|
30743
|
+
status: backStatus,
|
|
30744
|
+
started_at: null,
|
|
30745
|
+
implementation_notes: [...task.implementation_notes, entry]
|
|
30746
|
+
});
|
|
30747
|
+
logEvent({ task_id: taskId, agent_id: agentId, event_type: "note_added", payload: { note: entry.note } });
|
|
30748
|
+
} else {
|
|
30749
|
+
updated = updateTask(taskId, { assigned_to: null, status: backStatus, started_at: null });
|
|
30630
30750
|
}
|
|
30631
|
-
|
|
30751
|
+
s.releaseAgent.run(agentId);
|
|
30632
30752
|
logEvent({
|
|
30633
30753
|
task_id: taskId,
|
|
30634
30754
|
agent_id: agentId,
|
|
30635
30755
|
event_type: "unclaimed",
|
|
30636
30756
|
payload: { reason: reason ?? "" }
|
|
30637
30757
|
});
|
|
30638
|
-
return { task:
|
|
30758
|
+
return { task: updated };
|
|
30639
30759
|
}
|
|
30640
30760
|
function heartbeat(agentId, taskId) {
|
|
30641
|
-
const
|
|
30642
|
-
|
|
30643
|
-
VALUES (?, ?, ?, ?)
|
|
30644
|
-
ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen`, [agentId, Date.now(), Date.now(), taskId ?? null]);
|
|
30761
|
+
const now = Date.now();
|
|
30762
|
+
stmts2().heartbeat.run(agentId, now, now, taskId ?? null);
|
|
30645
30763
|
}
|
|
30646
30764
|
function listAgents() {
|
|
30647
|
-
const db =
|
|
30765
|
+
const db = stmts2().db;
|
|
30648
30766
|
return db.query("SELECT * FROM agents ORDER BY last_seen DESC").all();
|
|
30649
30767
|
}
|
|
30650
30768
|
function getNextTask(agentId) {
|
|
30651
|
-
const
|
|
30769
|
+
const db = stmts2().db;
|
|
30652
30770
|
if (agentId) {
|
|
30653
|
-
const mine =
|
|
30771
|
+
const mine = db.query(`SELECT * FROM tasks WHERE status = 'ready' AND assigned_to = ?
|
|
30772
|
+
ORDER BY ${PRIORITY_ORDER}, created_at ASC LIMIT 1`).get(agentId);
|
|
30654
30773
|
if (mine)
|
|
30655
|
-
return mine;
|
|
30774
|
+
return parseTask(mine);
|
|
30656
30775
|
}
|
|
30657
|
-
|
|
30776
|
+
const unassigned = db.query(`SELECT * FROM tasks WHERE status = 'ready' AND assigned_to IS NULL
|
|
30777
|
+
ORDER BY ${PRIORITY_ORDER}, created_at ASC LIMIT 1`).get();
|
|
30778
|
+
if (unassigned)
|
|
30779
|
+
return parseTask(unassigned);
|
|
30780
|
+
const any = db.query(`SELECT * FROM tasks WHERE status = 'ready'
|
|
30781
|
+
ORDER BY ${PRIORITY_ORDER}, created_at ASC LIMIT 1`).get();
|
|
30782
|
+
return any ? parseTask(any) : null;
|
|
30658
30783
|
}
|
|
30659
30784
|
function getStats() {
|
|
30660
|
-
const db =
|
|
30661
|
-
const
|
|
30662
|
-
const
|
|
30663
|
-
const
|
|
30664
|
-
const
|
|
30665
|
-
|
|
30666
|
-
|
|
30667
|
-
|
|
30668
|
-
|
|
30669
|
-
|
|
30670
|
-
|
|
30785
|
+
const db = stmts2().db;
|
|
30786
|
+
const rows = db.query("SELECT status, priority, type, COUNT(*) as count FROM tasks GROUP BY status, priority, type").all();
|
|
30787
|
+
const by_status = {};
|
|
30788
|
+
const by_priority = {};
|
|
30789
|
+
const by_type = {};
|
|
30790
|
+
let total = 0;
|
|
30791
|
+
for (const row of rows) {
|
|
30792
|
+
by_status[row.status] = (by_status[row.status] ?? 0) + row.count;
|
|
30793
|
+
by_priority[row.priority] = (by_priority[row.priority] ?? 0) + row.count;
|
|
30794
|
+
by_type[row.type] = (by_type[row.type] ?? 0) + row.count;
|
|
30795
|
+
total += row.count;
|
|
30796
|
+
}
|
|
30797
|
+
return { by_status, by_priority, by_type, total };
|
|
30671
30798
|
}
|
|
30672
30799
|
function deleteTask(taskId, agentId) {
|
|
30673
|
-
const
|
|
30800
|
+
const s = stmts2();
|
|
30801
|
+
const db = s.db;
|
|
30674
30802
|
const task = getTask(taskId);
|
|
30675
30803
|
if (!task) {
|
|
30676
30804
|
return { success: false, error: "Task not found" };
|
|
30677
30805
|
}
|
|
30678
|
-
const
|
|
30679
|
-
if (
|
|
30806
|
+
const subtaskCount = s.subtaskCount.get(taskId).count;
|
|
30807
|
+
if (subtaskCount > 0) {
|
|
30680
30808
|
return {
|
|
30681
30809
|
success: false,
|
|
30682
|
-
error: `Cannot delete ${taskId} - it has ${
|
|
30810
|
+
error: `Cannot delete ${taskId} - it has ${subtaskCount} subtask(s). Delete subtasks first.`
|
|
30683
30811
|
};
|
|
30684
30812
|
}
|
|
30685
|
-
|
|
30686
|
-
|
|
30687
|
-
|
|
30688
|
-
|
|
30689
|
-
|
|
30813
|
+
const doDelete = db.transaction(() => {
|
|
30814
|
+
logEvent({
|
|
30815
|
+
task_id: taskId,
|
|
30816
|
+
agent_id: agentId,
|
|
30817
|
+
event_type: "deleted",
|
|
30818
|
+
payload: {}
|
|
30819
|
+
});
|
|
30820
|
+
const blocksRows = db.query(`SELECT id, blocks FROM tasks WHERE blocks LIKE '%' || ? || '%' AND id != ?`).all(taskId, taskId);
|
|
30821
|
+
for (const row of blocksRows) {
|
|
30822
|
+
const blocks = JSON.parse(row.blocks).filter((id) => id !== taskId);
|
|
30823
|
+
db.run(`UPDATE tasks SET blocks = ?, updated_at = ? WHERE id = ?`, [JSON.stringify(blocks), Date.now(), row.id]);
|
|
30824
|
+
}
|
|
30825
|
+
const blockedByRows = db.query(`SELECT id, blocked_by FROM tasks WHERE blocked_by LIKE '%' || ? || '%' AND id != ?`).all(taskId, taskId);
|
|
30826
|
+
for (const row of blockedByRows) {
|
|
30827
|
+
const remaining = JSON.parse(row.blocked_by).filter((id) => id !== taskId);
|
|
30828
|
+
const newStatus = remaining.length === 0 ? "ready" : "blocked";
|
|
30829
|
+
db.run(`UPDATE tasks SET blocked_by = ?, status = ?, updated_at = ? WHERE id = ?`, [JSON.stringify(remaining), newStatus, Date.now(), row.id]);
|
|
30830
|
+
}
|
|
30831
|
+
db.run(`UPDATE tasks SET parent_id = NULL, updated_at = ? WHERE parent_id = ?`, [Date.now(), taskId]);
|
|
30832
|
+
db.run("DELETE FROM events WHERE task_id = ?", [taskId]);
|
|
30833
|
+
db.run("UPDATE agents SET current_task = NULL WHERE current_task = ?", [taskId]);
|
|
30834
|
+
db.run("DELETE FROM tasks WHERE id = ?", [taskId]);
|
|
30690
30835
|
});
|
|
30691
|
-
|
|
30692
|
-
for (const t of allTasks) {
|
|
30693
|
-
if (t.blocks.includes(taskId)) {
|
|
30694
|
-
updateTask(t.id, { blocks: t.blocks.filter((id) => id !== taskId) });
|
|
30695
|
-
}
|
|
30696
|
-
if (t.blocked_by.includes(taskId)) {
|
|
30697
|
-
const remaining = t.blocked_by.filter((id) => id !== taskId);
|
|
30698
|
-
updateTask(t.id, {
|
|
30699
|
-
blocked_by: remaining,
|
|
30700
|
-
status: remaining.length === 0 ? "ready" : "blocked"
|
|
30701
|
-
});
|
|
30702
|
-
}
|
|
30703
|
-
if (t.parent_id === taskId) {
|
|
30704
|
-
updateTask(t.id, { parent_id: null });
|
|
30705
|
-
}
|
|
30706
|
-
}
|
|
30707
|
-
db.run("DELETE FROM events WHERE task_id = ?", [taskId]);
|
|
30708
|
-
db.run("UPDATE agents SET current_task = NULL WHERE current_task = ?", [taskId]);
|
|
30709
|
-
db.run("DELETE FROM tasks WHERE id = ?", [taskId]);
|
|
30836
|
+
doDelete();
|
|
30710
30837
|
return { success: true };
|
|
30711
30838
|
}
|
|
30712
30839
|
|
|
@@ -41096,7 +41223,7 @@ var claimCommand = new Command("claim").description("Claim task(s) to work on th
|
|
|
41096
41223
|
requireInitialized();
|
|
41097
41224
|
const json = isJsonMode(opts.json);
|
|
41098
41225
|
const agent = requireAgentId(opts.agent, "claim");
|
|
41099
|
-
const allTaskIds =
|
|
41226
|
+
const allTaskIds = getAllTaskIds();
|
|
41100
41227
|
const resolvedIds = resolveTaskIds(taskIds.map((id) => id.toUpperCase()), allTaskIds);
|
|
41101
41228
|
if (resolvedIds.length === 0) {
|
|
41102
41229
|
const patternUsed = taskIds.some((id) => isPattern(id));
|
|
@@ -41143,7 +41270,7 @@ var startCommand = new Command("start").description("Start working on task(s) (t
|
|
|
41143
41270
|
requireInitialized();
|
|
41144
41271
|
const json = isJsonMode(opts.json);
|
|
41145
41272
|
const agent = requireAgentId(opts.agent, "start");
|
|
41146
|
-
const allTaskIds =
|
|
41273
|
+
const allTaskIds = getAllTaskIds();
|
|
41147
41274
|
const resolvedIds = resolveTaskIds(taskIds.map((id) => id.toUpperCase()), allTaskIds);
|
|
41148
41275
|
if (resolvedIds.length === 0) {
|
|
41149
41276
|
const patternUsed = taskIds.some((id) => isPattern(id));
|
|
@@ -41247,7 +41374,7 @@ var doneCommand = new Command("done").description("Mark task(s) as complete (all
|
|
|
41247
41374
|
requireInitialized();
|
|
41248
41375
|
const json = isJsonMode(opts.json);
|
|
41249
41376
|
const agent = opts.agent ?? process.env.AITASKS_AGENT_ID;
|
|
41250
|
-
const allTaskIds =
|
|
41377
|
+
const allTaskIds = getAllTaskIds();
|
|
41251
41378
|
const resolvedIds = resolveTaskIds(taskIds.map((id) => id.toUpperCase()), allTaskIds);
|
|
41252
41379
|
if (resolvedIds.length === 0) {
|
|
41253
41380
|
const patternUsed = taskIds.some((id) => isPattern(id));
|
|
@@ -41343,7 +41470,7 @@ var reviewCommand = new Command("review").description("Request human review for
|
|
|
41343
41470
|
requireInitialized();
|
|
41344
41471
|
const json = isJsonMode(opts.json);
|
|
41345
41472
|
const agent = requireAgentId(opts.agent, "review");
|
|
41346
|
-
const allTaskIds =
|
|
41473
|
+
const allTaskIds = getAllTaskIds();
|
|
41347
41474
|
const resolvedIds = resolveTaskIds(taskIds.map((id) => id.toUpperCase()), allTaskIds);
|
|
41348
41475
|
if (resolvedIds.length === 0) {
|
|
41349
41476
|
const patternUsed = taskIds.some((id) => isPattern(id));
|
|
@@ -42085,7 +42212,20 @@ var TreeBoardComponent = ({ getTasks }) => {
|
|
|
42085
42212
|
const newStatus = statusMap[input];
|
|
42086
42213
|
if (newStatus && selectedTask) {
|
|
42087
42214
|
if (newStatus === "done") {
|
|
42088
|
-
|
|
42215
|
+
let { error, unchecked } = completeTask(selectedTask.id);
|
|
42216
|
+
if (error && unchecked && unchecked.length > 0) {
|
|
42217
|
+
const task = getTask(selectedTask.id);
|
|
42218
|
+
if (task) {
|
|
42219
|
+
for (let i = 0;i < task.acceptance_criteria.length; i++) {
|
|
42220
|
+
const alreadyChecked = task.test_results.some((r2) => r2.index === i);
|
|
42221
|
+
if (!alreadyChecked) {
|
|
42222
|
+
checkCriterion(selectedTask.id, i, "auto-verified (moved to Done on board)", "human");
|
|
42223
|
+
}
|
|
42224
|
+
}
|
|
42225
|
+
const retry = completeTask(selectedTask.id);
|
|
42226
|
+
error = retry.error;
|
|
42227
|
+
}
|
|
42228
|
+
}
|
|
42089
42229
|
if (error) {
|
|
42090
42230
|
setMoveError(error.split(`
|
|
42091
42231
|
`)[0] ?? error);
|
|
@@ -42996,7 +43136,7 @@ var searchCommand = new Command("search").description("Full-text search across t
|
|
|
42996
43136
|
}
|
|
42997
43137
|
const query = queryParts.join(" ").toLowerCase();
|
|
42998
43138
|
const searchTerms = query.split(/\s+/).filter(Boolean);
|
|
42999
|
-
const allTasks =
|
|
43139
|
+
const allTasks = searchTasks(searchTerms, opts.status);
|
|
43000
43140
|
const results = allTasks.filter((task) => matchesSearch(task, searchTerms));
|
|
43001
43141
|
if (results.length === 0) {
|
|
43002
43142
|
if (json)
|
|
@@ -43253,7 +43393,7 @@ var deleteCommand = new Command("delete").description("Delete task(s) - does not
|
|
|
43253
43393
|
requireInitialized();
|
|
43254
43394
|
const json = isJsonMode(opts.json);
|
|
43255
43395
|
const agent = opts.agent || process.env.AITASKS_AGENT_ID;
|
|
43256
|
-
const allTaskIds =
|
|
43396
|
+
const allTaskIds = getAllTaskIds();
|
|
43257
43397
|
const resolvedIds = expandPatterns(taskIds.map((id) => id.toUpperCase()), allTaskIds);
|
|
43258
43398
|
if (resolvedIds.length === 0) {
|
|
43259
43399
|
exitError("No tasks match the provided IDs/patterns", json);
|
|
@@ -43374,4 +43514,4 @@ program2.parseAsync(process.argv).catch((err) => {
|
|
|
43374
43514
|
process.exit(1);
|
|
43375
43515
|
});
|
|
43376
43516
|
|
|
43377
|
-
//# debugId=
|
|
43517
|
+
//# debugId=55B7B23CFF99871064756E2164756E21
|