Просмотр исходного кода

Prevent completion with open todos (#5716)

Co-authored-by: Roo Code <[email protected]>
Matt Rubens 5 месяцев назад
Родитель
Сommit
a6e16e80d9

+ 1 - 1
packages/types/npm/package.json

@@ -1,6 +1,6 @@
 {
 	"name": "@roo-code/types",
-	"version": "1.34.0",
+	"version": "1.35.0",
 	"description": "TypeScript type definitions for Roo Code.",
 	"publishConfig": {
 		"access": "public",

+ 2 - 0
packages/types/src/global-settings.ts

@@ -51,6 +51,7 @@ export const globalSettingsSchema = z.object({
 	allowedCommands: z.array(z.string()).optional(),
 	deniedCommands: z.array(z.string()).optional(),
 	commandExecutionTimeout: z.number().optional(),
+	preventCompletionWithOpenTodos: z.boolean().optional(),
 	allowedMaxRequests: z.number().nullish(),
 	autoCondenseContext: z.boolean().optional(),
 	autoCondenseContextPercent: z.number().optional(),
@@ -202,6 +203,7 @@ export const EVALS_SETTINGS: RooCodeSettings = {
 	followupAutoApproveTimeoutMs: 0,
 	allowedCommands: ["*"],
 	commandExecutionTimeout: 30_000,
+	preventCompletionWithOpenTodos: false,
 
 	browserToolEnabled: false,
 	browserViewportSize: "900x600",

+ 412 - 0
src/core/tools/__tests__/attemptCompletionTool.spec.ts

@@ -0,0 +1,412 @@
+import { describe, it, expect, vi, beforeEach } from "vitest"
+import { TodoItem } from "@roo-code/types"
+import { AttemptCompletionToolUse } from "../../../shared/tools"
+
+// Mock the formatResponse module before importing the tool
+vi.mock("../../prompts/responses", () => ({
+	formatResponse: {
+		toolError: vi.fn((msg: string) => `Error: ${msg}`),
+	},
+}))
+
+// Mock vscode module
+vi.mock("vscode", () => ({
+	workspace: {
+		getConfiguration: vi.fn(() => ({
+			get: vi.fn(),
+		})),
+	},
+}))
+
+// Mock Package module
+vi.mock("../../../shared/package", () => ({
+	Package: {
+		name: "roo-cline",
+	},
+}))
+
+import { attemptCompletionTool } from "../attemptCompletionTool"
+import { Task } from "../../task/Task"
+import * as vscode from "vscode"
+
+describe("attemptCompletionTool", () => {
+	let mockTask: Partial<Task>
+	let mockPushToolResult: ReturnType<typeof vi.fn>
+	let mockAskApproval: ReturnType<typeof vi.fn>
+	let mockHandleError: ReturnType<typeof vi.fn>
+	let mockRemoveClosingTag: ReturnType<typeof vi.fn>
+	let mockToolDescription: ReturnType<typeof vi.fn>
+	let mockAskFinishSubTaskApproval: ReturnType<typeof vi.fn>
+	let mockGetConfiguration: ReturnType<typeof vi.fn>
+
+	beforeEach(() => {
+		mockPushToolResult = vi.fn()
+		mockAskApproval = vi.fn()
+		mockHandleError = vi.fn()
+		mockRemoveClosingTag = vi.fn()
+		mockToolDescription = vi.fn()
+		mockAskFinishSubTaskApproval = vi.fn()
+		mockGetConfiguration = vi.fn(() => ({
+			get: vi.fn((key: string, defaultValue: any) => {
+				if (key === "preventCompletionWithOpenTodos") {
+					return defaultValue // Default to false unless overridden in test
+				}
+				return defaultValue
+			}),
+		}))
+
+		// Setup vscode mock
+		vi.mocked(vscode.workspace.getConfiguration).mockImplementation(mockGetConfiguration)
+
+		mockTask = {
+			consecutiveMistakeCount: 0,
+			recordToolError: vi.fn(),
+			todoList: undefined,
+		}
+	})
+
+	describe("todo list validation", () => {
+		it("should allow completion when there is no todo list", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			mockTask.todoList = undefined
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			// Should not call pushToolResult with an error for empty todo list
+			expect(mockTask.consecutiveMistakeCount).toBe(0)
+			expect(mockTask.recordToolError).not.toHaveBeenCalled()
+		})
+
+		it("should allow completion when todo list is empty", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			mockTask.todoList = []
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			expect(mockTask.consecutiveMistakeCount).toBe(0)
+			expect(mockTask.recordToolError).not.toHaveBeenCalled()
+		})
+
+		it("should allow completion when all todos are completed", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			const completedTodos: TodoItem[] = [
+				{ id: "1", content: "First task", status: "completed" },
+				{ id: "2", content: "Second task", status: "completed" },
+			]
+
+			mockTask.todoList = completedTodos
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			expect(mockTask.consecutiveMistakeCount).toBe(0)
+			expect(mockTask.recordToolError).not.toHaveBeenCalled()
+		})
+
+		it("should prevent completion when there are pending todos", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			const todosWithPending: TodoItem[] = [
+				{ id: "1", content: "First task", status: "completed" },
+				{ id: "2", content: "Second task", status: "pending" },
+			]
+
+			mockTask.todoList = todosWithPending
+
+			// Enable the setting to prevent completion with open todos
+			mockGetConfiguration.mockReturnValue({
+				get: vi.fn((key: string, defaultValue: any) => {
+					if (key === "preventCompletionWithOpenTodos") {
+						return true // Setting is enabled
+					}
+					return defaultValue
+				}),
+			})
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			expect(mockTask.consecutiveMistakeCount).toBe(1)
+			expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
+			expect(mockPushToolResult).toHaveBeenCalledWith(
+				expect.stringContaining("Cannot complete task while there are incomplete todos"),
+			)
+		})
+
+		it("should prevent completion when there are in-progress todos", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			const todosWithInProgress: TodoItem[] = [
+				{ id: "1", content: "First task", status: "completed" },
+				{ id: "2", content: "Second task", status: "in_progress" },
+			]
+
+			mockTask.todoList = todosWithInProgress
+
+			// Enable the setting to prevent completion with open todos
+			mockGetConfiguration.mockReturnValue({
+				get: vi.fn((key: string, defaultValue: any) => {
+					if (key === "preventCompletionWithOpenTodos") {
+						return true // Setting is enabled
+					}
+					return defaultValue
+				}),
+			})
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			expect(mockTask.consecutiveMistakeCount).toBe(1)
+			expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
+			expect(mockPushToolResult).toHaveBeenCalledWith(
+				expect.stringContaining("Cannot complete task while there are incomplete todos"),
+			)
+		})
+
+		it("should prevent completion when there are mixed incomplete todos", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			const mixedTodos: TodoItem[] = [
+				{ id: "1", content: "First task", status: "completed" },
+				{ id: "2", content: "Second task", status: "pending" },
+				{ id: "3", content: "Third task", status: "in_progress" },
+			]
+
+			mockTask.todoList = mixedTodos
+
+			// Enable the setting to prevent completion with open todos
+			mockGetConfiguration.mockReturnValue({
+				get: vi.fn((key: string, defaultValue: any) => {
+					if (key === "preventCompletionWithOpenTodos") {
+						return true // Setting is enabled
+					}
+					return defaultValue
+				}),
+			})
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			expect(mockTask.consecutiveMistakeCount).toBe(1)
+			expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
+			expect(mockPushToolResult).toHaveBeenCalledWith(
+				expect.stringContaining("Cannot complete task while there are incomplete todos"),
+			)
+		})
+
+		it("should allow completion when setting is disabled even with incomplete todos", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			const todosWithPending: TodoItem[] = [
+				{ id: "1", content: "First task", status: "completed" },
+				{ id: "2", content: "Second task", status: "pending" },
+			]
+
+			mockTask.todoList = todosWithPending
+
+			// Ensure the setting is disabled (default behavior)
+			mockGetConfiguration.mockReturnValue({
+				get: vi.fn((key: string, defaultValue: any) => {
+					if (key === "preventCompletionWithOpenTodos") {
+						return false // Setting is disabled
+					}
+					return defaultValue
+				}),
+			})
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			// Should not prevent completion when setting is disabled
+			expect(mockTask.consecutiveMistakeCount).toBe(0)
+			expect(mockTask.recordToolError).not.toHaveBeenCalled()
+			expect(mockPushToolResult).not.toHaveBeenCalledWith(
+				expect.stringContaining("Cannot complete task while there are incomplete todos"),
+			)
+		})
+
+		it("should prevent completion when setting is enabled with incomplete todos", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			const todosWithPending: TodoItem[] = [
+				{ id: "1", content: "First task", status: "completed" },
+				{ id: "2", content: "Second task", status: "pending" },
+			]
+
+			mockTask.todoList = todosWithPending
+
+			// Enable the setting
+			mockGetConfiguration.mockReturnValue({
+				get: vi.fn((key: string, defaultValue: any) => {
+					if (key === "preventCompletionWithOpenTodos") {
+						return true // Setting is enabled
+					}
+					return defaultValue
+				}),
+			})
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			// Should prevent completion when setting is enabled and there are incomplete todos
+			expect(mockTask.consecutiveMistakeCount).toBe(1)
+			expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
+			expect(mockPushToolResult).toHaveBeenCalledWith(
+				expect.stringContaining("Cannot complete task while there are incomplete todos"),
+			)
+		})
+
+		it("should allow completion when setting is enabled but all todos are completed", async () => {
+			const block: AttemptCompletionToolUse = {
+				type: "tool_use",
+				name: "attempt_completion",
+				params: { result: "Task completed successfully" },
+				partial: false,
+			}
+
+			const completedTodos: TodoItem[] = [
+				{ id: "1", content: "First task", status: "completed" },
+				{ id: "2", content: "Second task", status: "completed" },
+			]
+
+			mockTask.todoList = completedTodos
+
+			// Enable the setting
+			mockGetConfiguration.mockReturnValue({
+				get: vi.fn((key: string, defaultValue: any) => {
+					if (key === "preventCompletionWithOpenTodos") {
+						return true // Setting is enabled
+					}
+					return defaultValue
+				}),
+			})
+
+			await attemptCompletionTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+				mockToolDescription,
+				mockAskFinishSubTaskApproval,
+			)
+
+			// Should allow completion when setting is enabled but all todos are completed
+			expect(mockTask.consecutiveMistakeCount).toBe(0)
+			expect(mockTask.recordToolError).not.toHaveBeenCalled()
+			expect(mockPushToolResult).not.toHaveBeenCalledWith(
+				expect.stringContaining("Cannot complete task while there are incomplete todos"),
+			)
+		})
+	})
+})

+ 21 - 0
src/core/tools/attemptCompletionTool.ts

@@ -1,4 +1,5 @@
 import Anthropic from "@anthropic-ai/sdk"
+import * as vscode from "vscode"
 
 import { TelemetryService } from "@roo-code/telemetry"
 
@@ -14,6 +15,7 @@ import {
 	AskFinishSubTaskApproval,
 } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
+import { Package } from "../../shared/package"
 
 export async function attemptCompletionTool(
 	cline: Task,
@@ -28,6 +30,25 @@ export async function attemptCompletionTool(
 	const result: string | undefined = block.params.result
 	const command: string | undefined = block.params.command
 
+	// Get the setting for preventing completion with open todos from VSCode configuration
+	const preventCompletionWithOpenTodos = vscode.workspace
+		.getConfiguration(Package.name)
+		.get<boolean>("preventCompletionWithOpenTodos", false)
+
+	// Check if there are incomplete todos (only if the setting is enabled)
+	const hasIncompleteTodos = cline.todoList && cline.todoList.some((todo) => todo.status !== "completed")
+
+	if (preventCompletionWithOpenTodos && hasIncompleteTodos) {
+		cline.consecutiveMistakeCount++
+		cline.recordToolError("attempt_completion")
+		pushToolResult(
+			formatResponse.toolError(
+				"Cannot complete task while there are incomplete todos. Please finish all todos before attempting completion.",
+			),
+		)
+		return
+	}
+
 	try {
 		const lastMessage = cline.clineMessages.at(-1)
 

+ 5 - 0
src/i18n/locales/ca/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Esteu segur que voleu suprimir aquest mode personalitzat?",
 			"confirm": "Suprimeix"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Evitar la finalització de tasques quan hi ha todos incomplets a la llista de todos"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/de/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Bist du sicher, dass du diesen benutzerdefinierten Modus löschen möchtest?",
 			"confirm": "Löschen"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Aufgabenabschluss verhindern, wenn unvollständige Todos in der Todo-Liste vorhanden sind"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/en/common.json

@@ -155,5 +155,10 @@
 			"descriptionNoRules": "Are you sure you want to delete this custom mode?",
 			"confirm": "Delete"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Prevent task completion when there are incomplete todos in the todo list"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/es/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "¿Estás seguro de que quieres eliminar este modo personalizado?",
 			"confirm": "Eliminar"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Prevenir la finalización de tareas cuando hay todos incompletos en la lista de todos"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/fr/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Êtes-vous sûr de vouloir supprimer ce mode personnalisé ?",
 			"confirm": "Supprimer"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Empêcher la finalisation des tâches lorsqu'il y a des todos incomplets dans la liste de todos"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/hi/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "क्या आप वाकई इस कस्टम मोड को हटाना चाहते हैं?",
 			"confirm": "हटाएं"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "जब टूडू सूची में अधूरे टूडू हों तो कार्य पूर्ण होने से रोकें"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/id/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Anda yakin ingin menghapus mode kustom ini?",
 			"confirm": "Hapus"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Mencegah penyelesaian tugas ketika ada todo yang belum selesai dalam daftar todo"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/it/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Sei sicuro di voler eliminare questa modalità personalizzata?",
 			"confirm": "Elimina"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Impedire il completamento delle attività quando ci sono todo incompleti nella lista dei todo"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/ja/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "このカスタムモードを削除してもよろしいですか?",
 			"confirm": "削除"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Todoリストに未完了のTodoがある場合、タスクの完了を防ぐ"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/ko/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "이 사용자 정의 모드를 삭제하시겠습니까?",
 			"confirm": "삭제"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "할 일 목록에 미완료된 할 일이 있을 때 작업 완료를 방지"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/nl/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Weet je zeker dat je deze aangepaste modus wilt verwijderen?",
 			"confirm": "Verwijderen"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Voorkom taakafronding wanneer er onvolledige todos in de todolijst staan"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/pl/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Czy na pewno chcesz usunąć ten tryb niestandardowy?",
 			"confirm": "Usuń"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Zapobiegaj ukończeniu zadania gdy na liście zadań są nieukończone zadania"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/pt-BR/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Tem certeza de que deseja excluir este modo personalizado?",
 			"confirm": "Excluir"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Impedir a conclusão de tarefas quando há todos incompletos na lista de todos"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/ru/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Вы уверены, что хотите удалить этот пользовательский режим?",
 			"confirm": "Удалить"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Предотвратить завершение задач при наличии незавершенных дел в списке дел"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/tr/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "Bu özel modu silmek istediğinizden emin misiniz?",
 			"confirm": "Sil"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Todo listesinde tamamlanmamış todolar olduğunda görev tamamlanmasını engelle"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/vi/common.json

@@ -173,5 +173,10 @@
 			"descriptionNoRules": "Bạn có chắc chắn muốn xóa chế độ tùy chỉnh này không?",
 			"confirm": "Xóa"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "Ngăn chặn hoàn thành nhiệm vụ khi có các todo chưa hoàn thành trong danh sách todo"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/zh-CN/common.json

@@ -171,5 +171,10 @@
 			"descriptionNoRules": "您确定要删除此自定义模式吗?",
 			"confirm": "删除"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "当待办事项列表中有未完成的待办事项时阻止任务完成"
+		}
 	}
 }

+ 5 - 0
src/i18n/locales/zh-TW/common.json

@@ -166,5 +166,10 @@
 			"descriptionNoRules": "您確定要刪除此自訂模式嗎?",
 			"confirm": "刪除"
 		}
+	},
+	"commands": {
+		"preventCompletionWithOpenTodos": {
+			"description": "當待辦事項清單中有未完成的待辦事項時阻止工作完成"
+		}
 	}
 }

+ 5 - 0
src/package.json

@@ -345,6 +345,11 @@
 					"maximum": 600,
 					"description": "%commands.commandExecutionTimeout.description%"
 				},
+				"roo-cline.preventCompletionWithOpenTodos": {
+					"type": "boolean",
+					"default": false,
+					"description": "%commands.preventCompletionWithOpenTodos.description%"
+				},
 				"roo-cline.vsCodeLmModelSelector": {
 					"type": "object",
 					"properties": {

+ 1 - 0
src/package.nls.json

@@ -29,6 +29,7 @@
 	"commands.allowedCommands.description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled",
 	"commands.deniedCommands.description": "Command prefixes that will be automatically denied without asking for approval. In case of conflicts with allowed commands, the longest prefix match takes precedence. Add * to deny all commands.",
 	"commands.commandExecutionTimeout.description": "Maximum time in seconds to wait for command execution to complete before timing out (0 = no timeout, 1-600s, default: 0s)",
+	"commands.preventCompletionWithOpenTodos.description": "Prevent task completion when there are incomplete todos in the todo list",
 	"settings.vsCodeLmModelSelector.description": "Settings for VSCode Language Model API",
 	"settings.vsCodeLmModelSelector.vendor.description": "The vendor of the language model (e.g. copilot)",
 	"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",