@butlerw/vellum 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -0
  3. package/__fixtures__/responses/code-generation.json +42 -0
  4. package/__fixtures__/responses/error-response.json +20 -0
  5. package/__fixtures__/responses/hello-world.json +32 -0
  6. package/dist/auth-6MCXESOH.js +26 -0
  7. package/dist/chunk-SECXJGWA.js +597 -0
  8. package/dist/index.js +34023 -0
  9. package/package.json +67 -0
  10. package/src/__tests__/commands.e2e.test.ts +728 -0
  11. package/src/__tests__/credentials.test.ts +713 -0
  12. package/src/__tests__/mode-e2e.test.ts +391 -0
  13. package/src/__tests__/tui-integration.test.tsx +1271 -0
  14. package/src/agents/__tests__/task-persistence.test.ts +235 -0
  15. package/src/agents/commands/delegate.ts +240 -0
  16. package/src/agents/commands/index.ts +10 -0
  17. package/src/agents/commands/resume.ts +335 -0
  18. package/src/agents/index.ts +29 -0
  19. package/src/agents/task-persistence.ts +272 -0
  20. package/src/agents/task-resumption.ts +242 -0
  21. package/src/app.tsx +4737 -0
  22. package/src/commands/__tests__/.gitkeep +1 -0
  23. package/src/commands/__tests__/agents.test.ts +606 -0
  24. package/src/commands/__tests__/auth.test.ts +626 -0
  25. package/src/commands/__tests__/autocomplete.test.ts +683 -0
  26. package/src/commands/__tests__/batch.test.ts +287 -0
  27. package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
  28. package/src/commands/__tests__/completion.test.ts +238 -0
  29. package/src/commands/__tests__/core.test.ts +363 -0
  30. package/src/commands/__tests__/executor.test.ts +496 -0
  31. package/src/commands/__tests__/exit-codes.test.ts +220 -0
  32. package/src/commands/__tests__/init.test.ts +243 -0
  33. package/src/commands/__tests__/language.test.ts +353 -0
  34. package/src/commands/__tests__/mode-cli.test.ts +667 -0
  35. package/src/commands/__tests__/model.test.ts +277 -0
  36. package/src/commands/__tests__/parser.test.ts +493 -0
  37. package/src/commands/__tests__/performance.bench.ts +380 -0
  38. package/src/commands/__tests__/registry.test.ts +534 -0
  39. package/src/commands/__tests__/resume.test.ts +449 -0
  40. package/src/commands/__tests__/security.test.ts +845 -0
  41. package/src/commands/__tests__/stream-json.test.ts +372 -0
  42. package/src/commands/__tests__/user-commands.test.ts +597 -0
  43. package/src/commands/adapters.ts +267 -0
  44. package/src/commands/agent.ts +395 -0
  45. package/src/commands/agents/generate.ts +506 -0
  46. package/src/commands/agents/index.ts +272 -0
  47. package/src/commands/agents/show.ts +271 -0
  48. package/src/commands/agents/validate.ts +387 -0
  49. package/src/commands/auth.ts +883 -0
  50. package/src/commands/autocomplete.ts +480 -0
  51. package/src/commands/batch/command.ts +388 -0
  52. package/src/commands/batch/executor.ts +361 -0
  53. package/src/commands/batch/index.ts +12 -0
  54. package/src/commands/commit.ts +235 -0
  55. package/src/commands/completion/index.ts +371 -0
  56. package/src/commands/condense.ts +191 -0
  57. package/src/commands/config.ts +344 -0
  58. package/src/commands/context-provider.ts +173 -0
  59. package/src/commands/copy.ts +329 -0
  60. package/src/commands/core/clear.ts +38 -0
  61. package/src/commands/core/exit.ts +43 -0
  62. package/src/commands/core/help.ts +354 -0
  63. package/src/commands/core/index.ts +15 -0
  64. package/src/commands/cost.ts +179 -0
  65. package/src/commands/credentials.tsx +618 -0
  66. package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
  67. package/src/commands/custom-agents/create.ts +377 -0
  68. package/src/commands/custom-agents/export.ts +135 -0
  69. package/src/commands/custom-agents/import.ts +199 -0
  70. package/src/commands/custom-agents/index.ts +372 -0
  71. package/src/commands/custom-agents/info.ts +318 -0
  72. package/src/commands/custom-agents/list.ts +267 -0
  73. package/src/commands/custom-agents/validate.ts +388 -0
  74. package/src/commands/diff-mode.ts +241 -0
  75. package/src/commands/env.ts +53 -0
  76. package/src/commands/executor.ts +579 -0
  77. package/src/commands/exit-codes.ts +202 -0
  78. package/src/commands/index.ts +701 -0
  79. package/src/commands/init/index.ts +15 -0
  80. package/src/commands/init/prompts.ts +366 -0
  81. package/src/commands/init/templates/commands-readme.md +80 -0
  82. package/src/commands/init/templates/example-command.md +79 -0
  83. package/src/commands/init/templates/example-skill.md +168 -0
  84. package/src/commands/init/templates/example-workflow.md +101 -0
  85. package/src/commands/init/templates/prompts-readme.md +52 -0
  86. package/src/commands/init/templates/rules-readme.md +63 -0
  87. package/src/commands/init/templates/skills-readme.md +83 -0
  88. package/src/commands/init/templates/workflows-readme.md +94 -0
  89. package/src/commands/init.ts +391 -0
  90. package/src/commands/install.ts +90 -0
  91. package/src/commands/language.ts +191 -0
  92. package/src/commands/loaders/.gitkeep +1 -0
  93. package/src/commands/lsp.ts +199 -0
  94. package/src/commands/markdown-commands.ts +253 -0
  95. package/src/commands/mcp.ts +588 -0
  96. package/src/commands/memory/export.ts +341 -0
  97. package/src/commands/memory/index.ts +148 -0
  98. package/src/commands/memory/list.ts +261 -0
  99. package/src/commands/memory/search.ts +346 -0
  100. package/src/commands/memory/utils.ts +15 -0
  101. package/src/commands/metrics.ts +75 -0
  102. package/src/commands/migrate/index.ts +16 -0
  103. package/src/commands/migrate/prompts.ts +477 -0
  104. package/src/commands/mode.ts +331 -0
  105. package/src/commands/model.ts +298 -0
  106. package/src/commands/onboard.ts +205 -0
  107. package/src/commands/open.ts +169 -0
  108. package/src/commands/output/stream-json.ts +373 -0
  109. package/src/commands/parser/chain-parser.ts +370 -0
  110. package/src/commands/parser/index.ts +29 -0
  111. package/src/commands/parser/pipe-parser.ts +480 -0
  112. package/src/commands/parser.ts +588 -0
  113. package/src/commands/persistence.ts +355 -0
  114. package/src/commands/progress.ts +18 -0
  115. package/src/commands/prompt/index.ts +17 -0
  116. package/src/commands/prompt/validate.ts +621 -0
  117. package/src/commands/prompt-priority.ts +401 -0
  118. package/src/commands/registry.ts +374 -0
  119. package/src/commands/sandbox/index.ts +131 -0
  120. package/src/commands/security/index.ts +21 -0
  121. package/src/commands/security/input-sanitizer.ts +168 -0
  122. package/src/commands/security/permission-checker.ts +456 -0
  123. package/src/commands/security/sensitive-data.ts +350 -0
  124. package/src/commands/session/delete.ts +38 -0
  125. package/src/commands/session/export.ts +39 -0
  126. package/src/commands/session/index.ts +26 -0
  127. package/src/commands/session/list.ts +26 -0
  128. package/src/commands/session/resume.ts +562 -0
  129. package/src/commands/session/search.ts +434 -0
  130. package/src/commands/session/show.ts +26 -0
  131. package/src/commands/settings.ts +368 -0
  132. package/src/commands/setup.ts +23 -0
  133. package/src/commands/shell/index.ts +16 -0
  134. package/src/commands/shell/setup.ts +422 -0
  135. package/src/commands/shell-init.ts +50 -0
  136. package/src/commands/shell-integration/index.ts +194 -0
  137. package/src/commands/skill.ts +1220 -0
  138. package/src/commands/spec.ts +558 -0
  139. package/src/commands/status.ts +246 -0
  140. package/src/commands/theme.ts +211 -0
  141. package/src/commands/think.ts +551 -0
  142. package/src/commands/trust.ts +211 -0
  143. package/src/commands/tutorial.ts +522 -0
  144. package/src/commands/types.ts +512 -0
  145. package/src/commands/update.ts +274 -0
  146. package/src/commands/usage.ts +213 -0
  147. package/src/commands/user-commands.ts +630 -0
  148. package/src/commands/utils.ts +142 -0
  149. package/src/commands/vim.ts +152 -0
  150. package/src/commands/workflow.ts +257 -0
  151. package/src/components/header.tsx +25 -0
  152. package/src/components/input.tsx +25 -0
  153. package/src/components/message-list.tsx +32 -0
  154. package/src/components/status-bar.tsx +23 -0
  155. package/src/index.tsx +614 -0
  156. package/src/onboarding/__tests__/tutorial.test.ts +740 -0
  157. package/src/onboarding/index.ts +69 -0
  158. package/src/onboarding/tips/index.ts +9 -0
  159. package/src/onboarding/tips/tip-engine.ts +459 -0
  160. package/src/onboarding/tutorial/index.ts +88 -0
  161. package/src/onboarding/tutorial/lessons/basics.ts +151 -0
  162. package/src/onboarding/tutorial/lessons/index.ts +151 -0
  163. package/src/onboarding/tutorial/lessons/modes.ts +230 -0
  164. package/src/onboarding/tutorial/lessons/tools.ts +172 -0
  165. package/src/onboarding/tutorial/progress-tracker.ts +350 -0
  166. package/src/onboarding/tutorial/storage.ts +249 -0
  167. package/src/onboarding/tutorial/tutorial-system.ts +462 -0
  168. package/src/onboarding/tutorial/types.ts +310 -0
  169. package/src/orchestrator-singleton.ts +129 -0
  170. package/src/shutdown.ts +33 -0
  171. package/src/test/e2e/assertions.ts +267 -0
  172. package/src/test/e2e/fixtures.ts +204 -0
  173. package/src/test/e2e/harness.ts +575 -0
  174. package/src/test/e2e/index.ts +57 -0
  175. package/src/test/e2e/types.ts +228 -0
  176. package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
  177. package/src/test/fixtures/fake-response-loader.ts +314 -0
  178. package/src/test/fixtures/index.ts +20 -0
  179. package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
  180. package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
  181. package/src/tui/__tests__/mode-components.test.tsx +395 -0
  182. package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
  183. package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
  184. package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
  185. package/src/tui/adapters/agent-adapter.ts +1008 -0
  186. package/src/tui/adapters/index.ts +48 -0
  187. package/src/tui/adapters/message-adapter.ts +315 -0
  188. package/src/tui/adapters/persistence-bridge.ts +331 -0
  189. package/src/tui/adapters/session-adapter.ts +419 -0
  190. package/src/tui/buffered-stdout.ts +223 -0
  191. package/src/tui/components/AgentProgress.tsx +424 -0
  192. package/src/tui/components/Banner/AsciiArt.ts +160 -0
  193. package/src/tui/components/Banner/Banner.tsx +355 -0
  194. package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
  195. package/src/tui/components/Banner/ShimmerText.tsx +193 -0
  196. package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
  197. package/src/tui/components/Banner/index.ts +61 -0
  198. package/src/tui/components/Banner/useShimmer.ts +241 -0
  199. package/src/tui/components/ChatView.tsx +11 -0
  200. package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
  201. package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
  202. package/src/tui/components/Checkpoint/index.ts +19 -0
  203. package/src/tui/components/CostDisplay.tsx +226 -0
  204. package/src/tui/components/InitErrorBanner.tsx +122 -0
  205. package/src/tui/components/Input/Autocomplete.tsx +603 -0
  206. package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
  207. package/src/tui/components/Input/HighlightedText.tsx +236 -0
  208. package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
  209. package/src/tui/components/Input/TextInput.tsx +1002 -0
  210. package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
  211. package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
  212. package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
  213. package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
  214. package/src/tui/components/Input/highlight.ts +362 -0
  215. package/src/tui/components/Input/index.ts +36 -0
  216. package/src/tui/components/Input/slash-command-utils.ts +135 -0
  217. package/src/tui/components/Layout.tsx +432 -0
  218. package/src/tui/components/McpPanel.tsx +137 -0
  219. package/src/tui/components/MemoryPanel.tsx +448 -0
  220. package/src/tui/components/Messages/CodeBlock.tsx +527 -0
  221. package/src/tui/components/Messages/DiffView.tsx +679 -0
  222. package/src/tui/components/Messages/ImageReference.tsx +89 -0
  223. package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
  224. package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
  225. package/src/tui/components/Messages/MessageBubble.tsx +270 -0
  226. package/src/tui/components/Messages/MessageList.tsx +1719 -0
  227. package/src/tui/components/Messages/StreamingText.tsx +216 -0
  228. package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
  229. package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
  230. package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
  231. package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
  232. package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
  233. package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
  234. package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
  235. package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
  236. package/src/tui/components/Messages/index.ts +25 -0
  237. package/src/tui/components/ModeIndicator.tsx +177 -0
  238. package/src/tui/components/ModeSelector.tsx +216 -0
  239. package/src/tui/components/ModelSelector.tsx +339 -0
  240. package/src/tui/components/OnboardingWizard.tsx +670 -0
  241. package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
  242. package/src/tui/components/RateLimitIndicator.tsx +82 -0
  243. package/src/tui/components/ScreenReaderLayout.tsx +295 -0
  244. package/src/tui/components/SettingsPanel.tsx +643 -0
  245. package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
  246. package/src/tui/components/Sidebar/index.ts +9 -0
  247. package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
  248. package/src/tui/components/Status/index.ts +12 -0
  249. package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
  250. package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
  251. package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
  252. package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
  253. package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
  254. package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
  255. package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
  256. package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
  257. package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
  258. package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
  259. package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
  260. package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
  261. package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
  262. package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
  263. package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
  264. package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
  265. package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
  266. package/src/tui/components/StatusBar/index.ts +60 -0
  267. package/src/tui/components/TipBanner.tsx +115 -0
  268. package/src/tui/components/TodoItem.tsx +208 -0
  269. package/src/tui/components/TodoPanel.tsx +455 -0
  270. package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
  271. package/src/tui/components/Tools/OptionSelector.tsx +160 -0
  272. package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
  273. package/src/tui/components/Tools/ToolParams.tsx +483 -0
  274. package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
  275. package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
  276. package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
  277. package/src/tui/components/Tools/index.ts +21 -0
  278. package/src/tui/components/TrustPrompt.tsx +279 -0
  279. package/src/tui/components/UpdateBanner.tsx +166 -0
  280. package/src/tui/components/VimModeIndicator.tsx +112 -0
  281. package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
  282. package/src/tui/components/backtrack/index.ts +13 -0
  283. package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
  284. package/src/tui/components/common/CostWarning.tsx +294 -0
  285. package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
  286. package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
  287. package/src/tui/components/common/ErrorBoundary.tsx +140 -0
  288. package/src/tui/components/common/GradientText.tsx +224 -0
  289. package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
  290. package/src/tui/components/common/HotkeyHints.tsx +70 -0
  291. package/src/tui/components/common/MaxSizedBox.tsx +354 -0
  292. package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
  293. package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
  294. package/src/tui/components/common/ScrollIndicator.tsx +160 -0
  295. package/src/tui/components/common/Spinner.tsx +342 -0
  296. package/src/tui/components/common/StreamingIndicator.tsx +316 -0
  297. package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
  298. package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
  299. package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
  300. package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
  301. package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
  302. package/src/tui/components/common/VirtualizedList/index.ts +30 -0
  303. package/src/tui/components/common/VirtualizedList/types.ts +107 -0
  304. package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
  305. package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
  306. package/src/tui/components/common/index.ts +110 -0
  307. package/src/tui/components/index.ts +79 -0
  308. package/src/tui/components/session/CheckpointPanel.tsx +323 -0
  309. package/src/tui/components/session/RollbackDialog.tsx +169 -0
  310. package/src/tui/components/session/SessionItem.tsx +136 -0
  311. package/src/tui/components/session/SessionListPanel.tsx +252 -0
  312. package/src/tui/components/session/SessionPicker.tsx +449 -0
  313. package/src/tui/components/session/SessionPreview.tsx +240 -0
  314. package/src/tui/components/session/__tests__/session.test.tsx +408 -0
  315. package/src/tui/components/session/index.ts +28 -0
  316. package/src/tui/components/session/types.ts +116 -0
  317. package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
  318. package/src/tui/components/theme/index.ts +227 -0
  319. package/src/tui/components/theme/tokens.ts +484 -0
  320. package/src/tui/config/defaults.ts +134 -0
  321. package/src/tui/config/index.ts +17 -0
  322. package/src/tui/context/AnimationContext.tsx +284 -0
  323. package/src/tui/context/AppContext.tsx +349 -0
  324. package/src/tui/context/BracketedPasteContext.tsx +372 -0
  325. package/src/tui/context/LspContext.tsx +192 -0
  326. package/src/tui/context/McpContext.tsx +325 -0
  327. package/src/tui/context/MessagesContext.tsx +870 -0
  328. package/src/tui/context/OverflowContext.tsx +213 -0
  329. package/src/tui/context/RateLimitContext.tsx +108 -0
  330. package/src/tui/context/ResilienceContext.tsx +275 -0
  331. package/src/tui/context/RootProvider.tsx +136 -0
  332. package/src/tui/context/ScrollContext.tsx +331 -0
  333. package/src/tui/context/ToolsContext.tsx +702 -0
  334. package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
  335. package/src/tui/context/index.ts +140 -0
  336. package/src/tui/enterprise-integration.ts +282 -0
  337. package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
  338. package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
  339. package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
  340. package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
  341. package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
  342. package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
  343. package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
  344. package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
  345. package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
  346. package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
  347. package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
  348. package/src/tui/hooks/index.ts +252 -0
  349. package/src/tui/hooks/useAgentLoop.ts +712 -0
  350. package/src/tui/hooks/useAlternateBuffer.ts +398 -0
  351. package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
  352. package/src/tui/hooks/useBacktrack.ts +443 -0
  353. package/src/tui/hooks/useBracketedPaste.ts +104 -0
  354. package/src/tui/hooks/useCollapsible.ts +240 -0
  355. package/src/tui/hooks/useCopyMode.ts +382 -0
  356. package/src/tui/hooks/useCostSummary.ts +75 -0
  357. package/src/tui/hooks/useDesktopNotification.ts +414 -0
  358. package/src/tui/hooks/useDiffMode.ts +44 -0
  359. package/src/tui/hooks/useFileChangeStats.ts +110 -0
  360. package/src/tui/hooks/useFileSuggestions.ts +284 -0
  361. package/src/tui/hooks/useFlickerDetector.ts +250 -0
  362. package/src/tui/hooks/useGitStatus.ts +200 -0
  363. package/src/tui/hooks/useHotkeys.ts +579 -0
  364. package/src/tui/hooks/useImagePaste.ts +114 -0
  365. package/src/tui/hooks/useInputHighlight.ts +145 -0
  366. package/src/tui/hooks/useInputHistory.ts +246 -0
  367. package/src/tui/hooks/useKeyboardScroll.ts +209 -0
  368. package/src/tui/hooks/useLineBuffer.ts +356 -0
  369. package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
  370. package/src/tui/hooks/useModeController.ts +167 -0
  371. package/src/tui/hooks/useModeShortcuts.ts +196 -0
  372. package/src/tui/hooks/usePermissionHandler.ts +146 -0
  373. package/src/tui/hooks/usePersistence.ts +480 -0
  374. package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
  375. package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
  376. package/src/tui/hooks/useProviderStatus.ts +270 -0
  377. package/src/tui/hooks/useRateLimitStatus.ts +90 -0
  378. package/src/tui/hooks/useScreenReader.ts +315 -0
  379. package/src/tui/hooks/useScrollController.ts +450 -0
  380. package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
  381. package/src/tui/hooks/useSidebarPanelData.ts +115 -0
  382. package/src/tui/hooks/useSmoothScroll.ts +202 -0
  383. package/src/tui/hooks/useSnapshots.ts +300 -0
  384. package/src/tui/hooks/useStateAndRef.ts +50 -0
  385. package/src/tui/hooks/useTerminalSize.ts +206 -0
  386. package/src/tui/hooks/useToolApprovalController.ts +91 -0
  387. package/src/tui/hooks/useVim.ts +334 -0
  388. package/src/tui/hooks/useWorkspace.ts +56 -0
  389. package/src/tui/i18n/__tests__/init.test.ts +278 -0
  390. package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
  391. package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
  392. package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
  393. package/src/tui/i18n/index.ts +72 -0
  394. package/src/tui/i18n/init.ts +131 -0
  395. package/src/tui/i18n/language-config.ts +106 -0
  396. package/src/tui/i18n/locale-detection.ts +173 -0
  397. package/src/tui/i18n/settings-integration.ts +557 -0
  398. package/src/tui/i18n/tui-namespace.ts +538 -0
  399. package/src/tui/i18n/types.ts +312 -0
  400. package/src/tui/index.ts +43 -0
  401. package/src/tui/lsp-integration.ts +409 -0
  402. package/src/tui/metrics-integration.ts +366 -0
  403. package/src/tui/plugins.ts +383 -0
  404. package/src/tui/resilience.ts +342 -0
  405. package/src/tui/sandbox-integration.ts +317 -0
  406. package/src/tui/services/clipboard.ts +348 -0
  407. package/src/tui/services/fuzzy-search.ts +441 -0
  408. package/src/tui/services/index.ts +72 -0
  409. package/src/tui/services/markdown-renderer.ts +565 -0
  410. package/src/tui/services/open-external.ts +247 -0
  411. package/src/tui/services/syntax-highlighter.ts +483 -0
  412. package/src/tui/slash-commands.ts +12 -0
  413. package/src/tui/theme/index.ts +15 -0
  414. package/src/tui/theme/provider.tsx +206 -0
  415. package/src/tui/tip-integration.ts +300 -0
  416. package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
  417. package/src/tui/types/ink-extended.ts +87 -0
  418. package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
  419. package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
  420. package/src/tui/utils/__tests__/text-width.test.ts +158 -0
  421. package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
  422. package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
  423. package/src/tui/utils/bracketedPaste.ts +107 -0
  424. package/src/tui/utils/cursor-manager.ts +131 -0
  425. package/src/tui/utils/detectTerminal.ts +596 -0
  426. package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
  427. package/src/tui/utils/heightEstimator.ts +198 -0
  428. package/src/tui/utils/index.ts +91 -0
  429. package/src/tui/utils/isNarrowWidth.ts +52 -0
  430. package/src/tui/utils/stdoutGuard.ts +90 -0
  431. package/src/tui/utils/synchronized-update.ts +70 -0
  432. package/src/tui/utils/text-width.ts +225 -0
  433. package/src/tui/utils/textSanitizer.ts +225 -0
  434. package/src/tui/utils/textUtils.ts +114 -0
  435. package/src/tui/utils/ui-sizing.ts +192 -0
  436. package/src/tui-blessed/app.ts +160 -0
  437. package/src/tui-blessed/index.ts +2 -0
  438. package/src/tui-blessed/neo-blessed.d.ts +6 -0
  439. package/src/tui-blessed/test.ts +21 -0
  440. package/src/tui-blessed/types.ts +14 -0
  441. package/src/utils/icons.ts +130 -0
  442. package/src/utils/index.ts +33 -0
  443. package/src/utils/resume-hint.ts +86 -0
  444. package/src/version.ts +1 -0
  445. package/tsconfig.json +8 -0
  446. package/vitest.config.ts +35 -0
@@ -0,0 +1,667 @@
1
+ /**
2
+ * Mode CLI Integration Tests (T042)
3
+ *
4
+ * Tests for:
5
+ * - T037: --mode CLI flag parsing
6
+ * - T038: --approval CLI flag parsing
7
+ * - T039: --sandbox CLI flag parsing
8
+ * - T040: --full-auto shortcut flag
9
+ * - T041: Mode slash commands (/mode, /vibe, /plan, /spec)
10
+ * - T014: Verify mode commands work with new structure
11
+ * - T015: Integration tests for mode switching with agent resolution
12
+ *
13
+ * @module cli/commands/__tests__/mode-cli.test
14
+ */
15
+
16
+ import {
17
+ BuiltinAgentRegistry,
18
+ type ModeManager,
19
+ PLAN_AGENT,
20
+ SPEC_ORCHESTRATOR,
21
+ VIBE_AGENT,
22
+ } from "@vellum/core";
23
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
24
+ import {
25
+ getModeCommandsManager,
26
+ modeCommand,
27
+ modeSlashCommands,
28
+ planCommand,
29
+ setModeCommandsManager,
30
+ specCommand,
31
+ vibeCommand,
32
+ } from "../mode.js";
33
+ import type { CommandContext, ParsedArgs } from "../types.js";
34
+
35
+ // =============================================================================
36
+ // Mock Helpers
37
+ // =============================================================================
38
+
39
+ /**
40
+ * Create a mock CommandContext for testing.
41
+ */
42
+ function createMockContext(
43
+ positional: (string | number | boolean)[] = [],
44
+ named: Record<string, string | number | boolean> = {}
45
+ ): CommandContext {
46
+ const parsedArgs: ParsedArgs = {
47
+ raw: "",
48
+ command: "test",
49
+ positional,
50
+ named,
51
+ };
52
+
53
+ return {
54
+ parsedArgs,
55
+ session: {
56
+ id: "test-session",
57
+ provider: "anthropic",
58
+ cwd: "/test/cwd",
59
+ },
60
+ emit: vi.fn(),
61
+ credentials: {} as never,
62
+ toolRegistry: {} as never,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Create a mock ModeManager for testing.
68
+ */
69
+ function createMockModeManager(currentMode: "vibe" | "plan" | "spec" = "vibe"): ModeManager {
70
+ return {
71
+ getCurrentMode: vi.fn().mockReturnValue(currentMode),
72
+ getCurrentConfig: vi.fn().mockReturnValue({
73
+ codingMode: currentMode,
74
+ name: `${currentMode}-mode`,
75
+ description: `${currentMode} mode description`,
76
+ }),
77
+ switchMode: vi.fn().mockImplementation(async (mode, _options) => ({
78
+ success: true,
79
+ previousMode: currentMode,
80
+ currentMode: mode,
81
+ })),
82
+ on: vi.fn(),
83
+ off: vi.fn(),
84
+ once: vi.fn(),
85
+ emit: vi.fn(),
86
+ removeAllListeners: vi.fn(),
87
+ } as unknown as ModeManager;
88
+ }
89
+
90
+ // =============================================================================
91
+ // T037: --mode Flag Tests
92
+ // =============================================================================
93
+
94
+ describe("T037: --mode CLI Flag", () => {
95
+ it("should accept valid mode values", () => {
96
+ // Test validation by importing and calling parse functions
97
+ // The actual parsing is done by Commander.js, but we test the schema validation
98
+ const validModes = ["vibe", "plan", "spec"];
99
+ for (const mode of validModes) {
100
+ // These would pass Commander validation
101
+ expect(validModes).toContain(mode);
102
+ }
103
+ });
104
+
105
+ it("should have default mode of vibe", () => {
106
+ // Default is set in Commander option definition
107
+ // This test documents the expected default
108
+ const defaultMode = "vibe";
109
+ expect(defaultMode).toBe("vibe");
110
+ });
111
+
112
+ it("should reject invalid mode values", () => {
113
+ const invalidModes = ["invalid", "fast", "slow", "code"];
114
+ const validModes = ["vibe", "plan", "spec"];
115
+ for (const mode of invalidModes) {
116
+ expect(validModes).not.toContain(mode);
117
+ }
118
+ });
119
+ });
120
+
121
+ // =============================================================================
122
+ // T038: --approval Flag Tests
123
+ // =============================================================================
124
+
125
+ describe("T038: --approval CLI Flag", () => {
126
+ it("should accept valid approval policy values", () => {
127
+ const validPolicies = ["suggest", "auto-edit", "on-request", "full-auto"];
128
+ for (const policy of validPolicies) {
129
+ expect(validPolicies).toContain(policy);
130
+ }
131
+ });
132
+
133
+ it("should reject invalid approval policy values", () => {
134
+ const invalidPolicies = ["auto", "manual", "never", "always"];
135
+ const validPolicies = ["suggest", "auto-edit", "on-request", "full-auto"];
136
+ for (const policy of invalidPolicies) {
137
+ expect(validPolicies).not.toContain(policy);
138
+ }
139
+ });
140
+ });
141
+
142
+ // =============================================================================
143
+ // T039: --sandbox Flag Tests
144
+ // =============================================================================
145
+
146
+ describe("T039: --sandbox CLI Flag", () => {
147
+ it("should accept valid sandbox policy values", () => {
148
+ const validPolicies = [
149
+ "workspace-read",
150
+ "workspace-write",
151
+ "cwd-read",
152
+ "cwd-write",
153
+ "full-access",
154
+ ];
155
+ for (const policy of validPolicies) {
156
+ expect(validPolicies).toContain(policy);
157
+ }
158
+ });
159
+
160
+ it("should reject invalid sandbox policy values", () => {
161
+ const invalidPolicies = ["read", "write", "restricted", "unrestricted"];
162
+ const validPolicies = [
163
+ "workspace-read",
164
+ "workspace-write",
165
+ "cwd-read",
166
+ "cwd-write",
167
+ "full-access",
168
+ ];
169
+ for (const policy of invalidPolicies) {
170
+ expect(validPolicies).not.toContain(policy);
171
+ }
172
+ });
173
+ });
174
+
175
+ // =============================================================================
176
+ // T040: --full-auto Shortcut Tests
177
+ // =============================================================================
178
+
179
+ describe("T040: --full-auto Shortcut Flag", () => {
180
+ it("should set mode to vibe when --full-auto is used", () => {
181
+ // This tests the logic that would be applied in index.tsx
182
+ const options = { fullAuto: true, mode: "plan" as "vibe" | "plan" | "spec" };
183
+ let effectiveMode: "vibe" | "plan" | "spec" = options.mode;
184
+ let effectiveApproval: string | undefined;
185
+
186
+ if (options.fullAuto) {
187
+ effectiveMode = "vibe";
188
+ effectiveApproval = "full-auto";
189
+ }
190
+
191
+ expect(effectiveMode).toBe("vibe");
192
+ expect(effectiveApproval).toBe("full-auto");
193
+ });
194
+
195
+ it("should not override mode when --full-auto is not used", () => {
196
+ const options = { fullAuto: false, mode: "plan" as "vibe" | "plan" | "spec" };
197
+ let effectiveMode: "vibe" | "plan" | "spec" = options.mode;
198
+ let effectiveApproval: string | undefined;
199
+
200
+ if (options.fullAuto) {
201
+ effectiveMode = "vibe";
202
+ effectiveApproval = "full-auto";
203
+ }
204
+
205
+ expect(effectiveMode).toBe("plan");
206
+ expect(effectiveApproval).toBeUndefined();
207
+ });
208
+ });
209
+
210
+ // =============================================================================
211
+ // T041: Mode Slash Commands Tests
212
+ // =============================================================================
213
+
214
+ describe("T041: Mode Slash Commands", () => {
215
+ beforeEach(() => {
216
+ setModeCommandsManager(null);
217
+ });
218
+
219
+ afterEach(() => {
220
+ setModeCommandsManager(null);
221
+ });
222
+
223
+ describe("Command Registration", () => {
224
+ it("should export all mode commands", () => {
225
+ expect(modeSlashCommands).toHaveLength(4);
226
+ expect(modeSlashCommands).toContain(modeCommand);
227
+ expect(modeSlashCommands).toContain(vibeCommand);
228
+ expect(modeSlashCommands).toContain(planCommand);
229
+ expect(modeSlashCommands).toContain(specCommand);
230
+ });
231
+
232
+ it("should have correct command metadata", () => {
233
+ expect(modeCommand.name).toBe("mode");
234
+ expect(modeCommand.kind).toBe("builtin");
235
+ expect(modeCommand.category).toBe("workflow");
236
+
237
+ expect(vibeCommand.name).toBe("vibe");
238
+ expect(planCommand.name).toBe("plan");
239
+ expect(specCommand.name).toBe("spec");
240
+ });
241
+
242
+ it("modeCommand should have aliases", () => {
243
+ expect(modeCommand.aliases).toContain("modes");
244
+ });
245
+ });
246
+
247
+ describe("/mode Command", () => {
248
+ it("should show mode info without ModeManager", async () => {
249
+ const ctx = createMockContext();
250
+ const result = await modeCommand.execute(ctx);
251
+
252
+ expect(result.kind).toBe("success");
253
+ if (result.kind === "success") {
254
+ expect(result.message).toContain("Coding Modes");
255
+ expect(result.message).toContain("vibe");
256
+ expect(result.message).toContain("plan");
257
+ expect(result.message).toContain("spec");
258
+ expect(result.message).toContain("not yet initialized");
259
+ }
260
+ });
261
+
262
+ it("should show current mode with ModeManager", async () => {
263
+ const mockManager = createMockModeManager("plan");
264
+ setModeCommandsManager(mockManager);
265
+
266
+ const ctx = createMockContext();
267
+ const result = await modeCommand.execute(ctx);
268
+
269
+ expect(result.kind).toBe("success");
270
+ if (result.kind === "success") {
271
+ expect(result.message).toContain("Current mode:");
272
+ expect(result.message).toContain("plan");
273
+ }
274
+ expect(mockManager.getCurrentMode).toHaveBeenCalled();
275
+ });
276
+
277
+ it("should switch mode when argument provided", async () => {
278
+ const mockManager = createMockModeManager("vibe");
279
+ setModeCommandsManager(mockManager);
280
+
281
+ const ctx = createMockContext(["plan"]);
282
+ const result = await modeCommand.execute(ctx);
283
+
284
+ expect(result.kind).toBe("success");
285
+ expect(mockManager.switchMode).toHaveBeenCalledWith("plan", { skipConfirmation: false });
286
+ });
287
+
288
+ it("should error on invalid mode argument", async () => {
289
+ const mockManager = createMockModeManager();
290
+ setModeCommandsManager(mockManager);
291
+
292
+ const ctx = createMockContext(["invalid"]);
293
+ const result = await modeCommand.execute(ctx);
294
+
295
+ expect(result.kind).toBe("error");
296
+ if (result.kind === "error") {
297
+ expect(result.message).toContain("Invalid mode");
298
+ }
299
+ });
300
+ });
301
+
302
+ describe("/vibe Command", () => {
303
+ it("should switch to vibe mode", async () => {
304
+ const mockManager = createMockModeManager("plan");
305
+ setModeCommandsManager(mockManager);
306
+
307
+ const ctx = createMockContext();
308
+ const result = await vibeCommand.execute(ctx);
309
+
310
+ expect(result.kind).toBe("success");
311
+ expect(mockManager.switchMode).toHaveBeenCalledWith("vibe", { skipConfirmation: false });
312
+ });
313
+
314
+ it("should report already in vibe mode", async () => {
315
+ const mockManager = createMockModeManager("vibe");
316
+ setModeCommandsManager(mockManager);
317
+
318
+ const ctx = createMockContext();
319
+ const result = await vibeCommand.execute(ctx);
320
+
321
+ expect(result.kind).toBe("success");
322
+ if (result.kind === "success") {
323
+ expect(result.message).toContain("Already in");
324
+ expect(result.message).toContain("vibe");
325
+ }
326
+ });
327
+ });
328
+
329
+ describe("/plan Command", () => {
330
+ it("should switch to plan mode", async () => {
331
+ const mockManager = createMockModeManager("vibe");
332
+ setModeCommandsManager(mockManager);
333
+
334
+ const ctx = createMockContext();
335
+ const result = await planCommand.execute(ctx);
336
+
337
+ expect(result.kind).toBe("success");
338
+ expect(mockManager.switchMode).toHaveBeenCalledWith("plan", { skipConfirmation: false });
339
+ });
340
+ });
341
+
342
+ describe("/spec Command", () => {
343
+ it("should require confirmation for spec mode", async () => {
344
+ const mockManager = createMockModeManager("vibe");
345
+ setModeCommandsManager(mockManager);
346
+
347
+ const ctx = createMockContext();
348
+ const result = await specCommand.execute(ctx);
349
+
350
+ // Spec mode requires confirmation - returns interactive result
351
+ expect(result.kind).toBe("interactive");
352
+ if (result.kind === "interactive") {
353
+ expect(result.prompt.message).toContain("spec mode");
354
+ expect(result.prompt.inputType).toBe("confirm");
355
+ }
356
+ });
357
+
358
+ it("should switch after confirmation", async () => {
359
+ const mockManager = createMockModeManager("vibe");
360
+ setModeCommandsManager(mockManager);
361
+
362
+ const ctx = createMockContext();
363
+ const result = await specCommand.execute(ctx);
364
+
365
+ expect(result.kind).toBe("interactive");
366
+ if (result.kind === "interactive" && result.prompt.handler) {
367
+ // Simulate user confirming
368
+ const confirmResult = await result.prompt.handler("y");
369
+ expect(confirmResult.kind).toBe("success");
370
+ expect(mockManager.switchMode).toHaveBeenCalledWith("spec", { skipConfirmation: true });
371
+ }
372
+ });
373
+
374
+ it("should cancel on rejection", async () => {
375
+ const mockManager = createMockModeManager("vibe");
376
+ setModeCommandsManager(mockManager);
377
+
378
+ const ctx = createMockContext();
379
+ const result = await specCommand.execute(ctx);
380
+
381
+ expect(result.kind).toBe("interactive");
382
+ if (result.kind === "interactive" && result.prompt.handler) {
383
+ // Simulate user rejecting
384
+ const cancelResult = await result.prompt.handler("n");
385
+ expect(cancelResult.kind).toBe("success");
386
+ if (cancelResult.kind === "success") {
387
+ expect(cancelResult.message).toContain("cancelled");
388
+ }
389
+ }
390
+ });
391
+ });
392
+
393
+ describe("ModeManager Integration", () => {
394
+ it("should set and get ModeManager", () => {
395
+ expect(getModeCommandsManager()).toBeNull();
396
+
397
+ const mockManager = createMockModeManager();
398
+ setModeCommandsManager(mockManager);
399
+ expect(getModeCommandsManager()).toBe(mockManager);
400
+
401
+ setModeCommandsManager(null);
402
+ expect(getModeCommandsManager()).toBeNull();
403
+ });
404
+
405
+ it("should handle switch failure gracefully", async () => {
406
+ const mockManager = createMockModeManager();
407
+ (mockManager.switchMode as ReturnType<typeof vi.fn>).mockResolvedValue({
408
+ success: false,
409
+ reason: "Active operation in progress",
410
+ });
411
+ setModeCommandsManager(mockManager);
412
+
413
+ const ctx = createMockContext();
414
+ // Calling vibeCommand when already in vibe mode returns "Already in" message
415
+ // Let's test from plan mode
416
+ const mockManager2 = createMockModeManager("plan");
417
+ (mockManager2.switchMode as ReturnType<typeof vi.fn>).mockResolvedValue({
418
+ success: false,
419
+ reason: "Active operation in progress",
420
+ });
421
+ setModeCommandsManager(mockManager2);
422
+
423
+ const result2 = await vibeCommand.execute(ctx);
424
+ expect(result2.kind).toBe("error");
425
+ if (result2.kind === "error") {
426
+ expect(result2.message).toContain("Active operation");
427
+ }
428
+ });
429
+ });
430
+ });
431
+
432
+ // =============================================================================
433
+ // Integration Tests
434
+ // =============================================================================
435
+
436
+ describe("CLI Mode Integration", () => {
437
+ it("should work end-to-end with valid inputs", async () => {
438
+ const mockManager = createMockModeManager("vibe");
439
+ setModeCommandsManager(mockManager);
440
+
441
+ // Simulate full flow: /mode -> see options -> /plan -> switch
442
+ const modeCtx = createMockContext();
443
+ const modeResult = await modeCommand.execute(modeCtx);
444
+ expect(modeResult.kind).toBe("success");
445
+
446
+ const planCtx = createMockContext();
447
+ const planResult = await planCommand.execute(planCtx);
448
+ expect(planResult.kind).toBe("success");
449
+ expect(mockManager.switchMode).toHaveBeenCalledWith("plan", { skipConfirmation: false });
450
+ });
451
+
452
+ it("mode commands should have proper descriptions", () => {
453
+ for (const cmd of modeSlashCommands) {
454
+ expect(cmd.description).toBeTruthy();
455
+ expect(cmd.description.length).toBeGreaterThan(10);
456
+ }
457
+ });
458
+
459
+ it("mode commands should have examples", () => {
460
+ expect(modeCommand.examples).toBeDefined();
461
+ expect(modeCommand.examples?.length).toBeGreaterThan(0);
462
+ });
463
+ });
464
+
465
+ // =============================================================================
466
+ // T014: Verify Mode Commands Work with New Structure
467
+ // =============================================================================
468
+
469
+ describe("T014: Mode Commands with New Agent Structure", () => {
470
+ beforeEach(() => {
471
+ setModeCommandsManager(null);
472
+ });
473
+
474
+ afterEach(() => {
475
+ setModeCommandsManager(null);
476
+ });
477
+
478
+ describe("/vibe switches to VIBE_MODE", () => {
479
+ it("should switch to vibe mode successfully", async () => {
480
+ const mockManager = createMockModeManager("plan");
481
+ setModeCommandsManager(mockManager);
482
+
483
+ const ctx = createMockContext();
484
+ const result = await vibeCommand.execute(ctx);
485
+
486
+ expect(result.kind).toBe("success");
487
+ expect(mockManager.switchMode).toHaveBeenCalledWith("vibe", { skipConfirmation: false });
488
+ });
489
+ });
490
+
491
+ describe("/plan switches to PLAN_MODE", () => {
492
+ it("should switch to plan mode successfully", async () => {
493
+ const mockManager = createMockModeManager("vibe");
494
+ setModeCommandsManager(mockManager);
495
+
496
+ const ctx = createMockContext();
497
+ const result = await planCommand.execute(ctx);
498
+
499
+ expect(result.kind).toBe("success");
500
+ expect(mockManager.switchMode).toHaveBeenCalledWith("plan", { skipConfirmation: false });
501
+ });
502
+ });
503
+
504
+ describe("/spec switches to SPEC_MODE", () => {
505
+ it("should request confirmation for spec mode", async () => {
506
+ const mockManager = createMockModeManager("vibe");
507
+ setModeCommandsManager(mockManager);
508
+
509
+ const ctx = createMockContext();
510
+ const result = await specCommand.execute(ctx);
511
+
512
+ expect(result.kind).toBe("interactive");
513
+ if (result.kind === "interactive") {
514
+ expect(result.prompt.inputType).toBe("confirm");
515
+ }
516
+ });
517
+
518
+ it("should switch to spec mode after confirmation", async () => {
519
+ const mockManager = createMockModeManager("vibe");
520
+ setModeCommandsManager(mockManager);
521
+
522
+ const ctx = createMockContext();
523
+ const result = await specCommand.execute(ctx);
524
+
525
+ if (result.kind === "interactive" && result.prompt.handler) {
526
+ const confirmResult = await result.prompt.handler("y");
527
+ expect(confirmResult.kind).toBe("success");
528
+ expect(mockManager.switchMode).toHaveBeenCalledWith("spec", { skipConfirmation: true });
529
+ }
530
+ });
531
+ });
532
+
533
+ describe("legacy mode names still work", () => {
534
+ it("should reject legacy mode 'code' (not a valid new mode)", async () => {
535
+ const mockManager = createMockModeManager("vibe");
536
+ setModeCommandsManager(mockManager);
537
+
538
+ // 'code' is not in CODING_MODES, so it should fail
539
+ const ctx = createMockContext(["code"]);
540
+ const result = await modeCommand.execute(ctx);
541
+
542
+ expect(result.kind).toBe("error");
543
+ if (result.kind === "error") {
544
+ expect(result.message).toContain("Invalid mode");
545
+ }
546
+ });
547
+
548
+ it("should accept valid new mode names", async () => {
549
+ const mockManager = createMockModeManager("vibe");
550
+ setModeCommandsManager(mockManager);
551
+
552
+ for (const mode of ["vibe", "plan"]) {
553
+ const ctx = createMockContext([mode]);
554
+ const result = await modeCommand.execute(ctx);
555
+ expect(result.kind).toBe("success");
556
+ }
557
+ });
558
+ });
559
+ });
560
+
561
+ // =============================================================================
562
+ // T015: Integration Tests for Mode Switching with Agent Resolution
563
+ // =============================================================================
564
+
565
+ describe("T015: Mode Switching with Agent Resolution", () => {
566
+ beforeEach(() => {
567
+ // Reset and reinitialize registry for clean state
568
+ BuiltinAgentRegistry.getInstance().reset();
569
+ BuiltinAgentRegistry.getInstance().reinitialize();
570
+ setModeCommandsManager(null);
571
+ });
572
+
573
+ afterEach(() => {
574
+ setModeCommandsManager(null);
575
+ });
576
+
577
+ describe("/mode vibe activates VIBE_MODE", () => {
578
+ it("should activate vibe mode with vibe-agent", async () => {
579
+ const mockManager = createMockModeManager("plan");
580
+ setModeCommandsManager(mockManager);
581
+
582
+ const ctx = createMockContext(["vibe"]);
583
+ const result = await modeCommand.execute(ctx);
584
+
585
+ expect(result.kind).toBe("success");
586
+ expect(mockManager.switchMode).toHaveBeenCalledWith("vibe", { skipConfirmation: false });
587
+ });
588
+ });
589
+
590
+ describe("/mode plan activates PLAN_MODE", () => {
591
+ it("should activate plan mode with plan-agent", async () => {
592
+ const mockManager = createMockModeManager("vibe");
593
+ setModeCommandsManager(mockManager);
594
+
595
+ const ctx = createMockContext(["plan"]);
596
+ const result = await modeCommand.execute(ctx);
597
+
598
+ expect(result.kind).toBe("success");
599
+ expect(mockManager.switchMode).toHaveBeenCalledWith("plan", { skipConfirmation: false });
600
+ });
601
+ });
602
+
603
+ describe("/mode spec activates SPEC_MODE", () => {
604
+ it("should activate spec mode with spec-orchestrator (after confirmation)", async () => {
605
+ const mockManager = createMockModeManager("vibe");
606
+ setModeCommandsManager(mockManager);
607
+
608
+ const ctx = createMockContext(["spec"]);
609
+ const result = await modeCommand.execute(ctx);
610
+
611
+ // Spec mode requires confirmation
612
+ expect(result.kind).toBe("interactive");
613
+ });
614
+ });
615
+
616
+ describe("BuiltinAgentRegistry.get returns correct agent for mode", () => {
617
+ it("returns vibe-agent for vibe mode", () => {
618
+ const registry = BuiltinAgentRegistry.getInstance();
619
+ const agent = registry.get("vibe-agent");
620
+
621
+ expect(agent).toBeDefined();
622
+ expect(agent).toEqual(VIBE_AGENT);
623
+ expect(agent?.level).toBe(2);
624
+ expect(agent?.canSpawnAgents).toBe(false);
625
+ });
626
+
627
+ it("returns plan-agent for plan mode", () => {
628
+ const registry = BuiltinAgentRegistry.getInstance();
629
+ const agent = registry.get("plan-agent");
630
+
631
+ expect(agent).toBeDefined();
632
+ expect(agent).toEqual(PLAN_AGENT);
633
+ expect(agent?.level).toBe(1);
634
+ expect(agent?.canSpawnAgents).toBe(true);
635
+ });
636
+
637
+ it("returns spec-orchestrator for spec mode", () => {
638
+ const registry = BuiltinAgentRegistry.getInstance();
639
+ const agent = registry.get("spec-orchestrator");
640
+
641
+ expect(agent).toBeDefined();
642
+ expect(agent).toEqual(SPEC_ORCHESTRATOR);
643
+ expect(agent?.level).toBe(0);
644
+ expect(agent?.canSpawnAgents).toBe(true);
645
+ });
646
+
647
+ it("returns undefined for unknown agent", () => {
648
+ const registry = BuiltinAgentRegistry.getInstance();
649
+ const agent = registry.get("unknown-agent");
650
+
651
+ expect(agent).toBeUndefined();
652
+ });
653
+ });
654
+
655
+ describe("legacy /mode code maps to vibe (via legacy-modes.ts)", () => {
656
+ it("legacy mode 'code' is not directly valid in mode commands", async () => {
657
+ const mockManager = createMockModeManager("vibe");
658
+ setModeCommandsManager(mockManager);
659
+
660
+ // Direct 'code' should fail - legacy mapping is done at a higher level
661
+ const ctx = createMockContext(["code"]);
662
+ const result = await modeCommand.execute(ctx);
663
+
664
+ expect(result.kind).toBe("error");
665
+ });
666
+ });
667
+ });