From 210231daa22e192d919277025ac03e93ba4d7543 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 21 Jul 2025 19:00:04 +0000 Subject: [PATCH 1/8] feat: add todo list enable checkbox to provider advanced settings - Add todoListEnabled field to provider settings types - Create TodoListSettingsControl component similar to DiffSettingsControl - Integrate todo list control into ApiOptions advanced settings - Add translation keys for the new setting - Add comprehensive unit tests for the new component - Update ApiOptions tests to include VSCodeCheckbox mock --- packages/types/src/provider-settings.ts | 1 + .../src/components/settings/ApiOptions.tsx | 5 ++ .../settings/TodoListSettingsControl.tsx | 35 +++++++++ .../settings/__tests__/ApiOptions.spec.tsx | 26 +++++++ .../TodoListSettingsControl.spec.tsx | 77 +++++++++++++++++++ webview-ui/src/i18n/locales/en/settings.json | 4 + 6 files changed, 148 insertions(+) create mode 100644 webview-ui/src/components/settings/TodoListSettingsControl.tsx create mode 100644 webview-ui/src/components/settings/__tests__/TodoListSettingsControl.spec.tsx diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index be74ae6bb4c..ca127276a15 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -61,6 +61,7 @@ export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3 const baseProviderSettingsSchema = z.object({ includeMaxTokens: z.boolean().optional(), diffEnabled: z.boolean().optional(), + todoListEnabled: z.boolean().optional(), fuzzyMatchThreshold: z.number().optional(), modelTemperature: z.number().nullish(), rateLimitSeconds: z.number().optional(), diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 06994b16b9b..681268a7874 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -78,6 +78,7 @@ import { ModelInfoView } from "./ModelInfoView" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" import { DiffSettingsControl } from "./DiffSettingsControl" +import { TodoListSettingsControl } from "./TodoListSettingsControl" import { TemperatureControl } from "./TemperatureControl" import { RateLimitSecondsControl } from "./RateLimitSecondsControl" import { ConsecutiveMistakeLimitControl } from "./ConsecutiveMistakeLimitControl" @@ -564,6 +565,10 @@ const ApiOptions = ({ {t("settings:advancedSettings.title")} + setApiConfigurationField(field, value)} + /> void +} + +export const TodoListSettingsControl: React.FC = ({ + todoListEnabled = true, + onChange, +}) => { + const { t } = useAppTranslation() + + const handleTodoListEnabledChange = useCallback( + (e: any) => { + onChange("todoListEnabled", e.target.checked) + }, + [onChange], + ) + + return ( +
+
+ + {t("settings:advanced.todoList.label")} + +
+ {t("settings:advanced.todoList.description")} +
+
+
+ ) +} diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx index bf02840429d..7b7f9b33e48 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx @@ -21,6 +21,16 @@ vi.mock("@vscode/webview-ui-toolkit/react", () => ({ VSCodeRadio: ({ value, checked }: any) => , VSCodeRadioGroup: ({ children }: any) =>
{children}
, VSCodeButton: ({ children }: any) =>
{children}
, + VSCodeCheckbox: ({ children, checked, onChange }: any) => ( + + ), })) // Mock other components @@ -173,6 +183,22 @@ vi.mock("../DiffSettingsControl", () => ({ ), })) +// Mock TodoListSettingsControl for tests +vi.mock("../TodoListSettingsControl", () => ({ + TodoListSettingsControl: ({ todoListEnabled, onChange }: any) => ( +
+ +
+ ), +})) + // Mock ThinkingBudget component vi.mock("../ThinkingBudget", () => ({ ThinkingBudget: ({ modelInfo }: any) => { diff --git a/webview-ui/src/components/settings/__tests__/TodoListSettingsControl.spec.tsx b/webview-ui/src/components/settings/__tests__/TodoListSettingsControl.spec.tsx new file mode 100644 index 00000000000..432b2c9c611 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/TodoListSettingsControl.spec.tsx @@ -0,0 +1,77 @@ +import React from "react" +import { render, screen, fireEvent } from "@testing-library/react" +import { describe, it, expect, vi } from "vitest" +import { TodoListSettingsControl } from "../TodoListSettingsControl" + +// Mock the translation hook +vi.mock("@/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + "settings:advanced.todoList.label": "Enable todo list tool", + "settings:advanced.todoList.description": + "When enabled, Roo can create and manage todo lists to track task progress. This helps organize complex tasks into manageable steps.", + } + return translations[key] || key + }, + }), +})) + +// Mock VSCodeCheckbox +vi.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeCheckbox: ({ children, onChange, checked, ...props }: any) => ( + + ), +})) + +describe("TodoListSettingsControl", () => { + it("renders with default props", () => { + const onChange = vi.fn() + render() + + const checkbox = screen.getByRole("checkbox") + const label = screen.getByText("Enable todo list tool") + const description = screen.getByText(/When enabled, Roo can create and manage todo lists/) + + expect(checkbox).toBeInTheDocument() + expect(checkbox).toBeChecked() // Default is true + expect(label).toBeInTheDocument() + expect(description).toBeInTheDocument() + }) + + it("renders with todoListEnabled set to false", () => { + const onChange = vi.fn() + render() + + const checkbox = screen.getByRole("checkbox") + expect(checkbox).not.toBeChecked() + }) + + it("calls onChange when checkbox is clicked", () => { + const onChange = vi.fn() + render() + + const checkbox = screen.getByRole("checkbox") + fireEvent.click(checkbox) + + expect(onChange).toHaveBeenCalledWith("todoListEnabled", false) + }) + + it("toggles from unchecked to checked", () => { + const onChange = vi.fn() + render() + + const checkbox = screen.getByRole("checkbox") + fireEvent.click(checkbox) + + expect(onChange).toHaveBeenCalledWith("todoListEnabled", true) + }) +}) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 7e3c2e3fccb..457b4dbda9b 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -577,6 +577,10 @@ "label": "Match precision", "description": "This slider controls how precisely code sections must match when applying diffs. Lower values allow more flexible matching but increase the risk of incorrect replacements. Use values below 100% with extreme caution." } + }, + "todoList": { + "label": "Enable todo list tool", + "description": "When enabled, Roo can create and manage todo lists to track task progress. This helps organize complex tasks into manageable steps." } }, "experimental": { From c4937d750cd36830c0d417f245ec8acde8e243e2 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 21 Jul 2025 20:33:47 +0000 Subject: [PATCH 2/8] feat: add translations for todo list tool checkbox in all supported locales --- webview-ui/src/i18n/locales/ca/settings.json | 4 ++++ webview-ui/src/i18n/locales/de/settings.json | 4 ++++ webview-ui/src/i18n/locales/es/settings.json | 4 ++++ webview-ui/src/i18n/locales/fr/settings.json | 4 ++++ webview-ui/src/i18n/locales/hi/settings.json | 4 ++++ webview-ui/src/i18n/locales/id/settings.json | 4 ++++ webview-ui/src/i18n/locales/it/settings.json | 4 ++++ webview-ui/src/i18n/locales/ja/settings.json | 4 ++++ webview-ui/src/i18n/locales/ko/settings.json | 4 ++++ webview-ui/src/i18n/locales/nl/settings.json | 4 ++++ webview-ui/src/i18n/locales/pl/settings.json | 4 ++++ webview-ui/src/i18n/locales/pt-BR/settings.json | 4 ++++ webview-ui/src/i18n/locales/ru/settings.json | 4 ++++ webview-ui/src/i18n/locales/tr/settings.json | 4 ++++ webview-ui/src/i18n/locales/vi/settings.json | 4 ++++ webview-ui/src/i18n/locales/zh-CN/settings.json | 4 ++++ webview-ui/src/i18n/locales/zh-TW/settings.json | 4 ++++ 17 files changed, 68 insertions(+) diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index eaa83b1b0d8..8f270ee4ce4 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -577,6 +577,10 @@ "label": "Precisió de coincidència", "description": "Aquest control lliscant controla amb quina precisió han de coincidir les seccions de codi en aplicar diffs. Valors més baixos permeten coincidències més flexibles però augmenten el risc de reemplaçaments incorrectes. Utilitzeu valors per sota del 100% amb extrema precaució." } + }, + "todoList": { + "label": "Habilitar eina de llista de tasques", + "description": "Quan està habilitat, Roo pot crear i gestionar llistes de tasques per fer el seguiment del progrés de les tasques. Això ajuda a organitzar tasques complexes en passos manejables." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 7f7c2c22b72..73a4ede3d85 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -577,6 +577,10 @@ "label": "Übereinstimmungspräzision", "description": "Dieser Schieberegler steuert, wie genau Codeabschnitte bei der Anwendung von Diffs übereinstimmen müssen. Niedrigere Werte ermöglichen eine flexiblere Übereinstimmung, erhöhen aber das Risiko falscher Ersetzungen. Verwenden Sie Werte unter 100 % mit äußerster Vorsicht." } + }, + "todoList": { + "label": "Todo-Listen-Tool aktivieren", + "description": "Wenn aktiviert, kann Roo Todo-Listen erstellen und verwalten, um den Aufgabenfortschritt zu verfolgen. Dies hilft, komplexe Aufgaben in überschaubare Schritte zu organisieren." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index f00c2c9b427..d217e66226b 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -577,6 +577,10 @@ "label": "Precisión de coincidencia", "description": "Este control deslizante controla cuán precisamente deben coincidir las secciones de código al aplicar diffs. Valores más bajos permiten coincidencias más flexibles pero aumentan el riesgo de reemplazos incorrectos. Use valores por debajo del 100% con extrema precaución." } + }, + "todoList": { + "label": "Habilitar herramienta de lista de tareas", + "description": "Cuando está habilitado, Roo puede crear y gestionar listas de tareas para hacer seguimiento del progreso. Esto ayuda a organizar tareas complejas en pasos manejables." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 02dfac3552c..f7f9a2683ad 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -577,6 +577,10 @@ "label": "Précision de correspondance", "description": "Ce curseur contrôle la précision avec laquelle les sections de code doivent correspondre lors de l'application des diffs. Des valeurs plus basses permettent des correspondances plus flexibles mais augmentent le risque de remplacements incorrects. Utilisez des valeurs inférieures à 100 % avec une extrême prudence." } + }, + "todoList": { + "label": "Activer l'outil de liste de tâches", + "description": "Lorsqu'activé, Roo peut créer et gérer des listes de tâches pour suivre la progression. Cela aide à organiser les tâches complexes en étapes gérables." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 2b7fd03d56c..6a3679e0904 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -577,6 +577,10 @@ "label": "मिलान सटीकता", "description": "यह स्लाइडर नियंत्रित करता है कि diffs लागू करते समय कोड अनुभागों को कितनी सटीकता से मेल खाना चाहिए। निम्न मान अधिक लचीले मिलान की अनुमति देते हैं लेकिन गलत प्रतिस्थापन का जोखिम बढ़ाते हैं। 100% से नीचे के मानों का उपयोग अत्यधिक सावधानी के साथ करें।" } + }, + "todoList": { + "label": "टूडू सूची टूल सक्षम करें", + "description": "जब सक्षम हो, तो Roo कार्य प्रगति को ट्रैक करने के लिए टूडू सूचियाँ बना और प्रबंधित कर सकता है। यह जटिल कार्यों को प्रबंधनीय चरणों में व्यवस्थित करने में मदद करता है।" } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index c6ba2728a74..09d7e8254ab 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -581,6 +581,10 @@ "label": "Presisi pencocokan", "description": "Slider ini mengontrol seberapa tepat bagian kode harus cocok saat menerapkan diff. Nilai yang lebih rendah memungkinkan pencocokan yang lebih fleksibel tetapi meningkatkan risiko penggantian yang salah. Gunakan nilai di bawah 100% dengan sangat hati-hati." } + }, + "todoList": { + "label": "Aktifkan alat daftar tugas", + "description": "Saat diaktifkan, Roo dapat membuat dan mengelola daftar tugas untuk melacak kemajuan tugas. Ini membantu mengatur tugas kompleks menjadi langkah-langkah yang dapat dikelola." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 26c776d60db..c2836f2c715 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -577,6 +577,10 @@ "label": "Precisione corrispondenza", "description": "Questo cursore controlla quanto precisamente le sezioni di codice devono corrispondere quando si applicano i diff. Valori più bassi consentono corrispondenze più flessibili ma aumentano il rischio di sostituzioni errate. Usa valori inferiori al 100% con estrema cautela." } + }, + "todoList": { + "label": "Abilita strumento lista di cose da fare", + "description": "Quando abilitato, Roo può creare e gestire liste di cose da fare per tracciare il progresso delle attività. Questo aiuta a organizzare attività complesse in passaggi gestibili." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 9eb4328c126..01233ef505e 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -577,6 +577,10 @@ "label": "マッチ精度", "description": "このスライダーは、diffを適用する際にコードセクションがどれだけ正確に一致する必要があるかを制御します。低い値はより柔軟なマッチングを可能にしますが、誤った置換のリスクが高まります。100%未満の値は細心の注意を払って使用してください。" } + }, + "todoList": { + "label": "ToDoリストツールを有効にする", + "description": "有効にすると、Rooはタスクの進捗を追跡するためのToDoリストを作成・管理できます。これにより、複雑なタスクを管理しやすいステップに整理できます。" } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 18b054bbb8b..806b11f0225 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -577,6 +577,10 @@ "label": "일치 정확도", "description": "이 슬라이더는 diff를 적용할 때 코드 섹션이 얼마나 정확하게 일치해야 하는지 제어합니다. 낮은 값은 더 유연한 일치를 허용하지만 잘못된 교체 위험이 증가합니다. 100% 미만의 값은 극도로 주의해서 사용하세요." } + }, + "todoList": { + "label": "할 일 목록 도구 활성화", + "description": "활성화하면 Roo가 작업 진행 상황을 추적하기 위한 할 일 목록을 만들고 관리할 수 있습니다. 이는 복잡한 작업을 관리 가능한 단계로 구성하는 데 도움이 됩니다." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 73ecf87d349..28edfdfd52e 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -577,6 +577,10 @@ "label": "Matchnauwkeurigheid", "description": "Deze schuifregelaar bepaalt hoe nauwkeurig codeblokken moeten overeenkomen bij het toepassen van diffs. Lagere waarden laten flexibelere matching toe maar verhogen het risico op verkeerde vervangingen. Gebruik waarden onder 100% met uiterste voorzichtigheid." } + }, + "todoList": { + "label": "Takenlijst-tool inschakelen", + "description": "Wanneer ingeschakeld, kan Roo takenlijsten maken en beheren om de voortgang van taken bij te houden. Dit helpt complexe taken te organiseren in beheersbare stappen." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index b0fb8efac30..d034830c1a1 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -577,6 +577,10 @@ "label": "Precyzja dopasowania", "description": "Ten suwak kontroluje, jak dokładnie sekcje kodu muszą pasować podczas stosowania różnic. Niższe wartości umożliwiają bardziej elastyczne dopasowywanie, ale zwiększają ryzyko nieprawidłowych zamian. Używaj wartości poniżej 100% z najwyższą ostrożnością." } + }, + "todoList": { + "label": "Włącz narzędzie listy zadań", + "description": "Po włączeniu Roo może tworzyć i zarządzać listami zadań do śledzenia postępu zadań. Pomaga to organizować złożone zadania w łatwe do zarządzania kroki." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 4167ade9749..140042c7731 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -577,6 +577,10 @@ "label": "Precisão de correspondência", "description": "Este controle deslizante controla quão precisamente as seções de código devem corresponder ao aplicar diffs. Valores mais baixos permitem correspondências mais flexíveis, mas aumentam o risco de substituições incorretas. Use valores abaixo de 100% com extrema cautela." } + }, + "todoList": { + "label": "Habilitar ferramenta de lista de tarefas", + "description": "Quando habilitado, o Roo pode criar e gerenciar listas de tarefas para acompanhar o progresso das tarefas. Isso ajuda a organizar tarefas complexas em etapas gerenciáveis." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index c07bc1d98a5..895655fbdf0 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -577,6 +577,10 @@ "label": "Точность совпадения", "description": "Этот ползунок управляет точностью совпадения секций кода при применении диффов. Меньшие значения позволяют более гибкое совпадение, но увеличивают риск неверной замены. Используйте значения ниже 100% с осторожностью." } + }, + "todoList": { + "label": "Включить инструмент списка задач", + "description": "При включении Roo может создавать и управлять списками задач для отслеживания прогресса. Это помогает организовать сложные задачи в управляемые шаги." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index ae6fad364d9..06878be920c 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -577,6 +577,10 @@ "label": "Eşleşme hassasiyeti", "description": "Bu kaydırıcı, diff'ler uygulanırken kod bölümlerinin ne kadar hassas bir şekilde eşleşmesi gerektiğini kontrol eder. Daha düşük değerler daha esnek eşleşmeye izin verir ancak yanlış değiştirme riskini artırır. %100'ün altındaki değerleri son derece dikkatli kullanın." } + }, + "todoList": { + "label": "Yapılacaklar listesi aracını etkinleştir", + "description": "Etkinleştirildiğinde, Roo görev ilerlemesini takip etmek için yapılacaklar listeleri oluşturabilir ve yönetebilir. Bu, karmaşık görevleri yönetilebilir adımlara organize etmeye yardımcı olur." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 6505d7df0e9..c83b7a6642b 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -577,6 +577,10 @@ "label": "Độ chính xác khớp", "description": "Thanh trượt này kiểm soát mức độ chính xác các phần mã phải khớp khi áp dụng diff. Giá trị thấp hơn cho phép khớp linh hoạt hơn nhưng tăng nguy cơ thay thế không chính xác. Sử dụng giá trị dưới 100% với sự thận trọng cao." } + }, + "todoList": { + "label": "Bật công cụ danh sách việc cần làm", + "description": "Khi được bật, Roo có thể tạo và quản lý danh sách việc cần làm để theo dõi tiến độ công việc. Điều này giúp tổ chức các tác vụ phức tạp thành các bước có thể quản lý được." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index bf3eea0294e..11c469bf778 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -577,6 +577,10 @@ "label": "匹配精度", "description": "控制代码匹配的精确程度。数值越低匹配越宽松(容错率高但风险大),建议保持100%以确保安全。" } + }, + "todoList": { + "label": "启用任务清单工具", + "description": "启用后,Roo 可以创建和管理任务清单来跟踪任务进度。这有助于将复杂任务组织成可管理的步骤。" } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index e2a896dce48..dab70b88b1e 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -577,6 +577,10 @@ "label": "比對精確度", "description": "此滑桿控制套用差異時程式碼區段的比對精確度。較低的數值允許更彈性的比對,但也會增加錯誤取代的風險。使用低於 100% 的數值時請特別謹慎。" } + }, + "todoList": { + "label": "啟用待辦事項清單工具", + "description": "啟用後,Roo 可以建立和管理待辦事項清單來追蹤任務進度。這有助於將複雜任務組織成可管理的步驟。" } }, "experimental": { From adf30522d8d529f1dd75e0220d3361f68af867f9 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 21 Jul 2025 21:57:22 +0000 Subject: [PATCH 3/8] feat: connect todoListEnabled setting to system prompt - Remove update_todo_list tool from system prompt when todoListEnabled is false - Filter out todo list references from custom instructions when disabled - Add tests to verify the behavior --- .../prompts/__tests__/system-prompt.spec.ts | 241 ++++++++++++++---- .../prompts/sections/custom-instructions.ts | 24 +- src/core/prompts/system.ts | 4 +- src/core/prompts/tools/index.ts | 5 + 4 files changed, 215 insertions(+), 59 deletions(-) diff --git a/src/core/prompts/__tests__/system-prompt.spec.ts b/src/core/prompts/__tests__/system-prompt.spec.ts index 175f3ba2652..5002fe77934 100644 --- a/src/core/prompts/__tests__/system-prompt.spec.ts +++ b/src/core/prompts/__tests__/system-prompt.spec.ts @@ -59,64 +59,90 @@ vi.mock("../sections/modes", () => ({ getModesSection: vi.fn().mockImplementation(async () => `====\n\nMODES\n\n- Test modes section`), })) -// Mock the custom instructions -vi.mock("../sections/custom-instructions", () => { - const addCustomInstructions = vi.fn() - return { - addCustomInstructions, - __setMockImplementation: (impl: any) => { - addCustomInstructions.mockImplementation(impl) - }, - } -}) - -// Set up default mock implementation -const customInstructionsMock = vi.mocked(await import("../sections/custom-instructions")) -const { __setMockImplementation } = customInstructionsMock as any -__setMockImplementation( - async ( - modeCustomInstructions: string, - globalCustomInstructions: string, - cwd: string, - mode: string, - options?: { language?: string }, - ) => { - const sections = [] - - // Add language preference if provided - if (options?.language) { - sections.push( - `Language Preference:\nYou should always speak and think in the "${options.language}" language.`, - ) - } - - // Add global instructions first - if (globalCustomInstructions?.trim()) { - sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`) - } - - // Add mode-specific instructions after - if (modeCustomInstructions?.trim()) { - sections.push(`Mode-specific Instructions:\n${modeCustomInstructions}`) - } +// Mock fs/promises to prevent file system access in tests +vi.mock("fs/promises", () => ({ + default: { + readFile: vi.fn().mockRejectedValue({ code: "ENOENT" }), + readdir: vi.fn().mockResolvedValue([]), + stat: vi.fn().mockRejectedValue({ code: "ENOENT" }), + readlink: vi.fn().mockRejectedValue({ code: "ENOENT" }), + }, + readFile: vi.fn().mockRejectedValue({ code: "ENOENT" }), + readdir: vi.fn().mockResolvedValue([]), + stat: vi.fn().mockRejectedValue({ code: "ENOENT" }), + readlink: vi.fn().mockRejectedValue({ code: "ENOENT" }), +})) - // Add rules - const rules = [] - if (mode) { - rules.push(`# Rules from .clinerules-${mode}:\nMock mode-specific rules`) - } - rules.push(`# Rules from .clinerules:\nMock generic rules`) +// Conditionally mock custom instructions +let useRealCustomInstructions = false - if (rules.length > 0) { - sections.push(`Rules:\n${rules.join("\n")}`) - } +vi.mock("../sections/custom-instructions", async () => { + const actual = await vi.importActual( + "../sections/custom-instructions", + ) - const joinedSections = sections.join("\n\n") - return joinedSections - ? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}` - : "" - }, -) + return { + ...actual, + addCustomInstructions: vi + .fn() + .mockImplementation( + async ( + modeCustomInstructions: string, + globalCustomInstructions: string, + cwd: string, + mode: string, + options?: { language?: string; rooIgnoreInstructions?: string; settings?: Record }, + ) => { + if (useRealCustomInstructions) { + // Use the real implementation for todo list tests + return actual.addCustomInstructions( + modeCustomInstructions, + globalCustomInstructions, + cwd, + mode, + options, + ) + } + + // Use mock implementation for other tests + const sections = [] + + // Add language preference if provided + if (options?.language) { + sections.push( + `Language Preference:\nYou should always speak and think in the "${options.language}" language.`, + ) + } + + // Add global instructions first + if (globalCustomInstructions?.trim()) { + sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`) + } + + // Add mode-specific instructions after + if (modeCustomInstructions?.trim()) { + sections.push(`Mode-specific Instructions:\n${modeCustomInstructions}`) + } + + // Add rules + const rules = [] + if (mode) { + rules.push(`# Rules from .clinerules-${mode}:\nMock mode-specific rules`) + } + rules.push(`# Rules from .clinerules:\nMock generic rules`) + + if (rules.length > 0) { + sections.push(`Rules:\n${rules.join("\n")}`) + } + + const joinedSections = sections.join("\n\n") + return joinedSections + ? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}` + : "" + }, + ), + } +}) // Mock vscode language vi.mock("vscode", () => ({ @@ -575,6 +601,111 @@ describe("SYSTEM_PROMPT", () => { expect(prompt.indexOf(modes[0].roleDefinition)).toBeLessThan(prompt.indexOf("TOOL USE")) }) + it("should exclude update_todo_list tool when todoListEnabled is false", async () => { + // Use real custom instructions implementation for this test + useRealCustomInstructions = true + + const settings = { + todoListEnabled: false, + } + + const prompt = await SYSTEM_PROMPT( + mockContext, + "/test/path", + false, // supportsComputerUse + undefined, // mcpHub + undefined, // diffStrategy + undefined, // browserViewportSize + defaultModeSlug, // mode + undefined, // customModePrompts + undefined, // customModes + undefined, // globalCustomInstructions + undefined, // diffEnabled + experiments, + true, // enableMcpServerCreation + undefined, // language + undefined, // rooIgnoreInstructions + undefined, // partialReadsEnabled + settings, // settings + ) + + expect(prompt).not.toContain("update_todo_list") + expect(prompt).not.toContain("## update_todo_list") + + // Reset flag + useRealCustomInstructions = false + }) + + it("should include update_todo_list tool when todoListEnabled is true", async () => { + // Use real custom instructions implementation for this test + useRealCustomInstructions = true + + const settings = { + todoListEnabled: true, + } + + const prompt = await SYSTEM_PROMPT( + mockContext, + "/test/path", + false, // supportsComputerUse + undefined, // mcpHub + undefined, // diffStrategy + undefined, // browserViewportSize + defaultModeSlug, // mode + undefined, // customModePrompts + undefined, // customModes + undefined, // globalCustomInstructions + undefined, // diffEnabled + experiments, + true, // enableMcpServerCreation + undefined, // language + undefined, // rooIgnoreInstructions + undefined, // partialReadsEnabled + settings, // settings + ) + + expect(prompt).toContain("update_todo_list") + expect(prompt).toContain("## update_todo_list") + + // Reset flag + useRealCustomInstructions = false + }) + + it("should include update_todo_list tool when todoListEnabled is undefined", async () => { + // Use real custom instructions implementation for this test + useRealCustomInstructions = true + + const settings = { + // todoListEnabled not set + } + + const prompt = await SYSTEM_PROMPT( + mockContext, + "/test/path", + false, // supportsComputerUse + undefined, // mcpHub + undefined, // diffStrategy + undefined, // browserViewportSize + defaultModeSlug, // mode + undefined, // customModePrompts + undefined, // customModes + undefined, // globalCustomInstructions + undefined, // diffEnabled + experiments, + true, // enableMcpServerCreation + undefined, // language + undefined, // rooIgnoreInstructions + undefined, // partialReadsEnabled + settings, // settings + ) + + expect(prompt).toContain("update_todo_list") + expect(prompt).toContain("## update_todo_list") + + // Reset flag + useRealCustomInstructions = false + }) + afterAll(() => { vi.restoreAllMocks() }) diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index 3c8558a57f4..4e77b18c122 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -200,7 +200,7 @@ export async function addCustomInstructions( globalCustomInstructions: string, cwd: string, mode: string, - options: { language?: string; rooIgnoreInstructions?: string } = {}, + options: { language?: string; rooIgnoreInstructions?: string; settings?: Record } = {}, ): Promise { const sections = [] @@ -259,7 +259,27 @@ export async function addCustomInstructions( // Add mode-specific instructions after if (typeof modeCustomInstructions === "string" && modeCustomInstructions.trim()) { - sections.push(`Mode-specific Instructions:\n${modeCustomInstructions.trim()}`) + let instructions = modeCustomInstructions.trim() + + // Filter out todo list references if todoListEnabled is false + if (options.settings?.todoListEnabled === false) { + // Remove sentences that mention update_todo_list + instructions = instructions + .split("\n") + .map((line) => { + // Skip lines that mention update_todo_list tool + if (line.includes("update_todo_list") || line.includes("todo list using the")) { + return "" + } + return line + }) + .filter((line) => line !== "") + .join("\n") + // Clean up any double line breaks that might have been created + .replace(/\n\n+/g, "\n\n") + } + + sections.push(`Mode-specific Instructions:\n${instructions}`) } // Add rules - include both mode-specific and generic rules if they exist diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 92653cafd24..ea4f43823ea 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -119,7 +119,7 @@ ${getSystemInfoSection(cwd)} ${getObjectiveSection(codeIndexManager, experiments)} -${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions })}` +${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings })}` return basePrompt } @@ -177,7 +177,7 @@ export const SYSTEM_PROMPT = async ( globalCustomInstructions || "", cwd, mode, - { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions }, + { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings }, ) // For file-based prompts, don't include the tool sections diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 3fd5a636a4f..9f4af7f312c 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -109,6 +109,11 @@ export function getToolDescriptionsForMode( tools.delete("codebase_search") } + // Conditionally exclude update_todo_list if disabled in settings + if (settings?.todoListEnabled === false) { + tools.delete("update_todo_list") + } + // Map tool descriptions for allowed tools const descriptions = Array.from(tools).map((toolName) => { const descriptionFn = toolDescriptionMap[toolName] From 9b66b0780aa582de9b5b2b5dd14ea649e12300ad Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Mon, 21 Jul 2025 17:30:38 -0500 Subject: [PATCH 4/8] fix: remove todo list filtering from custom instructions Remove the filtering logic that was excluding update_todo_list references from mode-specific instructions when todoListEnabled was false. The todo list tool availability should be controlled at the tool level, not by filtering instructions. --- .../prompts/sections/custom-instructions.ts | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index 4e77b18c122..63f3735cde3 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -259,27 +259,7 @@ export async function addCustomInstructions( // Add mode-specific instructions after if (typeof modeCustomInstructions === "string" && modeCustomInstructions.trim()) { - let instructions = modeCustomInstructions.trim() - - // Filter out todo list references if todoListEnabled is false - if (options.settings?.todoListEnabled === false) { - // Remove sentences that mention update_todo_list - instructions = instructions - .split("\n") - .map((line) => { - // Skip lines that mention update_todo_list tool - if (line.includes("update_todo_list") || line.includes("todo list using the")) { - return "" - } - return line - }) - .filter((line) => line !== "") - .join("\n") - // Clean up any double line breaks that might have been created - .replace(/\n\n+/g, "\n\n") - } - - sections.push(`Mode-specific Instructions:\n${instructions}`) + sections.push(`Mode-specific Instructions:\n${modeCustomInstructions.trim()}`) } // Add rules - include both mode-specific and generic rules if they exist From 385b8714c626f3aecb58bd52363000f9a2bc57c9 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Mon, 21 Jul 2025 17:49:45 -0500 Subject: [PATCH 5/8] fix: update architect mode to include fallback when update_todo_list is unavailable - Remove filtering logic from custom-instructions.ts as requested - Update architect mode instructions to include fallback to markdown file - Update test to reflect that mode instructions still reference the tool - Update snapshots to include the new fallback instruction --- .../__snapshots__/system-prompt/consistent-system-prompt.snap | 2 ++ .../__snapshots__/system-prompt/with-computer-use-support.snap | 2 ++ .../__snapshots__/system-prompt/with-diff-enabled-false.snap | 2 ++ .../__snapshots__/system-prompt/with-diff-enabled-true.snap | 2 ++ .../system-prompt/with-diff-enabled-undefined.snap | 2 ++ .../system-prompt/with-different-viewport-size.snap | 2 ++ .../__snapshots__/system-prompt/with-mcp-hub-provided.snap | 2 ++ .../__snapshots__/system-prompt/with-undefined-mcp-hub.snap | 2 ++ src/core/prompts/__tests__/system-prompt.spec.ts | 3 ++- src/shared/modes.ts | 2 +- 10 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index e80857d354d..273e43d20b5 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index 576bf0ddd5a..e23dc220b42 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -611,6 +611,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index e80857d354d..273e43d20b5 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index 6fc7ad69cef..ad90a588507 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -643,6 +643,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index e80857d354d..273e43d20b5 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index 6ccccedefeb..005813848f4 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -611,6 +611,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index 4dce0f264a3..8e1b90a1cf8 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -623,6 +623,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index e80857d354d..273e43d20b5 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/system-prompt.spec.ts b/src/core/prompts/__tests__/system-prompt.spec.ts index 5002fe77934..d712b00b0bd 100644 --- a/src/core/prompts/__tests__/system-prompt.spec.ts +++ b/src/core/prompts/__tests__/system-prompt.spec.ts @@ -629,8 +629,9 @@ describe("SYSTEM_PROMPT", () => { settings, // settings ) - expect(prompt).not.toContain("update_todo_list") + // Should not contain the tool description expect(prompt).not.toContain("## update_todo_list") + // Mode instructions will still reference the tool with a fallback to markdown // Reset flag useRealCustomInstructions = false diff --git a/src/shared/modes.ts b/src/shared/modes.ts index 9c790236ebc..168f4ff8671 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -72,7 +72,7 @@ export const modes: readonly ModeConfig[] = [ description: "Plan and design before implementation", groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"], customInstructions: - "1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**", + "1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**", }, { slug: "code", From 97073c61c57c8e3f0b9707c5ac6c654cd5694651 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Mon, 21 Jul 2025 18:06:26 -0500 Subject: [PATCH 6/8] fix: update architect mode instructions with fallback for todo list - Add fallback to write plan to markdown file when update_todo_list tool is not available - Update test snapshots to reflect the new architect mode instructions --- .../add-custom-instructions/architect-mode-prompt.snap | 2 ++ .../add-custom-instructions/mcp-server-creation-disabled.snap | 2 ++ .../add-custom-instructions/mcp-server-creation-enabled.snap | 2 ++ .../add-custom-instructions/partial-reads-enabled.snap | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index e80857d354d..273e43d20b5 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index e80857d354d..273e43d20b5 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index 4dce0f264a3..8e1b90a1cf8 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -623,6 +623,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 0d172d756b1..7ee1ab207f1 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -560,6 +560,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. From 9399da485d1aa97f7ac56d0bcba453466223afa4 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Mon, 21 Jul 2025 18:57:57 -0500 Subject: [PATCH 7/8] refactor: remove unrelated changes from todo list settings PR - Reverted unrelated symlink handling refactoring in custom-instructions.ts - Reverted unrelated test mocking refactoring in system-prompt.spec.ts - Kept only necessary changes for todo list feature: - settings parameter in addCustomInstructions interface - three test cases for todo list enable/disable functionality - updated mock to accept settings parameter --- .../prompts/__tests__/system-prompt.spec.ts | 154 +++++++----------- .../prompts/sections/custom-instructions.ts | 55 +++++-- 2 files changed, 92 insertions(+), 117 deletions(-) diff --git a/src/core/prompts/__tests__/system-prompt.spec.ts b/src/core/prompts/__tests__/system-prompt.spec.ts index d712b00b0bd..7589790efe4 100644 --- a/src/core/prompts/__tests__/system-prompt.spec.ts +++ b/src/core/prompts/__tests__/system-prompt.spec.ts @@ -59,91 +59,65 @@ vi.mock("../sections/modes", () => ({ getModesSection: vi.fn().mockImplementation(async () => `====\n\nMODES\n\n- Test modes section`), })) -// Mock fs/promises to prevent file system access in tests -vi.mock("fs/promises", () => ({ - default: { - readFile: vi.fn().mockRejectedValue({ code: "ENOENT" }), - readdir: vi.fn().mockResolvedValue([]), - stat: vi.fn().mockRejectedValue({ code: "ENOENT" }), - readlink: vi.fn().mockRejectedValue({ code: "ENOENT" }), - }, - readFile: vi.fn().mockRejectedValue({ code: "ENOENT" }), - readdir: vi.fn().mockResolvedValue([]), - stat: vi.fn().mockRejectedValue({ code: "ENOENT" }), - readlink: vi.fn().mockRejectedValue({ code: "ENOENT" }), -})) - -// Conditionally mock custom instructions -let useRealCustomInstructions = false - -vi.mock("../sections/custom-instructions", async () => { - const actual = await vi.importActual( - "../sections/custom-instructions", - ) - +// Mock the custom instructions +vi.mock("../sections/custom-instructions", () => { + const addCustomInstructions = vi.fn() return { - ...actual, - addCustomInstructions: vi - .fn() - .mockImplementation( - async ( - modeCustomInstructions: string, - globalCustomInstructions: string, - cwd: string, - mode: string, - options?: { language?: string; rooIgnoreInstructions?: string; settings?: Record }, - ) => { - if (useRealCustomInstructions) { - // Use the real implementation for todo list tests - return actual.addCustomInstructions( - modeCustomInstructions, - globalCustomInstructions, - cwd, - mode, - options, - ) - } - - // Use mock implementation for other tests - const sections = [] - - // Add language preference if provided - if (options?.language) { - sections.push( - `Language Preference:\nYou should always speak and think in the "${options.language}" language.`, - ) - } - - // Add global instructions first - if (globalCustomInstructions?.trim()) { - sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`) - } - - // Add mode-specific instructions after - if (modeCustomInstructions?.trim()) { - sections.push(`Mode-specific Instructions:\n${modeCustomInstructions}`) - } - - // Add rules - const rules = [] - if (mode) { - rules.push(`# Rules from .clinerules-${mode}:\nMock mode-specific rules`) - } - rules.push(`# Rules from .clinerules:\nMock generic rules`) - - if (rules.length > 0) { - sections.push(`Rules:\n${rules.join("\n")}`) - } - - const joinedSections = sections.join("\n\n") - return joinedSections - ? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}` - : "" - }, - ), + addCustomInstructions, + __setMockImplementation: (impl: any) => { + addCustomInstructions.mockImplementation(impl) + }, } }) +// Set up default mock implementation +const customInstructionsMock = vi.mocked(await import("../sections/custom-instructions")) +const { __setMockImplementation } = customInstructionsMock as any +__setMockImplementation( + async ( + modeCustomInstructions: string, + globalCustomInstructions: string, + cwd: string, + mode: string, + options?: { language?: string; rooIgnoreInstructions?: string; settings?: Record }, + ) => { + const sections = [] + + // Add language preference if provided + if (options?.language) { + sections.push( + `Language Preference:\nYou should always speak and think in the "${options.language}" language.`, + ) + } + + // Add global instructions first + if (globalCustomInstructions?.trim()) { + sections.push(`Global Instructions:\n${globalCustomInstructions.trim()}`) + } + + // Add mode-specific instructions after + if (modeCustomInstructions?.trim()) { + sections.push(`Mode-specific Instructions:\n${modeCustomInstructions}`) + } + + // Add rules + const rules = [] + if (mode) { + rules.push(`# Rules from .clinerules-${mode}:\nMock mode-specific rules`) + } + rules.push(`# Rules from .clinerules:\nMock generic rules`) + + if (rules.length > 0) { + sections.push(`Rules:\n${rules.join("\n")}`) + } + + const joinedSections = sections.join("\n\n") + return joinedSections + ? `\n====\n\nUSER'S CUSTOM INSTRUCTIONS\n\nThe following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.\n\n${joinedSections}` + : "" + }, +) + // Mock vscode language vi.mock("vscode", () => ({ env: { @@ -602,9 +576,6 @@ describe("SYSTEM_PROMPT", () => { }) it("should exclude update_todo_list tool when todoListEnabled is false", async () => { - // Use real custom instructions implementation for this test - useRealCustomInstructions = true - const settings = { todoListEnabled: false, } @@ -632,15 +603,9 @@ describe("SYSTEM_PROMPT", () => { // Should not contain the tool description expect(prompt).not.toContain("## update_todo_list") // Mode instructions will still reference the tool with a fallback to markdown - - // Reset flag - useRealCustomInstructions = false }) it("should include update_todo_list tool when todoListEnabled is true", async () => { - // Use real custom instructions implementation for this test - useRealCustomInstructions = true - const settings = { todoListEnabled: true, } @@ -667,15 +632,9 @@ describe("SYSTEM_PROMPT", () => { expect(prompt).toContain("update_todo_list") expect(prompt).toContain("## update_todo_list") - - // Reset flag - useRealCustomInstructions = false }) it("should include update_todo_list tool when todoListEnabled is undefined", async () => { - // Use real custom instructions implementation for this test - useRealCustomInstructions = true - const settings = { // todoListEnabled not set } @@ -702,9 +661,6 @@ describe("SYSTEM_PROMPT", () => { expect(prompt).toContain("update_todo_list") expect(prompt).toContain("## update_todo_list") - - // Reset flag - useRealCustomInstructions = false }) afterAll(() => { diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index 63f3735cde3..a78882cc904 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -44,7 +44,7 @@ const MAX_DEPTH = 5 async function resolveDirectoryEntry( entry: Dirent, dirPath: string, - filePaths: string[], + fileInfo: Array<{ originalPath: string; resolvedPath: string }>, depth: number, ): Promise { // Avoid cyclic symlinks @@ -54,44 +54,49 @@ async function resolveDirectoryEntry( const fullPath = path.resolve(entry.parentPath || dirPath, entry.name) if (entry.isFile()) { - // Regular file - filePaths.push(fullPath) + // Regular file - both original and resolved paths are the same + fileInfo.push({ originalPath: fullPath, resolvedPath: fullPath }) } else if (entry.isSymbolicLink()) { // Await the resolution of the symbolic link - await resolveSymLink(fullPath, filePaths, depth + 1) + await resolveSymLink(fullPath, fileInfo, depth + 1) } } /** * Recursively resolve a symbolic link and collect file paths */ -async function resolveSymLink(fullPath: string, filePaths: string[], depth: number): Promise { +async function resolveSymLink( + symlinkPath: string, + fileInfo: Array<{ originalPath: string; resolvedPath: string }>, + depth: number, +): Promise { // Avoid cyclic symlinks if (depth > MAX_DEPTH) { return } try { // Get the symlink target - const linkTarget = await fs.readlink(fullPath) + const linkTarget = await fs.readlink(symlinkPath) // Resolve the target path (relative to the symlink location) - const resolvedTarget = path.resolve(path.dirname(fullPath), linkTarget) + const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget) // Check if the target is a file const stats = await fs.stat(resolvedTarget) if (stats.isFile()) { - filePaths.push(resolvedTarget) + // For symlinks to files, store the symlink path as original and target as resolved + fileInfo.push({ originalPath: symlinkPath, resolvedPath: resolvedTarget }) } else if (stats.isDirectory()) { const anotherEntries = await fs.readdir(resolvedTarget, { withFileTypes: true, recursive: true }) // Collect promises for recursive calls within the directory const directoryPromises: Promise[] = [] for (const anotherEntry of anotherEntries) { - directoryPromises.push(resolveDirectoryEntry(anotherEntry, resolvedTarget, filePaths, depth + 1)) + directoryPromises.push(resolveDirectoryEntry(anotherEntry, resolvedTarget, fileInfo, depth + 1)) } // Wait for all entries in the resolved directory to be processed await Promise.all(directoryPromises) } else if (stats.isSymbolicLink()) { // Handle nested symlinks by awaiting the recursive call - await resolveSymLink(resolvedTarget, filePaths, depth + 1) + await resolveSymLink(resolvedTarget, fileInfo, depth + 1) } } catch (err) { // Skip invalid symlinks @@ -106,29 +111,31 @@ async function readTextFilesFromDirectory(dirPath: string): Promise = [] // Collect promises for the initial resolution calls const initialPromises: Promise[] = [] for (const entry of entries) { - initialPromises.push(resolveDirectoryEntry(entry, dirPath, filePaths, 0)) + initialPromises.push(resolveDirectoryEntry(entry, dirPath, fileInfo, 0)) } // Wait for all asynchronous operations (including recursive ones) to complete await Promise.all(initialPromises) const fileContents = await Promise.all( - filePaths.map(async (file) => { + fileInfo.map(async ({ originalPath, resolvedPath }) => { try { // Check if it's a file (not a directory) - const stats = await fs.stat(file) + const stats = await fs.stat(resolvedPath) if (stats.isFile()) { // Filter out cache files and system files that shouldn't be in rules - if (!shouldIncludeRuleFile(file)) { + if (!shouldIncludeRuleFile(resolvedPath)) { return null } - const content = await safeReadFile(file) - return { filename: file, content } + const content = await safeReadFile(resolvedPath) + // Use resolvedPath for display to maintain existing behavior + return { filename: resolvedPath, content, sortKey: originalPath } } return null } catch (err) { @@ -138,7 +145,19 @@ async function readTextFilesFromDirectory(dirPath: string): Promise item !== null) + const filteredFiles = fileContents.filter( + (item): item is { filename: string; content: string; sortKey: string } => item !== null, + ) + + // Sort files alphabetically by the original filename (case-insensitive) to ensure consistent order + // For symlinks, this will use the symlink name, not the target name + return filteredFiles + .sort((a, b) => { + const filenameA = path.basename(a.sortKey).toLowerCase() + const filenameB = path.basename(b.sortKey).toLowerCase() + return filenameA.localeCompare(filenameB) + }) + .map(({ filename, content }) => ({ filename, content })) } catch (err) { return [] } From 182c3b6a7a9aa638c156d90ba1f90660319c8427 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 21 Jul 2025 23:09:58 -0400 Subject: [PATCH 8/8] Migrate profiles --- src/core/config/ProviderSettingsManager.ts | 21 +++++++++ .../__tests__/ProviderSettingsManager.spec.ts | 43 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 7823a3040af..350c8136f28 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -28,6 +28,7 @@ export const providerProfilesSchema = z.object({ diffSettingsMigrated: z.boolean().optional(), openAiHeadersMigrated: z.boolean().optional(), consecutiveMistakeLimitMigrated: z.boolean().optional(), + todoListEnabledMigrated: z.boolean().optional(), }) .optional(), }) @@ -51,6 +52,7 @@ export class ProviderSettingsManager { diffSettingsMigrated: true, // Mark as migrated on fresh installs openAiHeadersMigrated: true, // Mark as migrated on fresh installs consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs + todoListEnabledMigrated: true, // Mark as migrated on fresh installs }, } @@ -117,6 +119,7 @@ export class ProviderSettingsManager { diffSettingsMigrated: false, openAiHeadersMigrated: false, consecutiveMistakeLimitMigrated: false, + todoListEnabledMigrated: false, } // Initialize with default values isDirty = true } @@ -145,6 +148,12 @@ export class ProviderSettingsManager { isDirty = true } + if (!providerProfiles.migrations.todoListEnabledMigrated) { + await this.migrateTodoListEnabled(providerProfiles) + providerProfiles.migrations.todoListEnabledMigrated = true + isDirty = true + } + if (isDirty) { await this.store(providerProfiles) } @@ -250,6 +259,18 @@ export class ProviderSettingsManager { } } + private async migrateTodoListEnabled(providerProfiles: ProviderProfiles) { + try { + for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) { + if (apiConfig.todoListEnabled === undefined) { + apiConfig.todoListEnabled = true + } + } + } catch (error) { + console.error(`[MigrateTodoListEnabled] Failed to migrate todo list enabled setting:`, error) + } + } + /** * List all available configs with metadata. */ diff --git a/src/core/config/__tests__/ProviderSettingsManager.spec.ts b/src/core/config/__tests__/ProviderSettingsManager.spec.ts index 6d24c631016..e52c1974b60 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.spec.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.spec.ts @@ -67,6 +67,7 @@ describe("ProviderSettingsManager", () => { diffSettingsMigrated: true, openAiHeadersMigrated: true, consecutiveMistakeLimitMigrated: true, + todoListEnabledMigrated: true, }, }), ) @@ -186,6 +187,48 @@ describe("ProviderSettingsManager", () => { expect(storedConfig.migrations.consecutiveMistakeLimitMigrated).toEqual(true) }) + it("should call migrateTodoListEnabled if it has not done so already", async () => { + mockSecrets.get.mockResolvedValue( + JSON.stringify({ + currentApiConfigName: "default", + apiConfigs: { + default: { + config: {}, + id: "default", + todoListEnabled: undefined, + }, + test: { + apiProvider: "anthropic", + todoListEnabled: undefined, + }, + existing: { + apiProvider: "anthropic", + // this should not really be possible, unless someone has loaded a hand edited config, + // but we don't overwrite so we'll check that + todoListEnabled: false, + }, + }, + migrations: { + rateLimitSecondsMigrated: true, + diffSettingsMigrated: true, + openAiHeadersMigrated: true, + consecutiveMistakeLimitMigrated: true, + todoListEnabledMigrated: false, + }, + }), + ) + + await providerSettingsManager.initialize() + + // Get the last call to store, which should contain the migrated config + const calls = mockSecrets.store.mock.calls + const storedConfig = JSON.parse(calls[calls.length - 1][1]) + expect(storedConfig.apiConfigs.default.todoListEnabled).toEqual(true) + expect(storedConfig.apiConfigs.test.todoListEnabled).toEqual(true) + expect(storedConfig.apiConfigs.existing.todoListEnabled).toEqual(false) + expect(storedConfig.migrations.todoListEnabledMigrated).toEqual(true) + }) + it("should throw error if secrets storage fails", async () => { mockSecrets.get.mockRejectedValue(new Error("Storage failed"))