@pennyfarthing/core 11.3.7 → 11.4.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 (405) hide show
  1. package/README.md +1 -1
  2. package/package.json +17 -16
  3. package/packages/core/dist/public/css/react.css +1 -1
  4. package/packages/core/dist/public/js/react/react.js +24 -24
  5. package/packages/core/src/public/App.tsx +356 -0
  6. package/packages/core/src/public/components/AgentLoadDialog.tsx +202 -0
  7. package/packages/core/src/public/components/AgentPopup.tsx +308 -0
  8. package/packages/core/src/public/components/ApprovalModal/ApprovalModal.css +35 -0
  9. package/packages/core/src/public/components/ApprovalModal/index.tsx +632 -0
  10. package/packages/core/src/public/components/BikeRackIndex.tsx +53 -0
  11. package/packages/core/src/public/components/BikeRackWorkspace.tsx +217 -0
  12. package/packages/core/src/public/components/CommandPalette.tsx +554 -0
  13. package/packages/core/src/public/components/ConfirmDialog.tsx +168 -0
  14. package/packages/core/src/public/components/ContextIndicator/ContextIndicator.css +85 -0
  15. package/packages/core/src/public/components/ContextIndicator/index.tsx +330 -0
  16. package/packages/core/src/public/components/ContextSparkline.tsx +56 -0
  17. package/packages/core/src/public/components/ControlBar.tsx +636 -0
  18. package/packages/core/src/public/components/DeadCodeDialog.tsx +169 -0
  19. package/packages/core/src/public/components/DiffViewer.tsx +585 -0
  20. package/packages/core/src/public/components/DockviewWorkspace.tsx +749 -0
  21. package/packages/core/src/public/components/Editor.tsx +630 -0
  22. package/packages/core/src/public/components/ErrorBoundary.tsx +67 -0
  23. package/packages/core/src/public/components/FileTree.tsx +379 -0
  24. package/packages/core/src/public/components/FontPicker/FontPicker.css +276 -0
  25. package/packages/core/src/public/components/FontPicker/index.tsx +430 -0
  26. package/packages/core/src/public/components/FullFileTree.tsx +237 -0
  27. package/packages/core/src/public/components/HealthGauge.tsx +181 -0
  28. package/packages/core/src/public/components/Message.tsx +225 -0
  29. package/packages/core/src/public/components/MessageList.tsx +98 -0
  30. package/packages/core/src/public/components/MessageView.tsx +400 -0
  31. package/packages/core/src/public/components/ModeSwitch/ModeSwitch.css +165 -0
  32. package/packages/core/src/public/components/ModeSwitch/index.tsx +372 -0
  33. package/packages/core/src/public/components/PersonaHeader.tsx +242 -0
  34. package/packages/core/src/public/components/ProjectInfoBar.tsx +45 -0
  35. package/packages/core/src/public/components/QuickActions.tsx +267 -0
  36. package/packages/core/src/public/components/SpanTimeline.tsx +352 -0
  37. package/packages/core/src/public/components/StandalonePanel.tsx +82 -0
  38. package/packages/core/src/public/components/StatsStrip.tsx +162 -0
  39. package/packages/core/src/public/components/StreamingContent.tsx +77 -0
  40. package/packages/core/src/public/components/SubagentSpan.tsx +180 -0
  41. package/packages/core/src/public/components/TandemPortrait.tsx +72 -0
  42. package/packages/core/src/public/components/ThemePalette/ThemePalette.css +179 -0
  43. package/packages/core/src/public/components/ThemePalette/index.tsx +326 -0
  44. package/packages/core/src/public/components/ToolCallBlock.tsx +252 -0
  45. package/packages/core/src/public/components/ToolStack.tsx +209 -0
  46. package/packages/core/src/public/components/ToolStatus.tsx +57 -0
  47. package/packages/core/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
  48. package/packages/core/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
  49. package/packages/core/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
  50. package/packages/core/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
  51. package/packages/core/src/public/components/dialogs/ToolDialog.tsx +43 -0
  52. package/packages/core/src/public/components/panel-registry.ts +13 -0
  53. package/packages/core/src/public/components/panels/ACPanel.tsx +93 -0
  54. package/packages/core/src/public/components/panels/AcceptanceCriteriaPanel.tsx +104 -0
  55. package/packages/core/src/public/components/panels/AuditLogPanel.tsx +489 -0
  56. package/packages/core/src/public/components/panels/BackgroundPanel.tsx +115 -0
  57. package/packages/core/src/public/components/panels/BikeLanePanel.tsx +214 -0
  58. package/packages/core/src/public/components/panels/DebugPanel.tsx +344 -0
  59. package/packages/core/src/public/components/panels/DiffView.tsx +109 -0
  60. package/packages/core/src/public/components/panels/DiffsPanel.tsx +56 -0
  61. package/packages/core/src/public/components/panels/GitPanel.tsx +260 -0
  62. package/packages/core/src/public/components/panels/HotspotsPanel.tsx +365 -0
  63. package/packages/core/src/public/components/panels/MessageFeed.tsx +39 -0
  64. package/packages/core/src/public/components/panels/MessagePanel.tsx +497 -0
  65. package/packages/core/src/public/components/panels/ProgressPanel.tsx +189 -0
  66. package/packages/core/src/public/components/panels/SettingsPanel.tsx +361 -0
  67. package/packages/core/src/public/components/panels/SprintPanel.tsx +723 -0
  68. package/packages/core/src/public/components/panels/TandemPanel.tsx +104 -0
  69. package/packages/core/src/public/components/panels/TaskTracker.tsx +48 -0
  70. package/packages/core/src/public/components/panels/TeamPanel.tsx +64 -0
  71. package/packages/core/src/public/components/panels/TeamRoster.tsx +67 -0
  72. package/packages/core/src/public/components/panels/TodoPanel.tsx +142 -0
  73. package/packages/core/src/public/components/panels/WorkflowPanel.tsx +224 -0
  74. package/packages/core/src/public/components/panels/index.ts +24 -0
  75. package/packages/core/src/public/components/ui/alert-dialog.tsx +139 -0
  76. package/packages/core/src/public/components/ui/badge.tsx +36 -0
  77. package/packages/core/src/public/components/ui/button.tsx +57 -0
  78. package/packages/core/src/public/components/ui/checkbox.tsx +28 -0
  79. package/packages/core/src/public/components/ui/collapsible.tsx +9 -0
  80. package/packages/core/src/public/components/ui/command.tsx +151 -0
  81. package/packages/core/src/public/components/ui/dialog.tsx +120 -0
  82. package/packages/core/src/public/components/ui/popover.tsx +31 -0
  83. package/packages/core/src/public/components/ui/progress.tsx +28 -0
  84. package/packages/core/src/public/components/ui/scroll-area.tsx +46 -0
  85. package/packages/core/src/public/components/ui/select.tsx +157 -0
  86. package/packages/core/src/public/components/ui/separator.tsx +29 -0
  87. package/packages/core/src/public/components/ui/skeleton.tsx +15 -0
  88. package/packages/core/src/public/components/ui/switch.tsx +27 -0
  89. package/packages/core/src/public/components/ui/toggle-group.tsx +59 -0
  90. package/packages/core/src/public/components/ui/toggle.tsx +43 -0
  91. package/packages/core/src/public/components/ui/tooltip.tsx +30 -0
  92. package/packages/core/src/public/contexts/ClaudeContext.tsx +311 -0
  93. package/packages/core/src/public/contexts/MessageQueueContext.tsx +143 -0
  94. package/packages/core/src/public/css/theme-browser.css +550 -0
  95. package/packages/core/src/public/css/theme-system.css +630 -0
  96. package/packages/core/src/public/hooks/index.ts +49 -0
  97. package/packages/core/src/public/hooks/useAgentLoad.ts +105 -0
  98. package/packages/core/src/public/hooks/useBackgroundTasks.ts +131 -0
  99. package/packages/core/src/public/hooks/useClaude.ts +234 -0
  100. package/packages/core/src/public/hooks/useCodeMarkers.ts +101 -0
  101. package/packages/core/src/public/hooks/useColorScheme.ts +42 -0
  102. package/packages/core/src/public/hooks/useCommandHistory.ts +99 -0
  103. package/packages/core/src/public/hooks/useComplexity.ts +80 -0
  104. package/packages/core/src/public/hooks/useDeadCode.ts +99 -0
  105. package/packages/core/src/public/hooks/useDependencies.ts +82 -0
  106. package/packages/core/src/public/hooks/useDiffs.ts +143 -0
  107. package/packages/core/src/public/hooks/useFileBrowser.ts +73 -0
  108. package/packages/core/src/public/hooks/useFocusPanel.ts +137 -0
  109. package/packages/core/src/public/hooks/useGitStatus.ts +233 -0
  110. package/packages/core/src/public/hooks/useHealthScore.ts +71 -0
  111. package/packages/core/src/public/hooks/useHotspots.ts +123 -0
  112. package/packages/core/src/public/hooks/useLayoutPersistence.ts +141 -0
  113. package/packages/core/src/public/hooks/useMarkdownParser.ts +36 -0
  114. package/packages/core/src/public/hooks/useMarkerActions.ts +234 -0
  115. package/packages/core/src/public/hooks/useMessageQueue.ts +380 -0
  116. package/packages/core/src/public/hooks/useMessageStream.ts +131 -0
  117. package/packages/core/src/public/hooks/usePersona.ts +112 -0
  118. package/packages/core/src/public/hooks/usePlanModeExit.ts +105 -0
  119. package/packages/core/src/public/hooks/useResponsiveLayout.ts +173 -0
  120. package/packages/core/src/public/hooks/useSprint.ts +157 -0
  121. package/packages/core/src/public/hooks/useStatsStrip.ts +204 -0
  122. package/packages/core/src/public/hooks/useStory.ts +135 -0
  123. package/packages/core/src/public/hooks/useSubagentHelper.ts +64 -0
  124. package/packages/core/src/public/hooks/useSyntaxHighlighter.ts +52 -0
  125. package/packages/core/src/public/hooks/useTabCompletion.ts +124 -0
  126. package/packages/core/src/public/hooks/useTandemObservations.ts +165 -0
  127. package/packages/core/src/public/hooks/useTeamMembers.ts +273 -0
  128. package/packages/core/src/public/hooks/useTodos.ts +93 -0
  129. package/packages/core/src/public/hooks/useUserAvatar.ts +54 -0
  130. package/packages/core/src/public/images/cyclist-dark.png +0 -0
  131. package/packages/core/src/public/images/cyclist-light.png +0 -0
  132. package/packages/core/src/public/index.html +14 -0
  133. package/packages/core/src/public/index.tsx +10 -0
  134. package/packages/core/src/public/lib/utils.ts +6 -0
  135. package/packages/core/src/public/styles/dockview-theme.css +376 -0
  136. package/packages/core/src/public/styles/tailwind.css +4454 -0
  137. package/packages/core/src/public/types/message.ts +51 -0
  138. package/packages/core/src/public/utils/avatar-service.ts +73 -0
  139. package/packages/core/src/public/utils/color-presets.ts +940 -0
  140. package/packages/core/src/public/utils/font-presets.ts +362 -0
  141. package/packages/core/src/public/utils/formatDuration.ts +14 -0
  142. package/packages/core/src/public/utils/markdown.ts +249 -0
  143. package/packages/core/src/public/utils/messageFilters.ts +128 -0
  144. package/packages/core/src/public/utils/slash-commands.ts +341 -0
  145. package/packages/core/src/public/utils/subagent-display.ts +146 -0
  146. package/packages/core/src/public/utils/syntax.ts +219 -0
  147. package/packages/core/src/public/utils/toolIntentSummarizer.ts +199 -0
  148. package/packages/core/src/public/utils/toolStackGrouper.ts +106 -0
  149. package/packages/core/src/public/utils/toolTypeColors.ts +45 -0
  150. package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
  151. package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
  152. package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
  153. package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  154. package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
  155. package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
  156. package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
  157. package/pennyfarthing-dist/pf/bc/cli.py +0 -1
  158. package/pennyfarthing-dist/pf/bc/focus.py +0 -1
  159. package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  160. package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  161. package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  162. package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  163. package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  164. package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  165. package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  166. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  167. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  168. package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  169. package/pennyfarthing-dist/pf/bikerack/base_panel.py +0 -1
  170. package/pennyfarthing-dist/pf/bikerack/events.py +1 -7
  171. package/pennyfarthing-dist/pf/bikerack/git_panel.py +273 -10
  172. package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +21 -0
  173. package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +58 -1
  174. package/pennyfarthing-dist/pf/bikerack/tui.py +5 -20
  175. package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  176. package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  177. package/pennyfarthing-dist/pf/bmad/__pycache__/parser.cpython-314.pyc +0 -0
  178. package/pennyfarthing-dist/pf/bmad/parser.py +15 -9
  179. package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  180. package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  181. package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  182. package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
  183. package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
  184. package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
  185. package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  186. package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
  187. package/pennyfarthing-dist/pf/common/pr_config.py +27 -2
  188. package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  189. package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  190. package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
  191. package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  192. package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  193. package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  194. package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  195. package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  196. package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  197. package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  198. package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  199. package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  200. package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  201. package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
  202. package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  204. package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  205. package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  206. package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  207. package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  208. package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  209. package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  210. package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  211. package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  212. package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  213. package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  214. package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  215. package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  216. package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  217. package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  218. package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  219. package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  220. package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  221. package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  222. package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  223. package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  224. package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  225. package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  226. package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  227. package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  228. package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  229. package/pennyfarthing-dist/pf/hooks/cyclist-pretooluse-hook.sh +0 -0
  230. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  231. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  232. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  233. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  234. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  235. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  236. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
  237. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
  238. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
  239. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
  240. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
  241. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
  242. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  243. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
  244. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
  245. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  246. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
  247. package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  248. package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
  249. package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
  250. package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
  251. package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
  252. package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
  253. package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  254. package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  255. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
  256. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
  257. package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
  259. package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
  260. package/pennyfarthing-dist/pf/settings/settings.py +44 -8
  261. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  262. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  263. package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  264. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  265. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  266. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  267. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  268. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
  269. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  270. package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  271. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  272. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  273. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  274. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
  275. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  276. package/pennyfarthing-dist/pf/sprint/story_finish.py +14 -2
  277. package/pennyfarthing-dist/pf/sprint/validator.py +7 -7
  278. package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  279. package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  280. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  281. package/pennyfarthing-dist/pf/tests/test_sprint_validator.py +44 -0
  282. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  283. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
  284. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  285. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
  286. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  287. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  288. package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  289. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  290. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
  291. package/pennyfarthing-dist/scripts/core/agent-session.sh +0 -0
  292. package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
  293. package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +0 -0
  294. package/pennyfarthing-dist/scripts/core/pf.sh +0 -0
  295. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
  296. package/pennyfarthing-dist/scripts/core/prime.sh +0 -0
  297. package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
  298. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
  299. package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
  300. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +0 -0
  301. package/pennyfarthing-dist/scripts/git/release.sh +0 -0
  302. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
  303. package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
  304. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +0 -0
  305. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
  306. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
  307. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  308. package/pennyfarthing-dist/scripts/hooks/dispatcher-template.sh +0 -0
  309. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
  310. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +0 -0
  311. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +0 -0
  312. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
  313. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +0 -0
  314. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
  315. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
  316. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
  317. package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
  318. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
  319. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
  320. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
  321. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
  322. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
  323. package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
  324. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
  325. package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
  326. package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
  327. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
  328. package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
  329. package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
  330. package/pennyfarthing-dist/scripts/lib/env.sh +0 -0
  331. package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
  332. package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
  333. package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
  334. package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
  335. package/pennyfarthing-dist/scripts/lib/run-pf.sh +0 -0
  336. package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
  337. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
  338. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
  339. package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
  340. package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
  341. package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
  342. package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
  343. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
  344. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
  345. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
  346. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
  347. package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
  348. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
  349. package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
  350. package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
  351. package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
  352. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
  353. package/pennyfarthing-dist/scripts/misc/statusline.sh +0 -0
  354. package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
  355. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +0 -0
  356. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
  357. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
  358. package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +0 -0
  359. package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
  360. package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
  361. package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
  362. package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
  363. package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
  364. package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
  365. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +0 -0
  366. package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
  367. package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
  368. package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
  369. package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
  370. package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
  371. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +0 -0
  372. package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
  373. package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
  374. package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
  375. package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
  376. package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
  377. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -0
  378. package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
  379. package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
  380. package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
  381. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +0 -0
  382. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
  383. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
  384. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
  385. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
  386. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
  387. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
  388. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
  389. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
  390. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
  391. package/pennyfarthing-dist/skills/pf-story/scripts/create-story.sh +0 -0
  392. package/pennyfarthing-dist/skills/pf-story/scripts/size-story.sh +0 -0
  393. package/pennyfarthing-dist/skills/pf-story/scripts/story-template.sh +0 -0
  394. package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
  395. package/pennyfarthing-dist/workflows/release/steps/step-10-publish.md +41 -9
  396. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +15 -2
  397. package/packages/core/dist/workflow/__test_context_watch__/.session/.tandem-turn-counter +0 -1
  398. package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-session.md +0 -3
  399. package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-tandem-architect.md +0 -6
  400. package/packages/core/dist/workflow/__test_file_watch__/.session/95-4-tandem-architect.md +0 -6
  401. package/packages/core/dist/workflow/__test_file_watch__/workdir/trigger.ts +0 -1
  402. package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-architect.md +0 -6
  403. package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-toolcalls.jsonl +0 -1
  404. package/pennyfarthing-dist/pf/bikerack/changed_panel.py +0 -201
  405. package/scripts/README.md +0 -41
@@ -0,0 +1,234 @@
1
+ /**
2
+ * useMarkerActions Hook
3
+ *
4
+ * Detects CYCLIST markers in message content and returns action metadata
5
+ * for rendering QuickActions buttons.
6
+ *
7
+ * Story: MSSCI-12787 - Implement CYCLIST Marker Parsing and Action Buttons
8
+ *
9
+ * @see sprint/context/MSSCI-12787-reference/quick-actions.js.deleted
10
+ */
11
+
12
+ import { useMemo } from 'react';
13
+ import { detectMarkers, stripMarkers, MARKER_TYPES } from '../../shared/browser.js';
14
+
15
+ /**
16
+ * Action types that map to different UI presentations
17
+ */
18
+ export type ActionType =
19
+ | 'handoff' // Show agent button + "Not yet" option
20
+ | 'yesno' // Show Yes/No buttons
21
+ | 'open' // Show text input
22
+ | 'choices' // Show choice buttons
23
+ | 'continue' // Show Continue button
24
+ | 'invoke' // Auto-execute (no buttons)
25
+ | 'context_clear'; // Clear context and reload
26
+
27
+ /**
28
+ * Result from marker detection
29
+ */
30
+ export interface MarkerAction {
31
+ type: ActionType;
32
+ value?: string;
33
+ responses?: string[];
34
+ choices?: Array<{ number: number; text: string }>;
35
+ autoExecute?: boolean;
36
+ }
37
+
38
+ /**
39
+ * Extract option text from message content for numbered choices.
40
+ * Looks for patterns like "1. Option text" or "1) Option text".
41
+ */
42
+ function extractChoiceTexts(
43
+ text: string,
44
+ choiceNumbers: number[]
45
+ ): Array<{ number: number; text: string }> {
46
+ if (!text) {
47
+ return choiceNumbers.map(num => ({ number: num, text: `Option ${num}` }));
48
+ }
49
+
50
+ // Remove code blocks
51
+ const withoutCode = text.replace(/```[\s\S]*?```/g, '');
52
+
53
+ // Patterns for numbered lists
54
+ const patterns = [
55
+ /^\s*(\d+)\.\s+(.+)$/gm, // "1. Option text"
56
+ /^\s*(\d+)\)\s+(.+)$/gm, // "1) Option text"
57
+ /\*\*(\d+)[.)]\*\*\s*(.+)/gm, // "**1.** Option text"
58
+ ];
59
+
60
+ const foundChoices = new Map<number, string>();
61
+
62
+ for (const pattern of patterns) {
63
+ pattern.lastIndex = 0;
64
+ let match;
65
+ while ((match = pattern.exec(withoutCode)) !== null) {
66
+ const num = parseInt(match[1], 10);
67
+ if (choiceNumbers.includes(num) && !foundChoices.has(num)) {
68
+ // Clean up the text - remove trailing markdown/formatting
69
+ let optionText = match[2].trim();
70
+ // Remove trailing description after " - " or " — " for cleaner button labels
71
+ const dashIndex = optionText.search(/\s+[-—]\s+/);
72
+ if (dashIndex > 0 && dashIndex < 30) {
73
+ optionText = optionText.substring(0, dashIndex);
74
+ }
75
+ foundChoices.set(num, optionText);
76
+ }
77
+ }
78
+ }
79
+
80
+ // Return choices in order, falling back to "Option N" if not found
81
+ return choiceNumbers.map(num => ({
82
+ number: num,
83
+ text: foundChoices.get(num) || `Option ${num}`,
84
+ }));
85
+ }
86
+
87
+ /**
88
+ * Process detected markers into action metadata
89
+ */
90
+ function processMarkers(
91
+ markers: ReturnType<typeof detectMarkers>,
92
+ fullText: string
93
+ ): MarkerAction | null {
94
+ if (!markers || markers.length === 0) return null;
95
+
96
+ const primaryMarker = markers[0];
97
+
98
+ switch (primaryMarker.type) {
99
+ case MARKER_TYPES.HANDOFF:
100
+ return {
101
+ type: 'handoff',
102
+ value: primaryMarker.value,
103
+ responses: [primaryMarker.value, 'Not yet'],
104
+ };
105
+
106
+ case MARKER_TYPES.INVOKE:
107
+ return {
108
+ type: 'invoke',
109
+ value: primaryMarker.value,
110
+ autoExecute: true,
111
+ };
112
+
113
+ case MARKER_TYPES.QUESTION: {
114
+ if (primaryMarker.value === 'yesno') {
115
+ return {
116
+ type: 'yesno',
117
+ responses: ['Yes', 'No'],
118
+ };
119
+ }
120
+ // Handle open questions - may have suggested prompt: "open" or "open:suggested text"
121
+ if (primaryMarker.value?.startsWith('open')) {
122
+ // Check for suggested prompt after "open:"
123
+ const colonIndex = primaryMarker.value.indexOf(':');
124
+ if (colonIndex !== -1) {
125
+ const suggestion = primaryMarker.value.substring(colonIndex + 1).trim();
126
+ if (suggestion) {
127
+ return {
128
+ type: 'open',
129
+ responses: [suggestion],
130
+ };
131
+ }
132
+ }
133
+ // Plain open question without suggestion
134
+ return {
135
+ type: 'open',
136
+ };
137
+ }
138
+ // Check for accompanying CHOICES marker
139
+ const choicesMarker = markers.find(m => m.type === MARKER_TYPES.CHOICES);
140
+ if (choicesMarker) {
141
+ return processChoicesMarker(choicesMarker.value, fullText);
142
+ }
143
+ return null;
144
+ }
145
+
146
+ case MARKER_TYPES.CHOICES:
147
+ return processChoicesMarker(primaryMarker.value, fullText);
148
+
149
+ case MARKER_TYPES.CONTEXT_CLEAR:
150
+ return {
151
+ type: 'context_clear',
152
+ value: primaryMarker.value,
153
+ };
154
+
155
+ case MARKER_TYPES.CONTINUE:
156
+ return {
157
+ type: 'continue',
158
+ responses: ['Continue'],
159
+ };
160
+
161
+ default:
162
+ return null;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Process a CHOICES marker value into choice buttons
168
+ */
169
+ function processChoicesMarker(
170
+ value: string,
171
+ fullText: string
172
+ ): MarkerAction {
173
+ const choiceValues = value.split(',').map(v => v.trim());
174
+ const firstValue = choiceValues[0];
175
+ const isNumeric = /^\d+$/.test(firstValue);
176
+
177
+ let choices: Array<{ number: number; text: string }>;
178
+
179
+ if (isNumeric) {
180
+ // Legacy numeric format - extract text from message
181
+ const choiceNumbers = choiceValues.map(n => parseInt(n, 10));
182
+ choices = extractChoiceTexts(fullText, choiceNumbers);
183
+ } else {
184
+ // Text label format - use labels directly
185
+ choices = choiceValues.map((text, index) => ({
186
+ number: index + 1,
187
+ text,
188
+ }));
189
+ }
190
+
191
+ return {
192
+ type: 'choices',
193
+ choices,
194
+ responses: choices.map(c => c.text),
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Hook to detect CYCLIST markers in content and return action metadata.
200
+ *
201
+ * @param content - Message content to analyze
202
+ * @returns MarkerAction if marker detected, null otherwise
203
+ *
204
+ * @example
205
+ * ```tsx
206
+ * const actions = useMarkerActions(message.content);
207
+ * if (actions?.type === 'yesno') {
208
+ * // Render Yes/No buttons
209
+ * }
210
+ * ```
211
+ */
212
+ export function useMarkerActions(content: string | undefined): MarkerAction | null {
213
+ return useMemo(() => {
214
+ if (!content) return null;
215
+
216
+ const markers = detectMarkers(content);
217
+ if (!markers) return null;
218
+
219
+ return processMarkers(markers, content);
220
+ }, [content]);
221
+ }
222
+
223
+ /**
224
+ * Strip CYCLIST markers from content for display.
225
+ *
226
+ * @param content - Content that may contain markers
227
+ * @returns Content with markers removed
228
+ */
229
+ export function useStrippedContent(content: string | undefined): string {
230
+ return useMemo(() => {
231
+ if (!content) return '';
232
+ return stripMarkers(content);
233
+ }, [content]);
234
+ }
@@ -0,0 +1,380 @@
1
+ /**
2
+ * useMessageQueue Hook
3
+ *
4
+ * React hook for message queueing while Claude is processing.
5
+ * Story MSSCI-12717 - React Migration
6
+ *
7
+ * Bell Mode (MSSCI-12275):
8
+ * When bell mode is enabled, queued messages are injected into Claude's context
9
+ * via the PostToolUse hook, rather than waiting for Claude to finish.
10
+ *
11
+ * Turn Complete Logic (MSSCI-12450):
12
+ * - Bell mode OFF: All queued messages sent at once when Claude stops
13
+ * - Bell mode ON: Messages injected by hook; remaining queue sent on stop
14
+ *
15
+ * Bell Injected Display (Audit Fix 2026-02-03):
16
+ * - When hook consumes a message, notify via onBellConsumed callback
17
+ * - MessagePanel displays the injected message with 🔔 indicator
18
+ * - "Send Now" button allows immediate injection (abort + submit)
19
+ */
20
+
21
+ import { useState, useCallback, useRef, useEffect } from 'react';
22
+
23
+ export interface QueuedMessage {
24
+ text: string;
25
+ images: Array<{
26
+ dataUrl: string;
27
+ mimeType: string;
28
+ filename: string;
29
+ }>;
30
+ }
31
+
32
+ /** Callback when a message is consumed by bell mode hook */
33
+ export type BellConsumedCallback = (message: QueuedMessage) => void;
34
+
35
+ /** Functions needed to inject a message immediately */
36
+ export interface InjectDependencies {
37
+ abort: () => void;
38
+ submit: (text: string, images: QueuedMessage['images']) => void;
39
+ }
40
+
41
+ interface UseMessageQueueResult {
42
+ queue: QueuedMessage[];
43
+ queueCount: number;
44
+ isProcessing: boolean;
45
+ bellMode: boolean;
46
+ queuePaused: boolean;
47
+ queueMessage: (message: QueuedMessage) => boolean;
48
+ dequeueMessage: () => QueuedMessage | null;
49
+ removeFromQueue: (index: number) => void;
50
+ clearQueue: () => void;
51
+ setProcessing: (value: boolean) => void;
52
+ setBellMode: (value: boolean) => void;
53
+ pauseQueue: () => void;
54
+ resumeQueue: () => void;
55
+ handleTurnComplete: (onSubmit: (text: string, images: QueuedMessage['images']) => void) => void;
56
+ /** Register callback for when bell mode hook consumes a message */
57
+ onBellConsumed: (callback: BellConsumedCallback) => () => void;
58
+ /** Immediately inject a queued message (abort current + submit) */
59
+ injectMessage: (index: number, deps: InjectDependencies) => Promise<boolean>;
60
+ }
61
+
62
+ const MAX_QUEUE_SIZE = 10;
63
+ const QUEUE_KEY = 'cyclist-message-queue';
64
+
65
+ /**
66
+ * Sync queue to bell-queue.json for PostToolUse hook
67
+ * Only writes when bell mode is enabled
68
+ */
69
+ async function syncQueueToFile(queue: QueuedMessage[]): Promise<void> {
70
+ try {
71
+ await fetch('/api/bell-queue', {
72
+ method: 'POST',
73
+ headers: { 'Content-Type': 'application/json' },
74
+ body: JSON.stringify(queue),
75
+ });
76
+ } catch (err) {
77
+ // Ignore errors - bell queue sync is best-effort
78
+ console.debug('[MessageQueue] Bell queue sync error:', err);
79
+ }
80
+ }
81
+
82
+ export function useMessageQueue(): UseMessageQueueResult {
83
+ const [queue, setQueue] = useState<QueuedMessage[]>(() => {
84
+ try {
85
+ const stored = localStorage.getItem(QUEUE_KEY);
86
+ return stored ? JSON.parse(stored) : [];
87
+ } catch {
88
+ return [];
89
+ }
90
+ });
91
+ const [isProcessing, setIsProcessing] = useState(false);
92
+ const [bellMode, setBellModeState] = useState(false);
93
+ const [queuePaused, setQueuePaused] = useState(false);
94
+ const queueRef = useRef(queue);
95
+ const bellModeRef = useRef(bellMode);
96
+
97
+ // Callbacks for bell-consumed events (notifies MessagePanel to display)
98
+ const bellConsumedCallbacksRef = useRef<Set<BellConsumedCallback>>(new Set());
99
+
100
+ // Keep refs in sync
101
+ useEffect(() => {
102
+ queueRef.current = queue;
103
+ }, [queue]);
104
+
105
+ useEffect(() => {
106
+ bellModeRef.current = bellMode;
107
+ }, [bellMode]);
108
+
109
+ // Load bell mode from settings on mount
110
+ useEffect(() => {
111
+ // Load via REST
112
+ fetch('/api/settings')
113
+ .then(res => res.json())
114
+ .then((settings: Record<string, unknown>) => {
115
+ const workflow = settings?.workflow as Record<string, unknown> | undefined;
116
+ if (workflow?.bell_mode) {
117
+ setBellModeState(true);
118
+ }
119
+ })
120
+ .catch(err => console.debug('[MessageQueue] Failed to load settings:', err));
121
+
122
+ // Subscribe to settings changes via WebSocket
123
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
124
+ const ws = new WebSocket(`${protocol}//${window.location.host}/ws/settings`);
125
+
126
+ ws.onmessage = (event) => {
127
+ try {
128
+ const data = JSON.parse(event.data);
129
+ if (data.type === 'init' || data.type === 'update') {
130
+ const workflow = data.settings?.workflow as Record<string, unknown> | undefined;
131
+ setBellModeState(!!workflow?.bell_mode);
132
+ }
133
+ } catch {
134
+ // Ignore parse errors
135
+ }
136
+ };
137
+
138
+ return () => ws.close();
139
+ }, []);
140
+
141
+ // Listen for bell-consumed WebSocket events
142
+ useEffect(() => {
143
+ // Connect to bell WebSocket for consumed events
144
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
145
+ const wsUrl = `${protocol}//${window.location.host}/ws/bell`;
146
+
147
+ let ws: WebSocket | null = null;
148
+ let reconnectTimeout: ReturnType<typeof setTimeout>;
149
+
150
+ const connect = () => {
151
+ try {
152
+ ws = new WebSocket(wsUrl);
153
+
154
+ ws.onmessage = (event) => {
155
+ try {
156
+ const data = JSON.parse(event.data);
157
+ if (data.type === 'bell-consumed') {
158
+ // Get the consumed message BEFORE dequeuing (for display callback)
159
+ const consumedMessage = queueRef.current[0];
160
+
161
+ // Dequeue the first message when hook consumes it
162
+ setQueue(prev => {
163
+ if (prev.length === 0) return prev;
164
+ const newQueue = prev.slice(1);
165
+ try {
166
+ localStorage.setItem(QUEUE_KEY, JSON.stringify(newQueue));
167
+ } catch { /* ignore */ }
168
+ return newQueue;
169
+ });
170
+
171
+ // Notify all registered callbacks so MessagePanel can display
172
+ if (consumedMessage) {
173
+ console.log('[MessageQueue] Bell consumed, notifying callbacks:', consumedMessage.text);
174
+ bellConsumedCallbacksRef.current.forEach(cb => {
175
+ try {
176
+ cb(consumedMessage);
177
+ } catch (err) {
178
+ console.error('[MessageQueue] Bell consumed callback error:', err);
179
+ }
180
+ });
181
+ }
182
+ }
183
+ } catch (err) {
184
+ console.error('[MessageQueue] Failed to parse bell message:', err);
185
+ }
186
+ };
187
+
188
+ ws.onclose = () => {
189
+ console.log('[MessageQueue] Bell WebSocket closed, reconnecting...');
190
+ reconnectTimeout = setTimeout(connect, 2000);
191
+ };
192
+
193
+ ws.onerror = () => {
194
+ // Will trigger onclose
195
+ };
196
+ } catch (err) {
197
+ console.debug('[MessageQueue] Bell WebSocket init failed:', err);
198
+ }
199
+ };
200
+
201
+ connect();
202
+
203
+ return () => {
204
+ clearTimeout(reconnectTimeout);
205
+ ws?.close();
206
+ };
207
+ }, []);
208
+
209
+ const saveQueue = useCallback((newQueue: QueuedMessage[]) => {
210
+ try {
211
+ localStorage.setItem(QUEUE_KEY, JSON.stringify(newQueue));
212
+ } catch {
213
+ // Ignore localStorage errors
214
+ }
215
+ // Always sync to file (API checks bell mode)
216
+ syncQueueToFile(newQueue);
217
+ }, []);
218
+
219
+ const queueMessage = useCallback((message: QueuedMessage): boolean => {
220
+ if (!message.text.trim() && message.images.length === 0) {
221
+ return false;
222
+ }
223
+ if (queueRef.current.length >= MAX_QUEUE_SIZE) {
224
+ return false;
225
+ }
226
+
227
+ setQueue(prev => {
228
+ const newQueue = [...prev, message];
229
+ saveQueue(newQueue);
230
+ return newQueue;
231
+ });
232
+ return true;
233
+ }, [saveQueue]);
234
+
235
+ const dequeueMessage = useCallback((): QueuedMessage | null => {
236
+ let dequeued: QueuedMessage | null = null;
237
+ setQueue(prev => {
238
+ if (prev.length === 0) return prev;
239
+ dequeued = prev[0];
240
+ const newQueue = prev.slice(1);
241
+ saveQueue(newQueue);
242
+ return newQueue;
243
+ });
244
+ return dequeued;
245
+ }, [saveQueue]);
246
+
247
+ const removeFromQueue = useCallback((index: number) => {
248
+ setQueue(prev => {
249
+ if (index < 0 || index >= prev.length) return prev;
250
+ const newQueue = [...prev.slice(0, index), ...prev.slice(index + 1)];
251
+ saveQueue(newQueue);
252
+ return newQueue;
253
+ });
254
+ }, [saveQueue]);
255
+
256
+ const clearQueue = useCallback(() => {
257
+ setQueue([]);
258
+ saveQueue([]);
259
+ }, [saveQueue]);
260
+
261
+ const setProcessing = useCallback((value: boolean) => {
262
+ setIsProcessing(value);
263
+ }, []);
264
+
265
+ const setBellMode = useCallback((value: boolean) => {
266
+ setBellModeState(value);
267
+ }, []);
268
+
269
+ const pauseQueue = useCallback(() => {
270
+ setQueuePaused(true);
271
+ console.log('[MessageQueue] Queue paused');
272
+ }, []);
273
+
274
+ const resumeQueue = useCallback(() => {
275
+ setQueuePaused(false);
276
+ console.log('[MessageQueue] Queue resumed');
277
+ }, []);
278
+
279
+ /**
280
+ * Handle turn complete - send queued messages based on bell mode
281
+ * Called when Claude's turn completes (via onComplete callback)
282
+ *
283
+ * Bell mode OFF: All queued messages sent at once
284
+ * Bell mode ON: Remaining messages (not consumed by hook) sent
285
+ */
286
+ const handleTurnComplete = useCallback((onSubmit: (text: string, images: QueuedMessage['images']) => void) => {
287
+ if (queuePaused) {
288
+ console.log('[MessageQueue] Queue paused, skipping turn complete');
289
+ return;
290
+ }
291
+
292
+ const currentQueue = queueRef.current;
293
+ if (currentQueue.length === 0) {
294
+ return;
295
+ }
296
+
297
+ console.log('[MessageQueue] Turn complete, sending', currentQueue.length, 'queued messages');
298
+
299
+ // Send all queued messages
300
+ for (const msg of currentQueue) {
301
+ onSubmit(msg.text, msg.images);
302
+ }
303
+
304
+ // Clear the queue
305
+ setQueue([]);
306
+ saveQueue([]);
307
+ }, [queuePaused, saveQueue]);
308
+
309
+ /**
310
+ * Register a callback for when bell mode hook consumes a message.
311
+ * Returns unsubscribe function.
312
+ */
313
+ const onBellConsumed = useCallback((callback: BellConsumedCallback): (() => void) => {
314
+ bellConsumedCallbacksRef.current.add(callback);
315
+ return () => {
316
+ bellConsumedCallbacksRef.current.delete(callback);
317
+ };
318
+ }, []);
319
+
320
+ /**
321
+ * Immediately inject a queued message (abort current + submit).
322
+ * Used for "Send Now" button functionality.
323
+ *
324
+ * @param index - Index of message to inject
325
+ * @param deps - abort and submit functions from Claude context
326
+ * @returns true if message was injected
327
+ */
328
+ const injectMessage = useCallback(async (
329
+ index: number,
330
+ deps: InjectDependencies
331
+ ): Promise<boolean> => {
332
+ const currentQueue = queueRef.current;
333
+ if (index < 0 || index >= currentQueue.length) {
334
+ return false;
335
+ }
336
+
337
+ // Get the message before removing
338
+ const message = currentQueue[index];
339
+ if (!message) return false;
340
+
341
+ // Remove from queue immediately
342
+ setQueue(prev => {
343
+ const newQueue = [...prev.slice(0, index), ...prev.slice(index + 1)];
344
+ saveQueue(newQueue);
345
+ return newQueue;
346
+ });
347
+
348
+ // Abort Claude if processing
349
+ console.log('[MessageQueue] Injecting message, aborting Claude...');
350
+ deps.abort();
351
+
352
+ // Brief delay to let abort complete
353
+ await new Promise(resolve => setTimeout(resolve, 100));
354
+
355
+ // Submit the message
356
+ console.log('[MessageQueue] Submitting injected message:', message.text);
357
+ deps.submit(message.text, message.images);
358
+
359
+ return true;
360
+ }, [saveQueue]);
361
+
362
+ return {
363
+ queue,
364
+ queueCount: queue.length,
365
+ isProcessing,
366
+ bellMode,
367
+ queuePaused,
368
+ queueMessage,
369
+ dequeueMessage,
370
+ removeFromQueue,
371
+ clearQueue,
372
+ setProcessing,
373
+ setBellMode,
374
+ pauseQueue,
375
+ resumeQueue,
376
+ handleTurnComplete,
377
+ onBellConsumed,
378
+ injectMessage,
379
+ };
380
+ }