aitasks 1.3.0 → 1.3.1

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 +44 -5
  2. package/dist/index.js +135 -48
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -72,6 +72,7 @@ export AITASKS_JSON=true
72
72
  | Command | Description |
73
73
  |---|---|
74
74
  | `aitasks init` | Initialize a task database in the current project |
75
+ | `aitasks init --with-review` | Initialize with review enforcement (agents cannot mark done without a passing review) |
75
76
  | `aitasks onboard` | Print or inject agent protocol instructions into CLAUDE.md / AGENTS.md |
76
77
 
77
78
  ### Task Discovery
@@ -79,7 +80,7 @@ export AITASKS_JSON=true
79
80
  | Command | Description |
80
81
  |---|---|
81
82
  | `aitasks list` | List all tasks, sorted by priority |
82
- | `aitasks list --status ready` | Filter by status (`ready`, `in_progress`, `done`, `blocked`, `needs_review`) |
83
+ | `aitasks list --status ready` | Filter by status (`ready`, `in_progress`, `blocked`, `review`, `done`) |
83
84
  | `aitasks next` | Show the highest-priority unblocked ready task |
84
85
  | `aitasks next --claim --agent <id>` | Auto-claim and start the best task |
85
86
  | `aitasks show <id>` | Full detail on a specific task (includes time tracking) |
@@ -96,9 +97,9 @@ export AITASKS_JSON=true
96
97
  | `aitasks start <id...> --agent <id>` | Begin active work on task(s) |
97
98
  | `aitasks note <id> <text>` | Add an implementation note |
98
99
  | `aitasks check <id> <n> --evidence <text>` | Verify acceptance criterion n |
99
- | `aitasks done <id...> --agent <id>` | Mark task(s) complete (all criteria must be verified) |
100
- | `aitasks review <id...> --agent <id>` | Submit for human review |
101
- | `aitasks reject <id> --reason <text>` | Reject and send back to in_progress |
100
+ | `aitasks done <id...> --agent <id>` | Mark task(s) complete (all criteria must be verified; must be in `review` status if enforcement is on) |
101
+ | `aitasks review <id...> --agent <id>` | Submit task(s) for review (moves to `review` status) |
102
+ | `aitasks reject <id> --reason <text>` | Reject a task in review, send it back to `in_progress` with feedback |
102
103
  | `aitasks unclaim <id> --agent <id>` | Release a task back to the pool |
103
104
  | `aitasks undo <id>` | Undo the last action on a task |
104
105
  | `aitasks delete <id...>` | Delete task(s) - does not require claiming first |
@@ -151,12 +152,50 @@ When you run `aitasks init`, it automatically injects a full agent protocol into
151
152
  You can also inject/view it manually:
152
153
 
153
154
  ```sh
154
- aitasks onboard # print to stdout
155
+ aitasks onboard # print to stdout (reflects current review_required setting)
155
156
  aitasks onboard --append # append to detected agent file
156
157
  aitasks onboard --file MY.md # append to a specific file
157
158
  aitasks onboard --json # output as JSON string
158
159
  ```
159
160
 
161
+ The injected instructions automatically adapt to the project's review enforcement setting — if `--with-review` is enabled, agents receive the full review workflow (with `aitasks review`, sub-agent approval, and `aitasks reject`) instead of the standard completion flow.
162
+
163
+ ---
164
+
165
+ ## Review Enforcement
166
+
167
+ Enable a mandatory review gate so agents cannot mark tasks done without an explicit approval step:
168
+
169
+ ```sh
170
+ # Enable at init time
171
+ aitasks init --with-review
172
+
173
+ # Enable on an existing project (also replaces the agent instructions file with review-aware version)
174
+ aitasks init --with-review # safe to re-run; updates DB setting and rewrites agent file
175
+ ```
176
+
177
+ **How it works:**
178
+
179
+ 1. Agent completes work and checks all acceptance criteria
180
+ 2. Agent submits for review — task moves to `review` status:
181
+ ```sh
182
+ aitasks review TASK-001 --agent $AITASKS_AGENT_ID
183
+ ```
184
+ 3. A separate review sub-agent inspects the implementation and either:
185
+ - **Approves** — moves task to done:
186
+ ```sh
187
+ aitasks done TASK-001 --agent review-agent
188
+ ```
189
+ - **Rejects** — sends it back to `in_progress` with feedback:
190
+ ```sh
191
+ aitasks reject TASK-001 --reason "Missing error handling for 404 case" --agent review-agent
192
+ ```
193
+ 4. If rejected, the agent addresses feedback, re-verifies criteria, and repeats from step 2.
194
+
195
+ **Enforcement:** When review is required, `aitasks done` and `aitasks update --status done` both block if the task is not already in `review` status. The gate cannot be bypassed.
196
+
197
+ **Board:** Tasks in `review` status appear in the **IN PROGRESS** section with a `◈` magenta indicator so they're visually distinct from actively worked tasks.
198
+
160
199
  ---
161
200
 
162
201
  ## Data Storage
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.0",
1893
+ version: "1.3.1",
1894
1894
  description: "CLI task management tool built for AI agents",
1895
1895
  type: "module",
1896
1896
  bin: {
@@ -29071,7 +29071,16 @@ 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 = 1;
29074
+ var SCHEMA_VERSION = 2;
29075
+ function runMigrations(db) {
29076
+ const row = db.query(`SELECT value FROM meta WHERE key = 'schema_version'`).get();
29077
+ const current = row ? parseInt(row.value, 10) : 1;
29078
+ if (current < 2) {
29079
+ db.exec(`UPDATE tasks SET status = 'review' WHERE status = 'needs_review'`);
29080
+ db.exec(`INSERT OR IGNORE INTO meta (key, value) VALUES ('review_required', 'false')`);
29081
+ db.exec(`UPDATE meta SET value = '2' WHERE key = 'schema_version'`);
29082
+ }
29083
+ }
29075
29084
  function initializeSchema(db) {
29076
29085
  db.exec(`
29077
29086
  CREATE TABLE IF NOT EXISTS meta (
@@ -29126,6 +29135,7 @@ function initializeSchema(db) {
29126
29135
  insert.run("last_task_number", "0");
29127
29136
  insert.run("schema_version", String(SCHEMA_VERSION));
29128
29137
  insert.run("initialized_at", String(Date.now()));
29138
+ insert.run("review_required", "false");
29129
29139
  }
29130
29140
 
29131
29141
  // src/db/backup.ts
@@ -29240,10 +29250,20 @@ function getDb() {
29240
29250
  const db = new Database(dbPath);
29241
29251
  applyPragmas(db);
29242
29252
  checkIntegrity(db, taskieDir);
29253
+ runMigrations(db);
29243
29254
  registerCleanup();
29244
29255
  _db = db;
29245
29256
  return db;
29246
29257
  }
29258
+ function getReviewRequired() {
29259
+ const db = getDb();
29260
+ const row = db.query(`SELECT value FROM meta WHERE key = 'review_required'`).get();
29261
+ return row?.value === "true";
29262
+ }
29263
+ function setReviewRequired(enabled) {
29264
+ const db = getDb();
29265
+ db.run(`INSERT OR REPLACE INTO meta (key, value) VALUES ('review_required', ?)`, [enabled ? "true" : "false"]);
29266
+ }
29247
29267
  function createFreshDb(taskieDir) {
29248
29268
  const dbPath = join3(taskieDir, "db.sqlite");
29249
29269
  const db = new Database(dbPath);
@@ -29265,7 +29285,8 @@ import { join as join4 } from "path";
29265
29285
  // src/utils/instructions.ts
29266
29286
  var INSTRUCTIONS_START_MARKER = "<!-- aitasks:instructions -->";
29267
29287
  var INSTRUCTIONS_END_MARKER = "<!-- aitasks:instructions:end -->";
29268
- function getAgentInstructions(version) {
29288
+ function getAgentInstructions(version, opts = {}) {
29289
+ const reviewRequired = opts.reviewRequired ?? false;
29269
29290
  return `${INSTRUCTIONS_START_MARKER}
29270
29291
 
29271
29292
  ## AITasks \u2014 Agent Task Protocol (v${version})
@@ -29372,7 +29393,7 @@ aitasks deps TASK-001 # Shows what this task is blocked by and what it blocks
29372
29393
 
29373
29394
  ---
29374
29395
 
29375
- ### Completing a Task
29396
+ ### Completing a Task${reviewRequired ? " \u26A0\uFE0F REVIEW REQUIRED" : ""}
29376
29397
 
29377
29398
  You MUST verify every acceptance criterion before marking done.
29378
29399
 
@@ -29388,15 +29409,30 @@ You MUST verify every acceptance criterion before marking done.
29388
29409
  aitasks check TASK-001 2 --evidence "integration test suite passes: 12/12 green"
29389
29410
  \`\`\`
29390
29411
 
29391
- 3. Mark done (will FAIL if any criterion is unchecked):
29412
+ ${reviewRequired ? `3. Submit for review \u2014 you CANNOT mark done directly:
29413
+ \`\`\`bash
29414
+ aitasks review TASK-001 --agent $AITASKS_AGENT_ID
29415
+ \`\`\`
29416
+
29417
+ 4. Spawn a review sub-agent to inspect the implementation and either:
29418
+ - **Approve** (moves task to done):
29419
+ \`\`\`bash
29420
+ aitasks done TASK-001 --agent <review-agent-id>
29421
+ \`\`\`
29422
+ - **Reject** (sends task back to in_progress with feedback):
29423
+ \`\`\`bash
29424
+ aitasks reject TASK-001 --reason "<specific feedback>" --agent <review-agent-id>
29425
+ \`\`\`
29426
+
29427
+ 5. If rejected: address the feedback, re-check criteria, and repeat from step 3.` : `3. Mark done (will FAIL if any criterion is unchecked):
29392
29428
  \`\`\`bash
29393
29429
  aitasks done TASK-001 --agent $AITASKS_AGENT_ID
29394
29430
  \`\`\`
29395
29431
 
29396
- If human review is needed instead:
29432
+ If review is needed:
29397
29433
  \`\`\`bash
29398
29434
  aitasks review TASK-001 --agent $AITASKS_AGENT_ID
29399
- \`\`\`
29435
+ \`\`\``}
29400
29436
 
29401
29437
  ---
29402
29438
 
@@ -29434,7 +29470,8 @@ aitasks unclaim TASK-001 --agent $AITASKS_AGENT_ID --reason "Blocked on missing
29434
29470
  4. Add implementation notes continuously, not just at the end.
29435
29471
  5. If a task needs splitting, create subtasks BEFORE marking parent done.
29436
29472
  6. Your evidence strings must be concrete and verifiable \u2014 not vague affirmations.
29437
- 7. Always provide --desc and at least one --ac when creating a task. Both are required.
29473
+ 7. Always provide --desc and at least one --ac when creating a task. Both are required.${reviewRequired ? `
29474
+ 8. NEVER move a task to done directly. Always submit for review first with \`aitasks review\`, then spawn a review sub-agent to approve or reject. Tasks cannot be marked done without a passing review.` : ""}
29438
29475
 
29439
29476
  ---
29440
29477
 
@@ -29481,13 +29518,17 @@ function findExistingAgentFile(projectRoot) {
29481
29518
  }
29482
29519
  return null;
29483
29520
  }
29484
- function injectOrCreateAgentFile(projectRoot, version) {
29485
- const instructions = getAgentInstructions(version);
29521
+ function injectOrCreateAgentFile(projectRoot, version, reviewRequired = false, force = false) {
29522
+ const instructions = getAgentInstructions(version, { reviewRequired });
29486
29523
  const existing = findExistingAgentFile(projectRoot);
29487
29524
  if (existing) {
29488
29525
  const content = readFileSync2(existing, "utf8");
29489
29526
  if (instructionsAlreadyPresent(content)) {
29490
- return { filePath: existing, action: "skipped" };
29527
+ if (!force)
29528
+ return { filePath: existing, action: "skipped" };
29529
+ const replaced = replaceInstructionsBlock(content, instructions);
29530
+ writeFileSync2(existing, replaced, "utf8");
29531
+ return { filePath: existing, action: "appended" };
29491
29532
  }
29492
29533
  const separator = content.endsWith(`
29493
29534
  `) ? `
@@ -29503,8 +29544,25 @@ function injectOrCreateAgentFile(projectRoot, version) {
29503
29544
  `, "utf8");
29504
29545
  return { filePath: newPath, action: "created" };
29505
29546
  }
29506
- function appendToSpecificFile(filePath, version) {
29507
- const instructions = getAgentInstructions(version);
29547
+ function replaceInstructionsBlock(content, newInstructions) {
29548
+ const start = content.indexOf(INSTRUCTIONS_START_MARKER);
29549
+ const end = content.indexOf(INSTRUCTIONS_END_MARKER);
29550
+ if (start === -1 || end === -1)
29551
+ return content;
29552
+ const before = content.slice(0, start).replace(/\n+$/, "");
29553
+ const after = content.slice(end + INSTRUCTIONS_END_MARKER.length).replace(/^\n+/, "");
29554
+ const sep = before.length > 0 ? `
29555
+
29556
+ ` : "";
29557
+ const tail = after.length > 0 ? `
29558
+
29559
+ ` + after : `
29560
+ `;
29561
+ return before + sep + newInstructions + `
29562
+ ` + tail;
29563
+ }
29564
+ function appendToSpecificFile(filePath, version, reviewRequired = false) {
29565
+ const instructions = getAgentInstructions(version, { reviewRequired });
29508
29566
  if (!existsSync4(filePath)) {
29509
29567
  writeFileSync2(filePath, instructions + `
29510
29568
  `, "utf8");
@@ -29525,27 +29583,39 @@ function appendToSpecificFile(filePath, version) {
29525
29583
  }
29526
29584
 
29527
29585
  // src/commands/init.ts
29528
- var initCommand = new Command("init").description("Initialize AITasks in the current project").option("--skip-agent-file", "Skip injecting agent instructions into CLAUDE.md/AGENTS.md/GEMINI.md").action(async (opts) => {
29586
+ var initCommand = new Command("init").description("Initialize AITasks in the current project").option("--skip-agent-file", "Skip injecting agent instructions into CLAUDE.md/AGENTS.md/GEMINI.md").option("--with-review", "Enforce review gate: agents cannot mark tasks done without a passing review").action(async (opts) => {
29529
29587
  const root = findProjectRoot();
29530
29588
  const taskieDir = join5(root, ".aitasks");
29531
29589
  if (existsSync5(join5(taskieDir, "db.sqlite"))) {
29532
29590
  console.log(source_default.yellow(" AITasks is already initialized in this project."));
29533
29591
  console.log(source_default.dim(` DB: ${join5(taskieDir, "db.sqlite")}`));
29592
+ if (opts.withReview) {
29593
+ setReviewRequired(true);
29594
+ console.log(source_default.green(" \u2713") + " Review enforcement enabled.");
29595
+ }
29534
29596
  if (!opts.skipAgentFile) {
29535
- const result = injectOrCreateAgentFile(root, getVersion());
29597
+ const result = injectOrCreateAgentFile(root, getVersion(), !!opts.withReview, !!opts.withReview);
29536
29598
  printAgentFileResult(result);
29537
29599
  }
29538
29600
  return;
29539
29601
  }
29540
29602
  mkdirSync(taskieDir, { recursive: true });
29541
29603
  createFreshDb(taskieDir);
29604
+ if (opts.withReview) {
29605
+ setReviewRequired(true);
29606
+ }
29542
29607
  console.log("");
29543
29608
  console.log(source_default.green(" \u2713") + source_default.bold(" AITasks initialized"));
29544
29609
  console.log(source_default.dim(` Project root : ${root}`));
29545
29610
  console.log(source_default.dim(` Database : ${join5(taskieDir, "db.sqlite")}`));
29611
+ if (opts.withReview) {
29612
+ console.log(source_default.magenta(" \u25C8") + source_default.bold(" Review enforcement enabled"));
29613
+ console.log(source_default.dim(" Agents must submit tasks for review before marking done."));
29614
+ console.log(source_default.dim(" Use: aitasks review <id> \u2192 review sub-agent \u2192 aitasks done <id>"));
29615
+ }
29546
29616
  console.log("");
29547
29617
  if (!opts.skipAgentFile) {
29548
- const result = injectOrCreateAgentFile(root, getVersion());
29618
+ const result = injectOrCreateAgentFile(root, getVersion(), !!opts.withReview);
29549
29619
  printAgentFileResult(result);
29550
29620
  }
29551
29621
  console.log(source_default.dim(" Run `aitasks create` to add your first task."));
@@ -30156,7 +30226,7 @@ var PRIORITY_ORDER = `CASE priority
30156
30226
  WHEN 'low' THEN 3 END`;
30157
30227
  var STATUS_ORDER = `CASE status
30158
30228
  WHEN 'in_progress' THEN 0
30159
- WHEN 'needs_review' THEN 1
30229
+ WHEN 'review' THEN 1
30160
30230
  WHEN 'blocked' THEN 2
30161
30231
  WHEN 'ready' THEN 3
30162
30232
  WHEN 'backlog' THEN 4
@@ -30357,6 +30427,17 @@ function completeTask(taskId, agentId) {
30357
30427
  if (unchecked.length > 0) {
30358
30428
  return { task: null, error: "Not all acceptance criteria are verified", unchecked };
30359
30429
  }
30430
+ if (getReviewRequired() && task.status !== "review") {
30431
+ return {
30432
+ task: null,
30433
+ error: `Review required: submit this task for review first.
30434
+ ` + ` 1. aitasks review <taskId> --agent $AITASKS_AGENT_ID
30435
+ ` + ` 2. Spawn a review sub-agent to inspect the work
30436
+ ` + ` 3. Review agent approves: aitasks done <taskId> --agent <review-agent-id>
30437
+ ` + ` Review agent rejects: aitasks reject <taskId> --reason "<feedback>"
30438
+ ` + " Tasks cannot be moved to Done without a passing review."
30439
+ };
30440
+ }
30360
30441
  const updated = updateTask(taskId, { status: "done", completed_at: Date.now() });
30361
30442
  logEvent({ task_id: taskId, agent_id: agentId, event_type: "completed", payload: {} });
30362
30443
  const pendingRows = db.query(`SELECT id, blocked_by FROM tasks WHERE status != 'done'`).all();
@@ -30442,7 +30523,7 @@ function reviewTask(taskId, agentId) {
30442
30523
  if (task.status !== "in_progress") {
30443
30524
  return { task: null, error: "Task must be in_progress to request review" };
30444
30525
  }
30445
- const updated = updateTask(taskId, { status: "needs_review" });
30526
+ const updated = updateTask(taskId, { status: "review" });
30446
30527
  logEvent({ task_id: taskId, agent_id: agentId, event_type: "review_requested", payload: {} });
30447
30528
  return { task: updated };
30448
30529
  }
@@ -30450,8 +30531,8 @@ function rejectTask(taskId, reason, agentId) {
30450
30531
  const task = getTask(taskId);
30451
30532
  if (!task)
30452
30533
  return { task: null, error: "Task not found" };
30453
- if (task.status !== "needs_review") {
30454
- return { task: null, error: "Task must be in needs_review status to reject" };
30534
+ if (task.status !== "review") {
30535
+ return { task: null, error: "Task must be in review status to reject" };
30455
30536
  }
30456
30537
  updateTask(taskId, { status: "in_progress" });
30457
30538
  addImplementationNote(taskId, `REVIEW REJECTED: ${reason}`, agentId ?? "human");
@@ -38533,7 +38614,7 @@ var STATUS_ICON = {
38533
38614
  ready: "\u25CE",
38534
38615
  in_progress: "\u25B6",
38535
38616
  blocked: "\u2298",
38536
- needs_review: "\u25C8",
38617
+ review: "\u25C8",
38537
38618
  done: "\u2713"
38538
38619
  };
38539
38620
 
@@ -40114,7 +40195,7 @@ var STATUS_COLORS = {
40114
40195
  ready: "blue",
40115
40196
  in_progress: "yellow",
40116
40197
  blocked: "#FF5C5C",
40117
- needs_review: "magenta",
40198
+ review: "magenta",
40118
40199
  done: "green"
40119
40200
  };
40120
40201
  var PRIORITY_COLORS = {
@@ -40526,7 +40607,7 @@ var STATUS_COLORS2 = {
40526
40607
  ready: "blue",
40527
40608
  in_progress: "yellow",
40528
40609
  blocked: "#FF5C5C",
40529
- needs_review: "magenta",
40610
+ review: "magenta",
40530
40611
  done: "green"
40531
40612
  };
40532
40613
  var PRIORITY_COLORS2 = {
@@ -41214,8 +41295,9 @@ var reviewCommand = new Command("review").description("Request human review for
41214
41295
  if (!json) {
41215
41296
  console.log("");
41216
41297
  console.log(source_default.magenta(" \u25C8") + ` ${source_default.bold(task.id)} submitted for review`);
41217
- console.log(source_default.dim(` A human can approve with: aitasks done ${task.id}`));
41218
- console.log(source_default.dim(` Or reject with: aitasks reject ${task.id} --reason "<reason>"`));
41298
+ console.log(source_default.dim(` Spawn a review sub-agent to inspect the implementation.`));
41299
+ console.log(source_default.dim(` Approve: aitasks done ${task.id} --agent <review-agent-id>`));
41300
+ console.log(source_default.dim(` Reject: aitasks reject ${task.id} --reason "<feedback>"`));
41219
41301
  }
41220
41302
  }
41221
41303
  if (json) {
@@ -41364,6 +41446,9 @@ var updateCommand = new Command("update").description("Update task fields").argu
41364
41446
  if (Object.keys(changes).length === 0) {
41365
41447
  exitError("No changes specified. Use --help to see available options.", json);
41366
41448
  }
41449
+ if (opts.status === "done" && getReviewRequired() && task.status !== "review") {
41450
+ exitError("Review required: use `aitasks done` which enforces the review gate.\n" + " Submit for review first: aitasks review <taskId> --agent $AITASKS_AGENT_ID", json);
41451
+ }
41367
41452
  const updated = updateTask(id, changes);
41368
41453
  if (json)
41369
41454
  return jsonOut(true, updated);
@@ -41387,7 +41472,7 @@ var STATUS_COLORS3 = {
41387
41472
  ready: "blue",
41388
41473
  in_progress: "yellow",
41389
41474
  blocked: "#FF5C5C",
41390
- needs_review: "magenta",
41475
+ review: "magenta",
41391
41476
  done: "green"
41392
41477
  };
41393
41478
  var PRIORITY_COLORS3 = {
@@ -41398,11 +41483,11 @@ var PRIORITY_COLORS3 = {
41398
41483
  };
41399
41484
  function buildTree(tasks) {
41400
41485
  const sorted = [...tasks].sort((a2, b2) => a2.id.localeCompare(b2.id));
41401
- const inProgressIds = new Set(sorted.filter((t) => t.status === "in_progress").map((t) => t.id));
41486
+ const inProgressIds = new Set(sorted.filter((t) => t.status === "in_progress" || t.status === "review").map((t) => t.id));
41402
41487
  const doneIds = new Set(sorted.filter((t) => t.status === "done").map((t) => t.id));
41403
41488
  const shownIds = new Set;
41404
41489
  const items = [];
41405
- const ipCount = sorted.filter((t) => t.status === "in_progress").length;
41490
+ const ipCount = sorted.filter((t) => t.status === "in_progress" || t.status === "review").length;
41406
41491
  const doneCount = sorted.filter((t) => t.status === "done").length;
41407
41492
  function push(task, indent, isLastSibling, section, showHeader) {
41408
41493
  items.push({
@@ -41416,13 +41501,13 @@ function buildTree(tasks) {
41416
41501
  shownIds.add(task.id);
41417
41502
  }
41418
41503
  let firstIp = true;
41419
- for (const task of sorted.filter((t) => t.status === "in_progress" && !t.parent_id)) {
41504
+ for (const task of sorted.filter((t) => (t.status === "in_progress" || t.status === "review") && !t.parent_id)) {
41420
41505
  push(task, 0, false, "in_progress", firstIp);
41421
41506
  firstIp = false;
41422
41507
  const subs = sorted.filter((t) => t.parent_id === task.id);
41423
41508
  subs.forEach((sub, i) => push(sub, 1, i === subs.length - 1, "in_progress", false));
41424
41509
  }
41425
- for (const task of sorted.filter((t) => t.status === "in_progress" && !!t.parent_id)) {
41510
+ for (const task of sorted.filter((t) => (t.status === "in_progress" || t.status === "review") && !!t.parent_id)) {
41426
41511
  if (!shownIds.has(task.id)) {
41427
41512
  push(task, 0, false, "in_progress", firstIp);
41428
41513
  firstIp = false;
@@ -41666,7 +41751,7 @@ var RightPane = ({ task, width, height, scrollOffset, metricsRef }) => {
41666
41751
  }, i, true, undefined, this))
41667
41752
  }, undefined, false, undefined, this);
41668
41753
  };
41669
- var PICKER_STATUSES = ["backlog", "ready", "in_progress", "blocked", "needs_review", "done"];
41754
+ var PICKER_STATUSES = ["backlog", "ready", "in_progress", "blocked", "review", "done"];
41670
41755
  var StatusPicker = ({ task }) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
41671
41756
  flexDirection: "column",
41672
41757
  paddingLeft: 2,
@@ -41877,7 +41962,7 @@ var TreeBoardComponent = ({ getTasks }) => {
41877
41962
  "2": "ready",
41878
41963
  "3": "in_progress",
41879
41964
  "4": "blocked",
41880
- "5": "needs_review",
41965
+ "5": "review",
41881
41966
  "6": "done"
41882
41967
  };
41883
41968
  const newStatus = statusMap[input];
@@ -42274,10 +42359,10 @@ var STATUS_LABELS = {
42274
42359
  ready: "READY",
42275
42360
  in_progress: "IN PROGRESS",
42276
42361
  blocked: "BLOCKED",
42277
- needs_review: "NEEDS REVIEW",
42362
+ review: "REVIEW",
42278
42363
  done: "DONE"
42279
42364
  };
42280
- var ALL_STATUSES = ["in_progress", "needs_review", "ready", "blocked", "backlog", "done"];
42365
+ var ALL_STATUSES = ["in_progress", "review", "ready", "blocked", "backlog", "done"];
42281
42366
  var StaticCard = ({ task }) => {
42282
42367
  const priColor = PRIORITY_COLORS3[task.priority] ?? "gray";
42283
42368
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -42544,13 +42629,14 @@ var exportCommand = new Command("export").description("Export all task data").op
42544
42629
  // src/commands/onboard.ts
42545
42630
  var VERSION = "1.0.0";
42546
42631
  var onboardCommand = new Command("onboard").description("Print or inject agent protocol instructions").option("--append", "Auto-detect and append to CLAUDE.md/AGENTS.md/GEMINI.md").option("--file <path>", "Append to a specific file").option("--json", "Output the instructions as a JSON string").action((opts) => {
42547
- const instructions = getAgentInstructions(VERSION);
42632
+ const reviewRequired = isInitialized() ? getReviewRequired() : false;
42633
+ const instructions = getAgentInstructions(VERSION, { reviewRequired });
42548
42634
  if (opts.json) {
42549
42635
  console.log(JSON.stringify({ instructions }, null, 2));
42550
42636
  return;
42551
42637
  }
42552
42638
  if (opts.file) {
42553
- const result2 = appendToSpecificFile(opts.file, VERSION);
42639
+ const result2 = appendToSpecificFile(opts.file, VERSION, reviewRequired);
42554
42640
  const rel = opts.file.replace(process.cwd() + "/", "");
42555
42641
  switch (result2.action) {
42556
42642
  case "created":
@@ -42567,7 +42653,7 @@ var onboardCommand = new Command("onboard").description("Print or inject agent p
42567
42653
  }
42568
42654
  if (opts.append) {
42569
42655
  const root = findProjectRoot();
42570
- const result2 = injectOrCreateAgentFile(root, VERSION);
42656
+ const result2 = injectOrCreateAgentFile(root, VERSION, reviewRequired);
42571
42657
  const rel = result2.filePath.replace(process.cwd() + "/", "");
42572
42658
  switch (result2.action) {
42573
42659
  case "created":
@@ -42738,7 +42824,7 @@ function getStatusIcon(status) {
42738
42824
  ready: "\u25D0",
42739
42825
  in_progress: "\u25B6",
42740
42826
  blocked: "\u2298",
42741
- needs_review: "\u25C8",
42827
+ review: "\u25C8",
42742
42828
  done: "\u2713"
42743
42829
  };
42744
42830
  return icons[status] || "\u25CB";
@@ -42778,7 +42864,7 @@ var searchCommand = new Command("search").description("Full-text search across t
42778
42864
  const statusIcon = getStatusIcon2(task.status);
42779
42865
  const priColor = getPriorityColor(task.priority);
42780
42866
  console.log("");
42781
- console.log(`${source_default.bold.white(task.id)} ` + `${priColor(task.priority[0].toUpperCase())} ` + `${statusIcon} ` + highlightMatches(task.title, searchTerms));
42867
+ console.log(`${source_default.bold.white(task.id)} ` + `${priColor((task.priority[0] ?? "?").toUpperCase())} ` + `${statusIcon} ` + highlightMatches(task.title, searchTerms));
42782
42868
  const context = getSearchContext(task, searchTerms);
42783
42869
  if (context) {
42784
42870
  console.log(source_default.dim(` ${context}`));
@@ -42837,7 +42923,7 @@ function getStatusIcon2(status) {
42837
42923
  ready: source_default.blue("\u25D0"),
42838
42924
  in_progress: source_default.yellow("\u25B6"),
42839
42925
  blocked: source_default.hex("#FF5C5C")("\u2298"),
42840
- needs_review: source_default.magenta("\u25C8"),
42926
+ review: source_default.magenta("\u25C8"),
42841
42927
  done: source_default.green("\u2713")
42842
42928
  };
42843
42929
  return icons[status] || "\u25CB";
@@ -42927,25 +43013,26 @@ var undoCommand = new Command("undo").description("Undo the last action on a tas
42927
43013
  }
42928
43014
  });
42929
43015
  function undoComplete(task) {
43016
+ const newStatus = getReviewRequired() ? "review" : "in_progress";
42930
43017
  updateTask(task.id, {
42931
- status: "in_progress",
43018
+ status: newStatus,
42932
43019
  completed_at: null
42933
43020
  });
42934
43021
  return {
42935
43022
  success: true,
42936
- message: `${task.id} reverted from done to in_progress`,
42937
- data: { task: task.id, previousStatus: "done", newStatus: "in_progress" }
43023
+ message: `${task.id} reverted from done to ${newStatus}`,
43024
+ data: { task: task.id, previousStatus: "done", newStatus }
42938
43025
  };
42939
43026
  }
42940
43027
  function undoReviewRequested(task) {
42941
- if (task.status !== "needs_review") {
42942
- return { success: false, message: `${task.id} is not in needs_review status` };
43028
+ if (task.status !== "review") {
43029
+ return { success: false, message: `${task.id} is not in review status` };
42943
43030
  }
42944
43031
  updateTask(task.id, { status: "in_progress" });
42945
43032
  return {
42946
43033
  success: true,
42947
- message: `${task.id} reverted from needs_review to in_progress`,
42948
- data: { task: task.id, previousStatus: "needs_review", newStatus: "in_progress" }
43034
+ message: `${task.id} reverted from review to in_progress`,
43035
+ data: { task: task.id, previousStatus: "review", newStatus: "in_progress" }
42949
43036
  };
42950
43037
  }
42951
43038
  function undoStarted(task) {
@@ -43135,4 +43222,4 @@ program2.parseAsync(process.argv).catch((err) => {
43135
43222
  process.exit(1);
43136
43223
  });
43137
43224
 
43138
- //# debugId=26C003F11D02306964756E2164756E21
43225
+ //# debugId=B0EAB2DE4FE47CF564756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aitasks",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "CLI task management tool built for AI agents",
5
5
  "type": "module",
6
6
  "bin": {