@probelabs/probe 0.6.0-rc230 → 0.6.0-rc231

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.
@@ -2496,10 +2496,9 @@ ${extractGuidance}
2496
2496
  toolDefinitions += `${taskToolDefinition}\n`;
2497
2497
  }
2498
2498
 
2499
- // Always include attempt_completion (unless explicitly disabled in raw AI mode)
2500
- if (isToolAllowed('attempt_completion')) {
2501
- toolDefinitions += `${attemptCompletionToolDefinition}\n`;
2502
- }
2499
+ // Always include attempt_completion unconditionally - it's a completion signal, not a tool
2500
+ // This ensures agents can always complete their work, regardless of tool restrictions
2501
+ toolDefinitions += `${attemptCompletionToolDefinition}\n`;
2503
2502
 
2504
2503
  // Delegate tool (require both enableDelegate flag AND allowedTools permission)
2505
2504
  // Place after attempt_completion as it's an optional tool
@@ -3304,8 +3303,9 @@ Follow these instructions carefully:
3304
3303
  if (this.enableSkills && this.allowedTools.isEnabled('listSkills')) validTools.push('listSkills');
3305
3304
  if (this.enableSkills && this.allowedTools.isEnabled('useSkill')) validTools.push('useSkill');
3306
3305
  if (this.allowedTools.isEnabled('readImage')) validTools.push('readImage');
3307
- // Always allow attempt_completion - it's a completion signal, not a tool
3306
+ // Always allow attempt_completion in validTools - it's a completion signal, not a tool
3308
3307
  // This ensures agents can complete even when disableTools: true is set (fixes #333)
3308
+ // The tool DEFINITION may be hidden in raw AI mode, but we still need to recognize it
3309
3309
  validTools.push('attempt_completion');
3310
3310
 
3311
3311
  // Edit tools (require both allowEdit flag AND allowedTools permission)
@@ -9199,7 +9199,15 @@ function createTaskTool(options = {}) {
9199
9199
  });
9200
9200
  return `Error: Invalid task parameters - ${validation.error.message}`;
9201
9201
  }
9202
- const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
9202
+ const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
9203
+ let tasks = rawTasks;
9204
+ if (typeof rawTasks === "string") {
9205
+ try {
9206
+ tasks = JSON.parse(rawTasks);
9207
+ } catch (e) {
9208
+ return `Error: Invalid tasks JSON - ${e.message}`;
9209
+ }
9210
+ }
9203
9211
  switch (action) {
9204
9212
  case "create": {
9205
9213
  if (tasks && Array.isArray(tasks)) {
@@ -9374,7 +9382,8 @@ var init_taskTool = __esm({
9374
9382
  });
9375
9383
  taskSchema = external_exports.object({
9376
9384
  action: external_exports.enum(["create", "update", "complete", "delete", "list"]),
9377
- tasks: external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])).optional(),
9385
+ // Accept both array and JSON string (AI models sometimes serialize as string)
9386
+ tasks: external_exports.union([external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])), external_exports.string()]).optional(),
9378
9387
  id: external_exports.string().optional(),
9379
9388
  title: external_exports.string().optional(),
9380
9389
  description: external_exports.string().optional(),
@@ -9485,6 +9494,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
9485
9494
  **Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
9486
9495
  A single investigation with many steps is still ONE task, not many.
9487
9496
 
9497
+ ## Task Granularity
9498
+
9499
+ Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
9500
+ - "Fix 8 similar test files" \u2192 ONE task (same type of fix across files)
9501
+ - "Update API + tests + docs" \u2192 THREE tasks (different types of work)
9502
+ - "Implement feature in 5 files" \u2192 ONE task (single feature)
9503
+
9504
+ **Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
9505
+
9506
+ **Anti-patterns to avoid**:
9507
+ - One task per file \u274C
9508
+ - One task per function \u274C
9509
+ - One task per repository (when same type of work) \u274C
9510
+
9511
+ **Good patterns**:
9512
+ - One task per distinct deliverable \u2713
9513
+ - One task per phase (implement, test, document) \u2713
9514
+ - One task per different type of work \u2713
9515
+
9488
9516
  MODIFY TASKS when (during execution):
9489
9517
  - You discover the problem is more complex than expected \u2192 Add new tasks
9490
9518
  - A single task covers too much scope \u2192 Split into smaller tasks
@@ -55823,6 +55851,7 @@ var require_pattern = __commonJS({
55823
55851
  "use strict";
55824
55852
  Object.defineProperty(exports2, "__esModule", { value: true });
55825
55853
  var code_1 = require_code2();
55854
+ var util_1 = require_util3();
55826
55855
  var codegen_1 = require_codegen();
55827
55856
  var error = {
55828
55857
  message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`,
@@ -55835,10 +55864,18 @@ var require_pattern = __commonJS({
55835
55864
  $data: true,
55836
55865
  error,
55837
55866
  code(cxt) {
55838
- const { data, $data, schema, schemaCode, it } = cxt;
55867
+ const { gen, data, $data, schema, schemaCode, it } = cxt;
55839
55868
  const u = it.opts.unicodeRegExp ? "u" : "";
55840
- const regExp = $data ? (0, codegen_1._)`(new RegExp(${schemaCode}, ${u}))` : (0, code_1.usePattern)(cxt, schema);
55841
- cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data})`);
55869
+ if ($data) {
55870
+ const { regExp } = it.opts.code;
55871
+ const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp);
55872
+ const valid = gen.let("valid");
55873
+ gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false));
55874
+ cxt.fail$data((0, codegen_1._)`!${valid}`);
55875
+ } else {
55876
+ const regExp = (0, code_1.usePattern)(cxt, schema);
55877
+ cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data})`);
55878
+ }
55842
55879
  }
55843
55880
  };
55844
55881
  exports2.default = def;
@@ -72250,10 +72287,8 @@ Workspace: ${this.allowedFolders.join(", ")}`;
72250
72287
  toolDefinitions += `${taskToolDefinition}
72251
72288
  `;
72252
72289
  }
72253
- if (isToolAllowed("attempt_completion")) {
72254
- toolDefinitions += `${attemptCompletionToolDefinition}
72290
+ toolDefinitions += `${attemptCompletionToolDefinition}
72255
72291
  `;
72256
- }
72257
72292
  if (this.enableDelegate && isToolAllowed("delegate")) {
72258
72293
  toolDefinitions += `${delegateToolDefinition}
72259
72294
  `;
@@ -23,7 +23,8 @@ export const taskItemSchema = z.object({
23
23
  */
24
24
  export const taskSchema = z.object({
25
25
  action: z.enum(['create', 'update', 'complete', 'delete', 'list']),
26
- tasks: z.array(z.union([z.string(), taskItemSchema])).optional(),
26
+ // Accept both array and JSON string (AI models sometimes serialize as string)
27
+ tasks: z.union([z.array(z.union([z.string(), taskItemSchema])), z.string()]).optional(),
27
28
  id: z.string().optional(),
28
29
  title: z.string().optional(),
29
30
  description: z.string().optional(),
@@ -142,6 +143,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
142
143
  **Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
143
144
  A single investigation with many steps is still ONE task, not many.
144
145
 
146
+ ## Task Granularity
147
+
148
+ Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
149
+ - "Fix 8 similar test files" → ONE task (same type of fix across files)
150
+ - "Update API + tests + docs" → THREE tasks (different types of work)
151
+ - "Implement feature in 5 files" → ONE task (single feature)
152
+
153
+ **Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
154
+
155
+ **Anti-patterns to avoid**:
156
+ - One task per file ❌
157
+ - One task per function ❌
158
+ - One task per repository (when same type of work) ❌
159
+
160
+ **Good patterns**:
161
+ - One task per distinct deliverable ✓
162
+ - One task per phase (implement, test, document) ✓
163
+ - One task per different type of work ✓
164
+
145
165
  MODIFY TASKS when (during execution):
146
166
  - You discover the problem is more complex than expected → Add new tasks
147
167
  - A single task covers too much scope → Split into smaller tasks
@@ -314,7 +334,17 @@ export function createTaskTool(options = {}) {
314
334
  return `Error: Invalid task parameters - ${validation.error.message}`;
315
335
  }
316
336
 
317
- const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
337
+ const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
338
+
339
+ // Parse tasks if passed as JSON string (common AI model behavior)
340
+ let tasks = rawTasks;
341
+ if (typeof rawTasks === 'string') {
342
+ try {
343
+ tasks = JSON.parse(rawTasks);
344
+ } catch (e) {
345
+ return `Error: Invalid tasks JSON - ${e.message}`;
346
+ }
347
+ }
318
348
 
319
349
  switch (action) {
320
350
  case 'create': {
@@ -36310,7 +36310,15 @@ function createTaskTool(options = {}) {
36310
36310
  });
36311
36311
  return `Error: Invalid task parameters - ${validation.error.message}`;
36312
36312
  }
36313
- const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
36313
+ const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
36314
+ let tasks = rawTasks;
36315
+ if (typeof rawTasks === "string") {
36316
+ try {
36317
+ tasks = JSON.parse(rawTasks);
36318
+ } catch (e4) {
36319
+ return `Error: Invalid tasks JSON - ${e4.message}`;
36320
+ }
36321
+ }
36314
36322
  switch (action) {
36315
36323
  case "create": {
36316
36324
  if (tasks && Array.isArray(tasks)) {
@@ -36485,7 +36493,8 @@ var init_taskTool = __esm({
36485
36493
  });
36486
36494
  taskSchema = external_exports.object({
36487
36495
  action: external_exports.enum(["create", "update", "complete", "delete", "list"]),
36488
- tasks: external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])).optional(),
36496
+ // Accept both array and JSON string (AI models sometimes serialize as string)
36497
+ tasks: external_exports.union([external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])), external_exports.string()]).optional(),
36489
36498
  id: external_exports.string().optional(),
36490
36499
  title: external_exports.string().optional(),
36491
36500
  description: external_exports.string().optional(),
@@ -36596,6 +36605,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
36596
36605
  **Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
36597
36606
  A single investigation with many steps is still ONE task, not many.
36598
36607
 
36608
+ ## Task Granularity
36609
+
36610
+ Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
36611
+ - "Fix 8 similar test files" \u2192 ONE task (same type of fix across files)
36612
+ - "Update API + tests + docs" \u2192 THREE tasks (different types of work)
36613
+ - "Implement feature in 5 files" \u2192 ONE task (single feature)
36614
+
36615
+ **Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
36616
+
36617
+ **Anti-patterns to avoid**:
36618
+ - One task per file \u274C
36619
+ - One task per function \u274C
36620
+ - One task per repository (when same type of work) \u274C
36621
+
36622
+ **Good patterns**:
36623
+ - One task per distinct deliverable \u2713
36624
+ - One task per phase (implement, test, document) \u2713
36625
+ - One task per different type of work \u2713
36626
+
36599
36627
  MODIFY TASKS when (during execution):
36600
36628
  - You discover the problem is more complex than expected \u2192 Add new tasks
36601
36629
  - A single task covers too much scope \u2192 Split into smaller tasks
@@ -82501,6 +82529,7 @@ var require_pattern = __commonJS({
82501
82529
  "use strict";
82502
82530
  Object.defineProperty(exports2, "__esModule", { value: true });
82503
82531
  var code_1 = require_code2();
82532
+ var util_1 = require_util3();
82504
82533
  var codegen_1 = require_codegen();
82505
82534
  var error2 = {
82506
82535
  message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`,
@@ -82513,10 +82542,18 @@ var require_pattern = __commonJS({
82513
82542
  $data: true,
82514
82543
  error: error2,
82515
82544
  code(cxt) {
82516
- const { data: data2, $data, schema, schemaCode, it } = cxt;
82545
+ const { gen, data: data2, $data, schema, schemaCode, it } = cxt;
82517
82546
  const u4 = it.opts.unicodeRegExp ? "u" : "";
82518
- const regExp = $data ? (0, codegen_1._)`(new RegExp(${schemaCode}, ${u4}))` : (0, code_1.usePattern)(cxt, schema);
82519
- cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data2})`);
82547
+ if ($data) {
82548
+ const { regExp } = it.opts.code;
82549
+ const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp);
82550
+ const valid = gen.let("valid");
82551
+ gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u4}).test(${data2})`), () => gen.assign(valid, false));
82552
+ cxt.fail$data((0, codegen_1._)`!${valid}`);
82553
+ } else {
82554
+ const regExp = (0, code_1.usePattern)(cxt, schema);
82555
+ cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data2})`);
82556
+ }
82520
82557
  }
82521
82558
  };
82522
82559
  exports2.default = def;
@@ -98927,10 +98964,8 @@ Workspace: ${this.allowedFolders.join(", ")}`;
98927
98964
  toolDefinitions += `${taskToolDefinition}
98928
98965
  `;
98929
98966
  }
98930
- if (isToolAllowed("attempt_completion")) {
98931
- toolDefinitions += `${attemptCompletionToolDefinition}
98967
+ toolDefinitions += `${attemptCompletionToolDefinition}
98932
98968
  `;
98933
- }
98934
98969
  if (this.enableDelegate && isToolAllowed("delegate")) {
98935
98970
  toolDefinitions += `${delegateToolDefinition}
98936
98971
  `;
package/cjs/index.cjs CHANGED
@@ -35480,7 +35480,15 @@ function createTaskTool(options = {}) {
35480
35480
  });
35481
35481
  return `Error: Invalid task parameters - ${validation.error.message}`;
35482
35482
  }
35483
- const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
35483
+ const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
35484
+ let tasks = rawTasks;
35485
+ if (typeof rawTasks === "string") {
35486
+ try {
35487
+ tasks = JSON.parse(rawTasks);
35488
+ } catch (e4) {
35489
+ return `Error: Invalid tasks JSON - ${e4.message}`;
35490
+ }
35491
+ }
35484
35492
  switch (action) {
35485
35493
  case "create": {
35486
35494
  if (tasks && Array.isArray(tasks)) {
@@ -35655,7 +35663,8 @@ var init_taskTool = __esm({
35655
35663
  });
35656
35664
  taskSchema = external_exports.object({
35657
35665
  action: external_exports.enum(["create", "update", "complete", "delete", "list"]),
35658
- tasks: external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])).optional(),
35666
+ // Accept both array and JSON string (AI models sometimes serialize as string)
35667
+ tasks: external_exports.union([external_exports.array(external_exports.union([external_exports.string(), taskItemSchema])), external_exports.string()]).optional(),
35659
35668
  id: external_exports.string().optional(),
35660
35669
  title: external_exports.string().optional(),
35661
35670
  description: external_exports.string().optional(),
@@ -35766,6 +35775,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
35766
35775
  **Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
35767
35776
  A single investigation with many steps is still ONE task, not many.
35768
35777
 
35778
+ ## Task Granularity
35779
+
35780
+ Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
35781
+ - "Fix 8 similar test files" \u2192 ONE task (same type of fix across files)
35782
+ - "Update API + tests + docs" \u2192 THREE tasks (different types of work)
35783
+ - "Implement feature in 5 files" \u2192 ONE task (single feature)
35784
+
35785
+ **Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
35786
+
35787
+ **Anti-patterns to avoid**:
35788
+ - One task per file \u274C
35789
+ - One task per function \u274C
35790
+ - One task per repository (when same type of work) \u274C
35791
+
35792
+ **Good patterns**:
35793
+ - One task per distinct deliverable \u2713
35794
+ - One task per phase (implement, test, document) \u2713
35795
+ - One task per different type of work \u2713
35796
+
35769
35797
  MODIFY TASKS when (during execution):
35770
35798
  - You discover the problem is more complex than expected \u2192 Add new tasks
35771
35799
  - A single task covers too much scope \u2192 Split into smaller tasks
@@ -79234,6 +79262,7 @@ var require_pattern = __commonJS({
79234
79262
  "use strict";
79235
79263
  Object.defineProperty(exports2, "__esModule", { value: true });
79236
79264
  var code_1 = require_code2();
79265
+ var util_1 = require_util3();
79237
79266
  var codegen_1 = require_codegen();
79238
79267
  var error2 = {
79239
79268
  message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`,
@@ -79246,10 +79275,18 @@ var require_pattern = __commonJS({
79246
79275
  $data: true,
79247
79276
  error: error2,
79248
79277
  code(cxt) {
79249
- const { data: data2, $data, schema, schemaCode, it } = cxt;
79278
+ const { gen, data: data2, $data, schema, schemaCode, it } = cxt;
79250
79279
  const u4 = it.opts.unicodeRegExp ? "u" : "";
79251
- const regExp = $data ? (0, codegen_1._)`(new RegExp(${schemaCode}, ${u4}))` : (0, code_1.usePattern)(cxt, schema);
79252
- cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data2})`);
79280
+ if ($data) {
79281
+ const { regExp } = it.opts.code;
79282
+ const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp);
79283
+ const valid = gen.let("valid");
79284
+ gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u4}).test(${data2})`), () => gen.assign(valid, false));
79285
+ cxt.fail$data((0, codegen_1._)`!${valid}`);
79286
+ } else {
79287
+ const regExp = (0, code_1.usePattern)(cxt, schema);
79288
+ cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data2})`);
79289
+ }
79253
79290
  }
79254
79291
  };
79255
79292
  exports2.default = def;
@@ -95660,10 +95697,8 @@ Workspace: ${this.allowedFolders.join(", ")}`;
95660
95697
  toolDefinitions += `${taskToolDefinition}
95661
95698
  `;
95662
95699
  }
95663
- if (isToolAllowed("attempt_completion")) {
95664
- toolDefinitions += `${attemptCompletionToolDefinition}
95700
+ toolDefinitions += `${attemptCompletionToolDefinition}
95665
95701
  `;
95666
- }
95667
95702
  if (this.enableDelegate && isToolAllowed("delegate")) {
95668
95703
  toolDefinitions += `${delegateToolDefinition}
95669
95704
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc230",
3
+ "version": "0.6.0-rc231",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -79,7 +79,7 @@
79
79
  "@ai-sdk/openai": "^2.0.10",
80
80
  "@anthropic-ai/claude-agent-sdk": "^0.1.46",
81
81
  "@modelcontextprotocol/sdk": "^1.0.0",
82
- "@probelabs/maid": "^0.0.23",
82
+ "@probelabs/maid": "^0.0.24",
83
83
  "adm-zip": "^0.5.16",
84
84
  "ai": "^5.0.0",
85
85
  "ajv": "^8.17.1",
@@ -2496,10 +2496,9 @@ ${extractGuidance}
2496
2496
  toolDefinitions += `${taskToolDefinition}\n`;
2497
2497
  }
2498
2498
 
2499
- // Always include attempt_completion (unless explicitly disabled in raw AI mode)
2500
- if (isToolAllowed('attempt_completion')) {
2501
- toolDefinitions += `${attemptCompletionToolDefinition}\n`;
2502
- }
2499
+ // Always include attempt_completion unconditionally - it's a completion signal, not a tool
2500
+ // This ensures agents can always complete their work, regardless of tool restrictions
2501
+ toolDefinitions += `${attemptCompletionToolDefinition}\n`;
2503
2502
 
2504
2503
  // Delegate tool (require both enableDelegate flag AND allowedTools permission)
2505
2504
  // Place after attempt_completion as it's an optional tool
@@ -3304,8 +3303,9 @@ Follow these instructions carefully:
3304
3303
  if (this.enableSkills && this.allowedTools.isEnabled('listSkills')) validTools.push('listSkills');
3305
3304
  if (this.enableSkills && this.allowedTools.isEnabled('useSkill')) validTools.push('useSkill');
3306
3305
  if (this.allowedTools.isEnabled('readImage')) validTools.push('readImage');
3307
- // Always allow attempt_completion - it's a completion signal, not a tool
3306
+ // Always allow attempt_completion in validTools - it's a completion signal, not a tool
3308
3307
  // This ensures agents can complete even when disableTools: true is set (fixes #333)
3308
+ // The tool DEFINITION may be hidden in raw AI mode, but we still need to recognize it
3309
3309
  validTools.push('attempt_completion');
3310
3310
 
3311
3311
  // Edit tools (require both allowEdit flag AND allowedTools permission)
@@ -23,7 +23,8 @@ export const taskItemSchema = z.object({
23
23
  */
24
24
  export const taskSchema = z.object({
25
25
  action: z.enum(['create', 'update', 'complete', 'delete', 'list']),
26
- tasks: z.array(z.union([z.string(), taskItemSchema])).optional(),
26
+ // Accept both array and JSON string (AI models sometimes serialize as string)
27
+ tasks: z.union([z.array(z.union([z.string(), taskItemSchema])), z.string()]).optional(),
27
28
  id: z.string().optional(),
28
29
  title: z.string().optional(),
29
30
  description: z.string().optional(),
@@ -142,6 +143,25 @@ SKIP TASKS for single-goal requests, even if they require multiple searches:
142
143
  **Key insight**: Multiple *internal steps* (search, read, analyze) are NOT the same as multiple *goals*.
143
144
  A single investigation with many steps is still ONE task, not many.
144
145
 
146
+ ## Task Granularity
147
+
148
+ Tasks represent LOGICAL UNITS OF WORK, not individual files or steps:
149
+ - "Fix 8 similar test files" → ONE task (same type of fix across files)
150
+ - "Update API + tests + docs" → THREE tasks (different types of work)
151
+ - "Implement feature in 5 files" → ONE task (single feature)
152
+
153
+ **Rule of thumb**: If you're creating more than 3-4 tasks, you're probably too granular.
154
+
155
+ **Anti-patterns to avoid**:
156
+ - One task per file ❌
157
+ - One task per function ❌
158
+ - One task per repository (when same type of work) ❌
159
+
160
+ **Good patterns**:
161
+ - One task per distinct deliverable ✓
162
+ - One task per phase (implement, test, document) ✓
163
+ - One task per different type of work ✓
164
+
145
165
  MODIFY TASKS when (during execution):
146
166
  - You discover the problem is more complex than expected → Add new tasks
147
167
  - A single task covers too much scope → Split into smaller tasks
@@ -314,7 +334,17 @@ export function createTaskTool(options = {}) {
314
334
  return `Error: Invalid task parameters - ${validation.error.message}`;
315
335
  }
316
336
 
317
- const { action, tasks, id, title, description, status, priority, dependencies, after } = validation.data;
337
+ const { action, tasks: rawTasks, id, title, description, status, priority, dependencies, after } = validation.data;
338
+
339
+ // Parse tasks if passed as JSON string (common AI model behavior)
340
+ let tasks = rawTasks;
341
+ if (typeof rawTasks === 'string') {
342
+ try {
343
+ tasks = JSON.parse(rawTasks);
344
+ } catch (e) {
345
+ return `Error: Invalid tasks JSON - ${e.message}`;
346
+ }
347
+ }
318
348
 
319
349
  switch (action) {
320
350
  case 'create': {