@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,366 @@
1
+ /**
2
+ * Init Prompts Command
3
+ *
4
+ * Scaffolds the .vellum/ directory structure for customizing prompts,
5
+ * rules, skills, commands, and workflows.
6
+ *
7
+ * Usage:
8
+ * - `vellum init prompts` - Interactive scaffolding
9
+ * - `vellum init prompts --force` - Overwrite existing .vellum/
10
+ *
11
+ * @module cli/commands/init/prompts
12
+ * @see REQ-015
13
+ */
14
+
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
+ import { dirname, join } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ import { confirm } from "@inquirer/prompts";
20
+ import chalk from "chalk";
21
+
22
+ import { EXIT_CODES } from "../exit-codes.js";
23
+ import type { CommandContext, CommandResult, SlashCommand } from "../types.js";
24
+ import { error, pending, success } from "../types.js";
25
+
26
+ // =============================================================================
27
+ // Constants
28
+ // =============================================================================
29
+
30
+ /**
31
+ * Directory structure to scaffold
32
+ */
33
+ const SCAFFOLD_DIRECTORIES = [
34
+ "prompts",
35
+ "prompts/roles",
36
+ "prompts/providers",
37
+ "prompts/spec",
38
+ "prompts/workers",
39
+ "prompts/custom",
40
+ "rules",
41
+ "skills",
42
+ "commands",
43
+ "workflows",
44
+ ] as const;
45
+
46
+ /**
47
+ * README files to copy from templates
48
+ */
49
+ const README_MAPPINGS: Record<string, string> = {
50
+ prompts: "prompts-readme.md",
51
+ rules: "rules-readme.md",
52
+ skills: "skills-readme.md",
53
+ commands: "commands-readme.md",
54
+ workflows: "workflows-readme.md",
55
+ };
56
+
57
+ /**
58
+ * Example files to copy from templates
59
+ */
60
+ const EXAMPLE_MAPPINGS: Record<string, string> = {
61
+ "commands/summarize.example.md": "example-command.md",
62
+ "workflows/bugfix.example.md": "example-workflow.md",
63
+ "skills/react-patterns/SKILL.md": "example-skill.md",
64
+ };
65
+
66
+ // =============================================================================
67
+ // Types
68
+ // =============================================================================
69
+
70
+ /**
71
+ * Options for init prompts command
72
+ */
73
+ export interface InitPromptsOptions {
74
+ /** Overwrite existing .vellum/ without prompting */
75
+ force?: boolean;
76
+ /** Non-interactive mode (for CI) */
77
+ nonInteractive?: boolean;
78
+ /** Working directory (defaults to process.cwd()) */
79
+ cwd?: string;
80
+ }
81
+
82
+ /**
83
+ * Result of init prompts command
84
+ */
85
+ export interface InitPromptsResult {
86
+ /** Whether initialization succeeded */
87
+ success: boolean;
88
+ /** Path to created .vellum/ directory */
89
+ vellumPath?: string;
90
+ /** Directories created */
91
+ directoriesCreated?: string[];
92
+ /** Files created */
93
+ filesCreated?: string[];
94
+ /** Error message if failed */
95
+ error?: string;
96
+ /** Exit code */
97
+ exitCode: number;
98
+ }
99
+
100
+ // =============================================================================
101
+ // Template Loading
102
+ // =============================================================================
103
+
104
+ /**
105
+ * Get the templates directory path
106
+ */
107
+ function getTemplatesDir(): string {
108
+ const currentFile = fileURLToPath(import.meta.url);
109
+ return join(dirname(currentFile), "templates");
110
+ }
111
+
112
+ /**
113
+ * Read a template file
114
+ */
115
+ function readTemplate(templateName: string): string | null {
116
+ try {
117
+ const templatePath = join(getTemplatesDir(), templateName);
118
+ return readFileSync(templatePath, "utf-8");
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ // =============================================================================
125
+ // Scaffolding Logic
126
+ // =============================================================================
127
+
128
+ /**
129
+ * Create the .vellum/ directory structure
130
+ *
131
+ * @param rootDir - Project root directory
132
+ * @returns Created directories and files
133
+ */
134
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Multiple directory/file creation paths required
135
+ function scaffoldVellumDirectory(rootDir: string): {
136
+ directories: string[];
137
+ files: string[];
138
+ } {
139
+ const vellumPath = join(rootDir, ".vellum");
140
+ const directories: string[] = [];
141
+ const files: string[] = [];
142
+
143
+ // Create main .vellum directory
144
+ if (!existsSync(vellumPath)) {
145
+ mkdirSync(vellumPath, { recursive: true });
146
+ directories.push(".vellum");
147
+ }
148
+
149
+ // Create subdirectories
150
+ for (const subdir of SCAFFOLD_DIRECTORIES) {
151
+ const fullPath = join(vellumPath, subdir);
152
+ if (!existsSync(fullPath)) {
153
+ mkdirSync(fullPath, { recursive: true });
154
+ directories.push(`.vellum/${subdir}`);
155
+ }
156
+ }
157
+
158
+ // Copy README files
159
+ for (const [targetDir, templateFile] of Object.entries(README_MAPPINGS)) {
160
+ const targetPath = join(vellumPath, targetDir, "README.md");
161
+ if (!existsSync(targetPath)) {
162
+ const content = readTemplate(templateFile);
163
+ if (content) {
164
+ writeFileSync(targetPath, content, "utf-8");
165
+ files.push(`.vellum/${targetDir}/README.md`);
166
+ }
167
+ }
168
+ }
169
+
170
+ // Copy example files
171
+ for (const [targetFile, templateFile] of Object.entries(EXAMPLE_MAPPINGS)) {
172
+ const targetPath = join(vellumPath, targetFile);
173
+ const targetDirPath = dirname(targetPath);
174
+
175
+ // Ensure parent directory exists (for nested paths like skills/react-patterns/)
176
+ if (!existsSync(targetDirPath)) {
177
+ mkdirSync(targetDirPath, { recursive: true });
178
+ const relativePath = targetDirPath
179
+ .replace(`${vellumPath}/`, "")
180
+ .replace(`${vellumPath}\\`, "");
181
+ if (!directories.includes(`.vellum/${relativePath}`)) {
182
+ directories.push(`.vellum/${relativePath}`);
183
+ }
184
+ }
185
+
186
+ if (!existsSync(targetPath)) {
187
+ const content = readTemplate(templateFile);
188
+ if (content) {
189
+ writeFileSync(targetPath, content, "utf-8");
190
+ files.push(`.vellum/${targetFile}`);
191
+ }
192
+ }
193
+ }
194
+
195
+ return { directories, files };
196
+ }
197
+
198
+ // =============================================================================
199
+ // Command Execution
200
+ // =============================================================================
201
+
202
+ /**
203
+ * Execute init prompts command
204
+ *
205
+ * @param options - Command options
206
+ * @returns Init result
207
+ */
208
+ export async function executeInitPrompts(
209
+ options: InitPromptsOptions = {}
210
+ ): Promise<InitPromptsResult> {
211
+ const rootDir = options.cwd ?? process.cwd();
212
+ const vellumPath = join(rootDir, ".vellum");
213
+
214
+ try {
215
+ // Check for existing .vellum directory
216
+ const exists = existsSync(vellumPath);
217
+
218
+ if (exists && !options.force) {
219
+ // In non-interactive mode, fail if directory exists
220
+ if (options.nonInteractive) {
221
+ console.log(chalk.yellow(".vellum/ already exists. Use --force to overwrite."));
222
+ return {
223
+ success: false,
224
+ error: "Directory already exists",
225
+ exitCode: EXIT_CODES.ERROR,
226
+ };
227
+ }
228
+
229
+ // Interactive confirmation
230
+ const shouldOverwrite = await confirm({
231
+ message: ".vellum/ already exists. Overwrite?",
232
+ default: false,
233
+ });
234
+
235
+ if (!shouldOverwrite) {
236
+ console.log(chalk.gray("Aborted."));
237
+ return {
238
+ success: false,
239
+ error: "Aborted by user",
240
+ exitCode: EXIT_CODES.SUCCESS, // User choice, not an error
241
+ };
242
+ }
243
+ }
244
+
245
+ // Scaffold the directory structure
246
+ console.log(chalk.blue("\n🔧 Scaffolding .vellum/ directory...\n"));
247
+
248
+ const { directories, files } = scaffoldVellumDirectory(rootDir);
249
+
250
+ // Display created items
251
+ if (directories.length > 0) {
252
+ console.log(chalk.gray("Created directories:"));
253
+ for (const dir of directories) {
254
+ console.log(chalk.gray(` 📁 ${dir}`));
255
+ }
256
+ }
257
+
258
+ if (files.length > 0) {
259
+ console.log(chalk.gray("\nCreated files:"));
260
+ for (const file of files) {
261
+ console.log(chalk.gray(` 📄 ${file}`));
262
+ }
263
+ }
264
+
265
+ console.log(chalk.green("\n✅ .vellum/ directory scaffolded successfully!"));
266
+ console.log(chalk.gray("\nNext steps:"));
267
+ console.log(chalk.gray(" • Add custom prompts to .vellum/prompts/"));
268
+ console.log(chalk.gray(" • Add global rules to .vellum/rules/"));
269
+ console.log(chalk.gray(" • Create skills in .vellum/skills/"));
270
+ console.log(chalk.gray(" • Add custom commands in .vellum/commands/"));
271
+ console.log(chalk.gray(" • Define workflows in .vellum/workflows/"));
272
+ console.log(chalk.gray(" • Run `vellum prompt validate` to check syntax\n"));
273
+
274
+ return {
275
+ success: true,
276
+ vellumPath,
277
+ directoriesCreated: directories,
278
+ filesCreated: files,
279
+ exitCode: EXIT_CODES.SUCCESS,
280
+ };
281
+ } catch (err) {
282
+ const message = err instanceof Error ? err.message : String(err);
283
+ console.error(chalk.red(`\n❌ Failed to scaffold .vellum/: ${message}`));
284
+ return {
285
+ success: false,
286
+ error: message,
287
+ exitCode: EXIT_CODES.ERROR,
288
+ };
289
+ }
290
+ }
291
+
292
+ // =============================================================================
293
+ // Slash Command Definition
294
+ // =============================================================================
295
+
296
+ /**
297
+ * Init prompts slash command for TUI
298
+ *
299
+ * Scaffolds the .vellum/ directory structure.
300
+ */
301
+ export const initPromptsCommand: SlashCommand = {
302
+ name: "init-prompts",
303
+ description: "Scaffold .vellum/ directory for custom prompts and configuration",
304
+ kind: "builtin",
305
+ category: "config",
306
+ aliases: ["scaffold"],
307
+ namedArgs: [
308
+ {
309
+ name: "force",
310
+ shorthand: "f",
311
+ type: "boolean",
312
+ description: "Overwrite existing .vellum/ without prompting",
313
+ required: false,
314
+ default: false,
315
+ },
316
+ ],
317
+ examples: [
318
+ "/init-prompts - Interactive scaffolding",
319
+ "/init-prompts --force - Overwrite existing .vellum/",
320
+ ],
321
+
322
+ execute: async (ctx: CommandContext): Promise<CommandResult> => {
323
+ const force = ctx.parsedArgs.named.force as boolean | undefined;
324
+
325
+ return pending({
326
+ message: "Scaffolding .vellum/ directory...",
327
+ showProgress: true,
328
+ promise: (async (): Promise<CommandResult> => {
329
+ const result = await executeInitPrompts({
330
+ force: force ?? false,
331
+ nonInteractive: false,
332
+ });
333
+
334
+ if (result.success) {
335
+ const createdCount =
336
+ (result.directoriesCreated?.length ?? 0) + (result.filesCreated?.length ?? 0);
337
+ return success(`Scaffolded ${createdCount} items in .vellum/`, {
338
+ vellumPath: result.vellumPath,
339
+ directoriesCreated: result.directoriesCreated,
340
+ filesCreated: result.filesCreated,
341
+ });
342
+ }
343
+
344
+ return error("INTERNAL_ERROR", result.error ?? "Failed to scaffold .vellum/");
345
+ })(),
346
+ });
347
+ },
348
+ };
349
+
350
+ // =============================================================================
351
+ // CLI Entry Point
352
+ // =============================================================================
353
+
354
+ /**
355
+ * Run init prompts command from CLI
356
+ *
357
+ * @param options - CLI options
358
+ */
359
+ export async function runInitPromptsCli(options: { force?: boolean }): Promise<void> {
360
+ const result = await executeInitPrompts({
361
+ force: options.force ?? false,
362
+ nonInteractive: false,
363
+ });
364
+
365
+ process.exit(result.exitCode);
366
+ }
@@ -0,0 +1,80 @@
1
+ # Commands Directory
2
+
3
+ Custom slash commands that extend Vellum's functionality.
4
+
5
+ ## Structure
6
+
7
+ ```text
8
+ commands/
9
+ ├── {command-name}.md # Command definition
10
+ └── README.md
11
+ ```
12
+
13
+ ## File Format
14
+
15
+ ```markdown
16
+ ---
17
+ name: review
18
+ description: Request code review for current changes
19
+ category: tools
20
+ aliases:
21
+ - cr
22
+ - codereview
23
+ ---
24
+
25
+ # /review Command
26
+
27
+ Review the current changes and provide feedback.
28
+
29
+ ## Instructions
30
+
31
+ When this command is invoked:
32
+
33
+ 1. Examine the current git diff
34
+ 2. Look for potential issues:
35
+ - Code style violations
36
+ - Performance concerns
37
+ - Security vulnerabilities
38
+ - Missing error handling
39
+ 3. Provide structured feedback
40
+
41
+ ## Output Format
42
+
43
+ Format your review as:
44
+
45
+ ### Summary
46
+ Brief overview of changes
47
+
48
+ ### Issues Found
49
+ - [ ] Issue 1
50
+ - [ ] Issue 2
51
+
52
+ ### Suggestions
53
+ Improvement recommendations
54
+ ```markdown
55
+
56
+ ## Frontmatter Options
57
+
58
+ | Field | Type | Description |
59
+ |-------|------|-------------|
60
+ | `name` | string | Command name without slash (required) |
61
+ | `description` | string | Help text (required) |
62
+ | `category` | string | Grouping: system, auth, session, navigation, tools, config, debug |
63
+ | `aliases` | string[] | Alternative command names |
64
+ | `enabled` | boolean | Whether command is active |
65
+
66
+ ## Usage
67
+
68
+ After creating a command file:
69
+
70
+ ```bash
71
+ # Use the command
72
+ /review
73
+
74
+ # Or use an alias
75
+ /cr
76
+ ```
77
+
78
+ ## Documentation
79
+
80
+ See [Vellum Commands Documentation](https://vellum.dev/docs/commands) for more details.
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: summarize
3
+ description: Summarize the current file or selection
4
+ category: tools
5
+ aliases:
6
+ - sum
7
+ - tldr
8
+ enabled: true
9
+ ---
10
+
11
+ # /summarize Command
12
+
13
+ Provide a concise summary of the current context.
14
+
15
+ ## Instructions
16
+
17
+ When this command is invoked:
18
+
19
+ 1. Identify the current context:
20
+ - If text is selected, summarize the selection
21
+ - If a file is open, summarize the file
22
+ - If in a conversation, summarize recent messages
23
+
24
+ 2. Generate a summary that includes:
25
+ - **Purpose**: What does this code/text do?
26
+ - **Key Points**: Main concepts or functionality
27
+ - **Structure**: How is it organized?
28
+
29
+ 3. Keep the summary:
30
+ - Concise (3-5 sentences for small content)
31
+ - Structured (use bullet points for complex content)
32
+ - Actionable (highlight important details)
33
+
34
+ ## Output Format
35
+
36
+ ```markdown
37
+ ## Summary
38
+
39
+ **Purpose**: [One-sentence description]
40
+
41
+ **Key Points**:
42
+ - Point 1
43
+ - Point 2
44
+ - Point 3
45
+
46
+ **Notable**: [Any important observations]
47
+ ```
48
+
49
+ ## Examples
50
+
51
+ ### For Code Files
52
+
53
+ ```markdown
54
+ ## Summary
55
+
56
+ **Purpose**: HTTP request handler for user authentication
57
+
58
+ **Key Points**:
59
+ - Validates JWT tokens from Authorization header
60
+ - Supports both cookie and header-based auth
61
+ - Returns 401 for invalid/expired tokens
62
+
63
+ **Notable**: Rate limiting applied via middleware
64
+ ```
65
+
66
+ ### For Documentation
67
+
68
+ ```markdown
69
+ ## Summary
70
+
71
+ **Purpose**: API reference for the authentication module
72
+
73
+ **Key Points**:
74
+ - 5 endpoints documented (login, logout, refresh, verify, revoke)
75
+ - OAuth2 flow supported
76
+ - Includes code examples in TypeScript
77
+
78
+ **Notable**: Breaking changes from v1 noted in migration section
79
+ ```
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: react-patterns
3
+ description: Best practices and patterns for React development
4
+ version: "1.0.0"
5
+ triggers:
6
+ - react
7
+ - component
8
+ - hook
9
+ - jsx
10
+ - tsx
11
+ tags:
12
+ - react
13
+ - frontend
14
+ - typescript
15
+ priority: 100
16
+ ---
17
+
18
+ # React Patterns Skill
19
+
20
+ Expert knowledge for building React applications with TypeScript.
21
+
22
+ ## Core Principles
23
+
24
+ 1. **Composition over inheritance**: Build small, reusable components
25
+ 2. **Unidirectional data flow**: Props down, events up
26
+ 3. **Explicit over implicit**: Prefer explicit prop passing
27
+ 4. **Type safety**: Leverage TypeScript for all components
28
+
29
+ ## Component Patterns
30
+
31
+ ### Functional Components (Preferred)
32
+
33
+ ```tsx
34
+ interface ButtonProps {
35
+ label: string;
36
+ onClick: () => void;
37
+ variant?: 'primary' | 'secondary';
38
+ disabled?: boolean;
39
+ }
40
+
41
+ export function Button({
42
+ label,
43
+ onClick,
44
+ variant = 'primary',
45
+ disabled = false
46
+ }: ButtonProps) {
47
+ return (
48
+ <button
49
+ className={`btn btn-${variant}`}
50
+ onClick={onClick}
51
+ disabled={disabled}
52
+ >
53
+ {label}
54
+ </button>
55
+ );
56
+ }
57
+ ```markdown
58
+
59
+ ### Compound Components
60
+
61
+ ```tsx
62
+ interface TabsContextValue {
63
+ activeTab: string;
64
+ setActiveTab: (tab: string) => void;
65
+ }
66
+
67
+ const TabsContext = createContext<TabsContextValue | null>(null);
68
+
69
+ function Tabs({ children, defaultTab }: TabsProps) {
70
+ const [activeTab, setActiveTab] = useState(defaultTab);
71
+
72
+ return (
73
+ <TabsContext.Provider value={{ activeTab, setActiveTab }}>
74
+ <div className="tabs">{children}</div>
75
+ </TabsContext.Provider>
76
+ );
77
+ }
78
+
79
+ Tabs.Tab = function Tab({ id, children }: TabProps) {
80
+ const ctx = useContext(TabsContext);
81
+ if (!ctx) throw new Error('Tab must be used within Tabs');
82
+
83
+ return (
84
+ <button
85
+ className={ctx.activeTab === id ? 'active' : ''}
86
+ onClick={() => ctx.setActiveTab(id)}
87
+ >
88
+ {children}
89
+ </button>
90
+ );
91
+ };
92
+ ```markdown
93
+
94
+ ## Hook Patterns
95
+
96
+ ### Custom Hooks
97
+
98
+ ```tsx
99
+ function useLocalStorage<T>(key: string, initialValue: T) {
100
+ const [value, setValue] = useState<T>(() => {
101
+ const stored = localStorage.getItem(key);
102
+ return stored ? JSON.parse(stored) : initialValue;
103
+ });
104
+
105
+ useEffect(() => {
106
+ localStorage.setItem(key, JSON.stringify(value));
107
+ }, [key, value]);
108
+
109
+ return [value, setValue] as const;
110
+ }
111
+ ```markdown
112
+
113
+ ### Cleanup Pattern
114
+
115
+ ```tsx
116
+ useEffect(() => {
117
+ const controller = new AbortController();
118
+
119
+ fetchData(controller.signal)
120
+ .then(setData)
121
+ .catch(err => {
122
+ if (!controller.signal.aborted) {
123
+ setError(err);
124
+ }
125
+ });
126
+
127
+ return () => controller.abort();
128
+ }, []);
129
+ ```markdown
130
+
131
+ ## Anti-Patterns to Avoid
132
+
133
+ 1. **Don't mutate state directly**
134
+ ```tsx
135
+ // ❌ Bad
136
+ state.items.push(newItem);
137
+
138
+ // ✅ Good
139
+ setItems([...items, newItem]);
140
+ ```text
141
+
142
+ 2. **Don't use index as key for dynamic lists**
143
+ ```tsx
144
+ // ❌ Bad
145
+ items.map((item, i) => <Item key={i} {...item} />);
146
+
147
+ // ✅ Good
148
+ items.map(item => <Item key={item.id} {...item} />);
149
+ ```text
150
+
151
+ 3. **Don't overuse useEffect**
152
+ ```tsx
153
+ // ❌ Bad - derived state
154
+ const [fullName, setFullName] = useState('');
155
+ useEffect(() => {
156
+ setFullName(`${firstName} ${lastName}`);
157
+ }, [firstName, lastName]);
158
+
159
+ // ✅ Good - compute directly
160
+ const fullName = `${firstName} ${lastName}`;
161
+ ```
162
+
163
+ ## Testing Recommendations
164
+
165
+ - Use `@testing-library/react` for component tests
166
+ - Test behavior, not implementation
167
+ - Use `userEvent` over `fireEvent`
168
+ - Mock API calls at the network level with MSW