aitasks 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +88 -40
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -140,7 +140,8 @@ aitasks create \
140
140
  --ac "Returns 200" \ # Acceptance criterion (repeatable, at least one required)
141
141
  --priority high \ # critical | high | medium | low
142
142
  --type feature \ # feature | bug | chore | spike
143
- --parent TASK-001 # Parent task ID (optional)
143
+ --parent TASK-001 \ # Parent task ID (optional)
144
+ --agent $AITASKS_AGENT_ID # Agent creating the task (logged in event history)
144
145
  ```
145
146
 
146
147
  ---
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.3.2",
1893
+ version: "1.3.4",
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 = 2;
29074
+ var SCHEMA_VERSION = 3;
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;
@@ -29080,6 +29080,11 @@ function runMigrations(db) {
29080
29080
  db.exec(`INSERT OR IGNORE INTO meta (key, value) VALUES ('review_required', 'false')`);
29081
29081
  db.exec(`UPDATE meta SET value = '2' WHERE key = 'schema_version'`);
29082
29082
  }
29083
+ if (current < 3) {
29084
+ db.exec(`ALTER TABLE agents ADD COLUMN first_seen INTEGER`);
29085
+ db.exec(`UPDATE agents SET first_seen = last_seen WHERE first_seen IS NULL`);
29086
+ db.exec(`UPDATE meta SET value = '3' WHERE key = 'schema_version'`);
29087
+ }
29083
29088
  }
29084
29089
  function initializeSchema(db) {
29085
29090
  db.exec(`
@@ -29111,6 +29116,7 @@ function initializeSchema(db) {
29111
29116
 
29112
29117
  CREATE TABLE IF NOT EXISTS agents (
29113
29118
  id TEXT PRIMARY KEY,
29119
+ first_seen INTEGER NOT NULL,
29114
29120
  last_seen INTEGER NOT NULL,
29115
29121
  current_task TEXT REFERENCES tasks(id) ON DELETE SET NULL
29116
29122
  );
@@ -29383,7 +29389,7 @@ Always note:
29383
29389
 
29384
29390
  Creating subtasks:
29385
29391
  \`\`\`bash
29386
- aitasks create --title "Write unit tests for auth" --desc "Add unit tests covering all auth edge cases" --ac "All tests pass" --ac "Coverage \u2265 90%" --parent TASK-001 --priority high --type chore
29392
+ aitasks create --title "Write unit tests for auth" --desc "Add unit tests covering all auth edge cases" --ac "All tests pass" --ac "Coverage \u2265 90%" --parent TASK-001 --priority high --type chore --agent $AITASKS_AGENT_ID
29387
29393
  \`\`\`
29388
29394
 
29389
29395
  If you discover your task is blocked by something:
@@ -29420,17 +29426,22 @@ ${reviewRequired ? `3. Submit for review \u2014 you CANNOT mark done directly:
29420
29426
  \`\`\`bash
29421
29427
  aitasks review TASK-001 --agent $AITASKS_AGENT_ID
29422
29428
  \`\`\`
29429
+ **STOP HERE. Do not run \`aitasks done\` yourself.** The system will block self-approval.
29430
+
29431
+ 4. **IMMEDIATELY spawn a review sub-agent with a DIFFERENT agent ID** to inspect the implementation.
29432
+ The review sub-agent must first register itself, then approve or reject:
29433
+ \`\`\`bash
29434
+ # Review sub-agent registers itself
29435
+ aitasks heartbeat --agent <review-agent-id>
29423
29436
 
29424
- 4. **IMMEDIATELY spawn a review sub-agent** \u2014 do NOT move to other tasks until review resolves.
29425
- The task is still incomplete. The review sub-agent must either:
29426
- - **Approve** (moves task to done):
29427
- \`\`\`bash
29428
- aitasks done TASK-001 --agent <review-agent-id>
29429
- \`\`\`
29430
- - **Reject** (sends task back to in_progress with feedback):
29431
- \`\`\`bash
29432
- aitasks reject TASK-001 --reason "<specific feedback>" --agent <review-agent-id>
29433
- \`\`\`
29437
+ # Approve (moves task to done):
29438
+ aitasks done TASK-001 --agent <review-agent-id>
29439
+
29440
+ # OR reject (sends task back to in_progress with feedback):
29441
+ aitasks reject TASK-001 --reason "<specific feedback>" --agent <review-agent-id>
29442
+ \`\`\`
29443
+ The task is still incomplete until the review agent approves it.
29444
+ **The system will block approval from any agent that has not registered or that submitted the review itself.**
29434
29445
 
29435
29446
  5. If rejected: address the feedback, re-check criteria, and repeat from step 3.
29436
29447
 
@@ -29478,8 +29489,8 @@ aitasks unclaim TASK-001 --agent $AITASKS_AGENT_ID --reason "Blocked on missing
29478
29489
  5. Add implementation notes continuously, not just at the end.
29479
29490
  6. If a task needs splitting, create subtasks BEFORE marking parent done.
29480
29491
  7. Your evidence strings must be concrete and verifiable \u2014 not vague affirmations.
29481
- 8. Always provide --desc and at least one --ac when creating a task. Both are required.${reviewRequired ? `
29482
- 9. NEVER move a task to done directly. Always submit for review first with \`aitasks review\`, then IMMEDIATELY spawn a review sub-agent. Do NOT work on other tasks until the review resolves. A task is only complete when its status is \`done\`.` : ""}
29492
+ 8. Always provide --desc, at least one --ac, and --agent when creating a task. All three are required.${reviewRequired ? `
29493
+ 9. NEVER move a task to done directly. Always submit for review first with \`aitasks review\`, then IMMEDIATELY spawn a review sub-agent with a DIFFERENT agent ID. Do NOT call \`aitasks done\` yourself after submitting for review \u2014 the system blocks self-approval.` : ""}
29483
29494
 
29484
29495
  ---
29485
29496
 
@@ -29491,7 +29502,7 @@ aitasks list [--status <s>] [--json] List tasks
29491
29502
  aitasks show <id> Full task detail (includes time tracking)
29492
29503
  aitasks search <query> Search titles, descriptions, notes
29493
29504
  aitasks deps <id> Show dependency tree
29494
- aitasks create --title <t> --desc <d> --ac <c> [--ac <c> ...] Create a task
29505
+ aitasks create --title <t> --desc <d> --ac <c> [--ac <c> ...] --agent <id> Create a task
29495
29506
  aitasks claim <id...> --agent <id> Claim task(s) - supports patterns like TASK-0*
29496
29507
  aitasks start <id...> --agent <id> Begin work on task(s)
29497
29508
  aitasks note <id> <text> --agent <id> Add implementation note
@@ -30318,7 +30329,7 @@ function createTask(data) {
30318
30329
  now,
30319
30330
  JSON.stringify(data.metadata ?? {})
30320
30331
  ]);
30321
- logEvent({ task_id: id, event_type: "created", payload: { title: data.title } });
30332
+ logEvent({ task_id: id, agent_id: data.created_by, event_type: "created", payload: { title: data.title } });
30322
30333
  return getTask(id);
30323
30334
  }
30324
30335
  var JSON_FIELDS = new Set([
@@ -30404,9 +30415,9 @@ function claimTask(taskId, agentId) {
30404
30415
  error: `Task is blocked by: ${task.blocked_by.join(", ")}. Complete those first.`
30405
30416
  };
30406
30417
  }
30407
- db.run(`INSERT INTO agents (id, last_seen, current_task)
30408
- VALUES (?, ?, ?)
30409
- ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen, current_task = excluded.current_task`, [agentId, Date.now(), taskId]);
30418
+ db.run(`INSERT INTO agents (id, first_seen, last_seen, current_task)
30419
+ VALUES (?, ?, ?, ?)
30420
+ ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen, current_task = excluded.current_task`, [agentId, Date.now(), Date.now(), taskId]);
30410
30421
  const updated = updateTask(taskId, { assigned_to: agentId, status: "ready" });
30411
30422
  logEvent({ task_id: taskId, agent_id: agentId, event_type: "claimed", payload: {} });
30412
30423
  return { task: updated };
@@ -30422,9 +30433,9 @@ function startTask(taskId, agentId) {
30422
30433
  }
30423
30434
  const db = getDb();
30424
30435
  if (!task.assigned_to) {
30425
- db.run(`INSERT INTO agents (id, last_seen, current_task)
30426
- VALUES (?, ?, ?)
30427
- ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen, current_task = excluded.current_task`, [agentId, Date.now(), taskId]);
30436
+ db.run(`INSERT INTO agents (id, first_seen, last_seen, current_task)
30437
+ VALUES (?, ?, ?, ?)
30438
+ ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen, current_task = excluded.current_task`, [agentId, Date.now(), Date.now(), taskId]);
30428
30439
  }
30429
30440
  const updated = updateTask(taskId, {
30430
30441
  assigned_to: agentId,
@@ -30448,16 +30459,45 @@ function completeTask(taskId, agentId) {
30448
30459
  if (unchecked.length > 0) {
30449
30460
  return { task: null, error: "Not all acceptance criteria are verified", unchecked };
30450
30461
  }
30451
- if (getReviewRequired() && task.status !== "review") {
30452
- return {
30453
- task: null,
30454
- error: `Review required: submit this task for review first.
30462
+ if (getReviewRequired()) {
30463
+ if (task.status !== "review") {
30464
+ return {
30465
+ task: null,
30466
+ error: `Review required: submit this task for review first.
30455
30467
  ` + ` 1. aitasks review <taskId> --agent $AITASKS_AGENT_ID
30456
30468
  ` + ` 2. Spawn a review sub-agent to inspect the work
30457
30469
  ` + ` 3. Review agent approves: aitasks done <taskId> --agent <review-agent-id>
30458
30470
  ` + ` Review agent rejects: aitasks reject <taskId> --reason "<feedback>"
30459
30471
  ` + " Tasks cannot be moved to Done without a passing review."
30460
- };
30472
+ };
30473
+ }
30474
+ if (task.assigned_to && task.assigned_to === agentId) {
30475
+ return {
30476
+ task: null,
30477
+ error: `Self-approval blocked: ${agentId} is the implementing agent and cannot approve their own review.
30478
+ ` + ` A separate review sub-agent must run: aitasks done ${taskId} --agent <review-agent-id>`
30479
+ };
30480
+ }
30481
+ const events = getTaskEvents(taskId);
30482
+ const reviewEvent = [...events].reverse().find((e2) => e2.event_type === "review_requested");
30483
+ if (reviewEvent?.agent_id && reviewEvent.agent_id === agentId) {
30484
+ return {
30485
+ task: null,
30486
+ error: `Self-approval blocked: ${agentId} submitted this task for review and cannot also approve it.
30487
+ ` + ` A separate review sub-agent must run: aitasks done ${taskId} --agent <review-agent-id>`
30488
+ };
30489
+ }
30490
+ const reviewerRow = db.query(`SELECT first_seen FROM agents WHERE id = ?`).get(agentId);
30491
+ const reviewSubmittedAt = reviewEvent?.created_at ?? 0;
30492
+ if (!reviewerRow || reviewerRow.first_seen > reviewSubmittedAt) {
30493
+ return {
30494
+ task: null,
30495
+ error: `Review agent '${agentId}' was not active before this review was submitted.
30496
+ ` + ` A real review sub-agent must be independently spawned \u2014 it cannot be registered
30497
+ ` + ` moments before approving. The reviewer must have prior activity in the system
30498
+ ` + ` before the review was submitted.`
30499
+ };
30500
+ }
30461
30501
  }
30462
30502
  const updated = updateTask(taskId, { status: "done", completed_at: Date.now() });
30463
30503
  logEvent({ task_id: taskId, agent_id: agentId, event_type: "completed", payload: {} });
@@ -30589,9 +30629,9 @@ function unclaimTask(taskId, agentId, reason) {
30589
30629
  }
30590
30630
  function heartbeat(agentId, taskId) {
30591
30631
  const db = getDb();
30592
- db.run(`INSERT INTO agents (id, last_seen, current_task)
30593
- VALUES (?, ?, ?)
30594
- ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen`, [agentId, Date.now(), taskId ?? null]);
30632
+ db.run(`INSERT INTO agents (id, first_seen, last_seen, current_task)
30633
+ VALUES (?, ?, ?, ?)
30634
+ ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen`, [agentId, Date.now(), Date.now(), taskId ?? null]);
30595
30635
  }
30596
30636
  function listAgents() {
30597
30637
  const db = getDb();
@@ -30689,7 +30729,7 @@ function exitError(msg, json) {
30689
30729
  }
30690
30730
 
30691
30731
  // src/commands/create.ts
30692
- var createCommand2 = new Command("create").description("Create a new task").option("-t, --title <title>", "Task title").option("-d, --desc <description>", "Task description").option("-a, --ac <criterion>", "Acceptance criterion (repeatable)", collect, []).option("-p, --priority <priority>", "Priority: critical|high|medium|low", "medium").option("--type <type>", "Type: feature|bug|chore|spike", "feature").option("--parent <taskId>", "Parent task ID for subtasks").option("--json", "Output as JSON").action(async (opts) => {
30732
+ var createCommand2 = new Command("create").description("Create a new task").option("-t, --title <title>", "Task title").option("-d, --desc <description>", "Task description").option("-a, --ac <criterion>", "Acceptance criterion (repeatable)", collect, []).option("-p, --priority <priority>", "Priority: critical|high|medium|low", "medium").option("--type <type>", "Type: feature|bug|chore|spike", "feature").option("--parent <taskId>", "Parent task ID for subtasks").option("--agent <agentId>", "Agent ID creating this task (or set AITASKS_AGENT_ID)").option("--json", "Output as JSON").action(async (opts) => {
30693
30733
  requireInitialized();
30694
30734
  const json = isJsonMode(opts.json);
30695
30735
  if (opts.title) {
@@ -30714,13 +30754,15 @@ var createCommand2 = new Command("create").description("Create a new task").opti
30714
30754
  process.exit(1);
30715
30755
  }
30716
30756
  }
30757
+ const aid = agentId(opts.agent) ?? undefined;
30717
30758
  const task2 = createTask({
30718
30759
  title: opts.title,
30719
30760
  description: opts.desc ?? "",
30720
30761
  acceptance_criteria: opts.ac,
30721
30762
  priority: opts.priority,
30722
30763
  type: opts.type,
30723
- parent_id: opts.parent
30764
+ parent_id: opts.parent,
30765
+ created_by: aid
30724
30766
  });
30725
30767
  if (json)
30726
30768
  return jsonOut(true, task2);
@@ -41322,10 +41364,10 @@ var reviewCommand = new Command("review").description("Request human review for
41322
41364
  id: taskId,
41323
41365
  success: true,
41324
41366
  status: "review",
41325
- next_action: "REQUIRED: Spawn a review sub-agent immediately. This task is NOT complete until the review agent approves it.",
41367
+ next_action: `REQUIRED: Spawn a review sub-agent immediately with a DIFFERENT agent ID (e.g. review-${taskId.toLowerCase()}). Do NOT call aitasks done yourself \u2014 the system will block self-approval.`,
41326
41368
  review_commands: {
41327
- approve: `aitasks done ${taskId} --agent <review-agent-id>`,
41328
- reject: `aitasks reject ${taskId} --reason "<specific feedback>" --agent <review-agent-id>`
41369
+ approve: `aitasks done ${taskId} --agent review-${taskId.toLowerCase()}`,
41370
+ reject: `aitasks reject ${taskId} --reason "<specific feedback>" --agent review-${taskId.toLowerCase()}`
41329
41371
  }
41330
41372
  });
41331
41373
  } else {
@@ -41340,10 +41382,16 @@ var reviewCommand = new Command("review").description("Request human review for
41340
41382
  console.log(` You MUST ${source_default.bold("immediately spawn a review sub-agent")} to inspect the implementation.`);
41341
41383
  console.log(source_default.dim(" The task remains incomplete until the review agent moves it to done."));
41342
41384
  console.log("");
41385
+ console.log(source_default.yellow(" \u2716 ") + source_default.bold("STOP \u2014 do NOT run `aitasks done` yourself."));
41386
+ console.log(source_default.dim(" The system will block self-approval. A separate agent must do the review."));
41387
+ console.log("");
41343
41388
  console.log(source_default.dim(" Review sub-agent steps:"));
41344
- console.log(source_default.dim(` 1. Examine implementation and verify all acceptance criteria`));
41345
- console.log(source_default.dim(` 2. Approve: aitasks done ${task.id} --agent <review-agent-id>`));
41346
- console.log(source_default.dim(` Reject: aitasks reject ${task.id} --reason "<feedback>" --agent <review-agent-id>`));
41389
+ console.log(source_default.dim(` 1. Spawn a sub-agent with a unique agent ID, e.g. review-${task.id.toLowerCase()}`));
41390
+ console.log(source_default.dim(` 2. Sub-agent registers: aitasks heartbeat --agent review-${task.id.toLowerCase()}`));
41391
+ console.log(source_default.dim(` 3. Sub-agent examines implementation and verifies all acceptance criteria`));
41392
+ console.log(source_default.dim(` 4. Approve: aitasks done ${task.id} --agent review-${task.id.toLowerCase()}`));
41393
+ console.log(source_default.dim(` Reject: aitasks reject ${task.id} --reason "<feedback>" --agent review-${task.id.toLowerCase()}`));
41394
+ console.log(source_default.dim(` The system will reject approval from unregistered or self-approving agents.`));
41347
41395
  } else {
41348
41396
  console.log(source_default.dim(` Approve: aitasks done ${task.id} --agent <review-agent-id>`));
41349
41397
  console.log(source_default.dim(` Reject: aitasks reject ${task.id} --reason "<feedback>"`));
@@ -43280,4 +43328,4 @@ program2.parseAsync(process.argv).catch((err) => {
43280
43328
  process.exit(1);
43281
43329
  });
43282
43330
 
43283
- //# debugId=3AB57D11F115EBBB64756E2164756E21
43331
+ //# debugId=6CE8123AAE8C303564756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aitasks",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "CLI task management tool built for AI agents",
5
5
  "type": "module",
6
6
  "bin": {