@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,497 @@
1
+ /**
2
+ * MessagePanel - Center panel containing MessageView and Editor
3
+ *
4
+ * Story MSSCI-12717 - React Migration
5
+ *
6
+ * This panel wraps the message display and editor input in a single
7
+ * component for the docking workspace center region.
8
+ */
9
+
10
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
11
+ import MessageView from '../MessageView';
12
+ import Editor, { PastedImage } from '../Editor';
13
+ import { ControlBar, useControlBar } from '../ControlBar';
14
+ import PersonaHeader from '../PersonaHeader';
15
+ import StatsStrip from '../StatsStrip';
16
+ import { useMessageQueueContext, QueuedMessage, InjectDependencies } from '../../contexts/MessageQueueContext';
17
+ import { useClaudeContext } from '../../contexts/ClaudeContext';
18
+ import { usePersona } from '../../hooks/usePersona';
19
+ import type { ClaudeMessage } from '../../hooks/useClaude';
20
+ import type { MessageData } from '../../types/message';
21
+
22
+ // Content block types from SDK nested format (AC5: Story 75-5)
23
+ interface SDKTextBlock {
24
+ type: 'text';
25
+ text: string;
26
+ }
27
+
28
+ interface SDKToolUseBlock {
29
+ type: 'tool_use';
30
+ id?: string;
31
+ name?: string;
32
+ input?: Record<string, unknown>;
33
+ }
34
+
35
+ interface SDKToolResultBlock {
36
+ type: 'tool_result';
37
+ tool_use_id?: string;
38
+ content?: string;
39
+ is_error?: boolean;
40
+ }
41
+
42
+ type SDKContentBlock = SDKTextBlock | SDKToolUseBlock | SDKToolResultBlock | { type: string; text?: string };
43
+
44
+ interface SDKMessage {
45
+ type: string;
46
+ message?: {
47
+ content?: Array<SDKContentBlock>;
48
+ };
49
+ content?: string | Array<SDKContentBlock>;
50
+ tool_name?: string;
51
+ tool_id?: string;
52
+ input?: Record<string, unknown>;
53
+ output?: string;
54
+ is_error?: boolean;
55
+ /** Duration in milliseconds for tool execution (MSSCI-13402) */
56
+ durationMs?: number;
57
+ parent_tool_use_id?: string | null;
58
+ subagent_type?: string;
59
+ subagent_name?: string;
60
+ }
61
+
62
+ // =============================================================================
63
+ // Message Transform
64
+ // =============================================================================
65
+
66
+ /**
67
+ * Check if content is Stop hook feedback that should be hidden.
68
+ * Stop hook feedback messages are internal enforcement messages, not user-facing content.
69
+ * Example: "Stop hook feedback:\nMissing CYCLIST marker..."
70
+ */
71
+ function isStopHookFeedback(content: string): boolean {
72
+ return content.startsWith('Stop hook feedback:');
73
+ }
74
+
75
+ /**
76
+ * Transform SDK message to MessageData.
77
+ * Returns an array because nested SDK format may contain both text and tool_use blocks.
78
+ * (AC5: Story 75-5 - Extract tool_use from nested SDK format)
79
+ */
80
+ function transformMessage(sdkMessage: SDKMessage): MessageData[] {
81
+ const timestamp = Date.now();
82
+ // Map parent_tool_use_id to parent_id for subagent grouping
83
+ const parent_id = sdkMessage.parent_tool_use_id || undefined;
84
+ const subagent_type = sdkMessage.subagent_type;
85
+ const subagent_name = sdkMessage.subagent_name;
86
+
87
+ const results: MessageData[] = [];
88
+
89
+ // Handle assistant/message type
90
+ if (sdkMessage.type === 'assistant' || sdkMessage.type === 'message') {
91
+ const contentArray = sdkMessage.message?.content || sdkMessage.content;
92
+
93
+ if (Array.isArray(contentArray)) {
94
+ // Extract text blocks
95
+ const textContent = contentArray
96
+ .filter((block): block is SDKTextBlock =>
97
+ block.type === 'text' && typeof (block as SDKTextBlock).text === 'string'
98
+ )
99
+ .map(block => block.text)
100
+ .join('');
101
+
102
+ // Add text message if we have content (AC5: only if >= 3 chars)
103
+ // Filter out Stop hook feedback messages - they're internal enforcement, not user content
104
+ if (textContent && textContent.trim().length >= 3 && !isStopHookFeedback(textContent.trim())) {
105
+ results.push({
106
+ type: 'agent',
107
+ content: textContent,
108
+ timestamp,
109
+ isStreaming: true,
110
+ parent_id,
111
+ subagent_type,
112
+ subagent_name,
113
+ });
114
+ }
115
+
116
+ // Extract tool_use blocks from nested SDK format (AC5: Story 75-5)
117
+ const toolUseBlocks = contentArray.filter(
118
+ (block): block is SDKToolUseBlock => block.type === 'tool_use'
119
+ );
120
+
121
+ for (const toolBlock of toolUseBlocks) {
122
+ results.push({
123
+ type: 'tool_use',
124
+ tool_name: toolBlock.name,
125
+ tool_id: toolBlock.id,
126
+ input: toolBlock.input,
127
+ timestamp,
128
+ parent_id,
129
+ subagent_type,
130
+ subagent_name,
131
+ });
132
+ }
133
+ } else if (typeof contentArray === 'string') {
134
+ // Filter out Stop hook feedback messages - they're internal enforcement, not user content
135
+ if (contentArray.trim().length >= 3 && !isStopHookFeedback(contentArray.trim())) {
136
+ results.push({
137
+ type: 'agent',
138
+ content: contentArray,
139
+ timestamp,
140
+ isStreaming: true,
141
+ parent_id,
142
+ subagent_type,
143
+ subagent_name,
144
+ });
145
+ }
146
+ }
147
+
148
+ return results;
149
+ }
150
+
151
+ // Handle user messages (may contain tool_result blocks for completed Task tools)
152
+ if (sdkMessage.type === 'user') {
153
+ let content = '';
154
+ const contentArray = sdkMessage.message?.content || sdkMessage.content;
155
+ if (Array.isArray(contentArray)) {
156
+ content = contentArray
157
+ .filter((block): block is SDKTextBlock =>
158
+ block.type === 'text' && typeof (block as SDKTextBlock).text === 'string'
159
+ )
160
+ .map(block => block.text)
161
+ .join('');
162
+
163
+ // MSSCI-14394: Extract tool_result blocks from user messages.
164
+ // The SDK delivers tool results inside user-type messages with tool_use_id
165
+ // matching the original tool_use's tool_id. Without extracting these,
166
+ // the subagent cleanup code never fires and spans accumulate forever.
167
+ const toolResultBlocks = contentArray.filter(
168
+ (block): block is SDKToolResultBlock => block.type === 'tool_result'
169
+ );
170
+ for (const resultBlock of toolResultBlocks) {
171
+ results.push({
172
+ type: 'tool_result',
173
+ tool_id: resultBlock.tool_use_id,
174
+ content: typeof resultBlock.content === 'string' ? resultBlock.content : '',
175
+ timestamp,
176
+ is_error: resultBlock.is_error,
177
+ });
178
+ }
179
+ } else if (typeof contentArray === 'string') {
180
+ content = contentArray;
181
+ }
182
+
183
+ if (content) {
184
+ results.push({
185
+ type: 'user',
186
+ content,
187
+ timestamp,
188
+ parent_id,
189
+ subagent_type,
190
+ subagent_name,
191
+ });
192
+ }
193
+
194
+ return results;
195
+ }
196
+
197
+ // Handle discrete tool_use (not nested in assistant message)
198
+ if (sdkMessage.type === 'tool_use') {
199
+ results.push({
200
+ type: 'tool_use',
201
+ tool_name: sdkMessage.tool_name,
202
+ tool_id: sdkMessage.tool_id,
203
+ input: sdkMessage.input,
204
+ timestamp,
205
+ parent_id,
206
+ subagent_type,
207
+ subagent_name,
208
+ });
209
+ return results;
210
+ }
211
+
212
+ // Handle tool_result (MSSCI-13402: include is_error and durationMs)
213
+ if (sdkMessage.type === 'tool_result') {
214
+ results.push({
215
+ type: 'tool_result',
216
+ tool_id: sdkMessage.tool_id,
217
+ content: typeof sdkMessage.output === 'string' ? sdkMessage.output : '',
218
+ timestamp,
219
+ parent_id,
220
+ subagent_type,
221
+ subagent_name,
222
+ is_error: sdkMessage.is_error,
223
+ durationMs: sdkMessage.durationMs,
224
+ });
225
+ return results;
226
+ }
227
+
228
+ return results;
229
+ }
230
+
231
+ // =============================================================================
232
+ // MessagePanel Component
233
+ // =============================================================================
234
+
235
+ export function MessagePanel(): React.ReactElement {
236
+ const [messages, setMessages] = useState<MessageData[]>([]);
237
+ const [isProcessing, setIsProcessing] = useState(false);
238
+ const {
239
+ isRunning,
240
+ isStopping,
241
+ bellMode,
242
+ relayMode,
243
+ contextPercent,
244
+ currentAgent,
245
+ handleStop,
246
+ handleForceStop,
247
+ handleReset,
248
+ handleBellModeChange,
249
+ handleRelayModeChange,
250
+ handleTirePump,
251
+ handleAgentSwitch,
252
+ } = useControlBar();
253
+
254
+ // Claude context for WebSocket communication
255
+ const { send, abort, onMessage, onComplete, onError, onUserMessage, onClear, isConnected } = useClaudeContext();
256
+
257
+ // Persona context - capture current persona to stamp on agent messages
258
+ const { persona } = usePersona();
259
+ const personaRef = useRef(persona);
260
+ personaRef.current = persona;
261
+
262
+ // Message queue context for turn complete handling and bell mode (shared with Editor)
263
+ const { handleTurnComplete, pauseQueue, onBellConsumed, injectMessage } = useMessageQueueContext();
264
+
265
+ // Ref to track the submit function for turn complete
266
+ const submitRef = useRef<(text: string, images: QueuedMessage['images']) => void>();
267
+
268
+ // Subscribe to bell-consumed events to display injected messages
269
+ useEffect(() => {
270
+ const unsubscribe = onBellConsumed((consumedMessage) => {
271
+ // Add bell-injected message to the message view
272
+ setMessages(prev => [...prev, {
273
+ type: 'bell_injected',
274
+ content: consumedMessage.text,
275
+ timestamp: Date.now(),
276
+ imageCount: consumedMessage.images.length > 0 ? consumedMessage.images.length : undefined,
277
+ }]);
278
+ });
279
+
280
+ return unsubscribe;
281
+ }, [onBellConsumed]);
282
+
283
+ // Create inject dependencies for "Send Now" button
284
+ const injectDeps: InjectDependencies = {
285
+ abort,
286
+ submit: (text, images) => {
287
+ // User message display is handled by onUserMessage subscription
288
+ setIsProcessing(true);
289
+ send(text, images);
290
+ },
291
+ };
292
+
293
+ // Handle incoming SDK message - stamp current persona on agent messages
294
+ const handleSDKMessage = useCallback((sdkMessage: ClaudeMessage) => {
295
+ const transformed = transformMessage(sdkMessage as SDKMessage);
296
+ if (transformed.length > 0) {
297
+ const p = personaRef.current;
298
+ const stamped = transformed.map(msg =>
299
+ msg.type === 'agent' && p
300
+ ? { ...msg, agentSlug: p.slug ?? undefined, agentTheme: p.theme ?? undefined, agentCharacter: p.character ?? undefined }
301
+ : msg
302
+ );
303
+
304
+ // MSSCI-14394: When tool_results arrive for completed Task tools, remove their
305
+ // subagent messages from the view (they have parent_id matching the tool_result's tool_id).
306
+ const completedTaskIds = stamped
307
+ .filter(m => m.type === 'tool_result' && !m.parent_id && m.tool_id)
308
+ .map(m => m.tool_id!);
309
+
310
+ if (completedTaskIds.length > 0) {
311
+ setMessages(prev => {
312
+ const idsToRemove = new Set(completedTaskIds.filter(id =>
313
+ prev.some(m => m.parent_id === id)
314
+ ));
315
+ if (idsToRemove.size > 0) {
316
+ return [...prev.filter(m => !m.parent_id || !idsToRemove.has(m.parent_id)), ...stamped];
317
+ }
318
+ return [...prev, ...stamped];
319
+ });
320
+ } else {
321
+ setMessages(prev => [...prev, ...stamped]);
322
+ }
323
+ }
324
+ }, []);
325
+
326
+ // Handle query completion
327
+ const handleComplete = useCallback(() => {
328
+ setIsProcessing(false);
329
+ // Mark ALL agent messages as no longer streaming
330
+ setMessages(prev => prev.map(msg =>
331
+ msg.type === 'agent' && msg.isStreaming
332
+ ? { ...msg, isStreaming: false }
333
+ : msg
334
+ ));
335
+
336
+ // MSSCI-12450: Process queued messages on turn complete
337
+ // Uses the submit function via ref to send queued messages
338
+ if (submitRef.current) {
339
+ handleTurnComplete(submitRef.current);
340
+ }
341
+ }, [handleTurnComplete]);
342
+
343
+ // Handle SDK error
344
+ const handleError = useCallback((error: string) => {
345
+ setIsProcessing(false);
346
+ setMessages(prev => [...prev, {
347
+ type: 'agent',
348
+ content: `Error: ${error}`,
349
+ timestamp: Date.now(),
350
+ }]);
351
+ }, []);
352
+
353
+ // Subscribe to user messages sent via send() - displays ALL user messages
354
+ // regardless of origin (Editor, QuickActions, GitPanel, etc.)
355
+ useEffect(() => {
356
+ const cleanup = onUserMessage((userMessage) => {
357
+ setMessages(prev => [...prev, {
358
+ type: 'user',
359
+ content: userMessage.prompt,
360
+ timestamp: userMessage.timestamp,
361
+ imageCount: userMessage.images.length > 0 ? userMessage.images.length : undefined,
362
+ }]);
363
+ });
364
+ return cleanup;
365
+ }, [onUserMessage]);
366
+
367
+ // Subscribe to clear events — insert a divider message
368
+ useEffect(() => {
369
+ const cleanup = onClear(() => {
370
+ setMessages(prev => [...prev, {
371
+ type: 'context_cleared',
372
+ content: 'Context cleared',
373
+ timestamp: Date.now(),
374
+ }]);
375
+ setIsProcessing(false);
376
+ });
377
+ return cleanup;
378
+ }, [onClear]);
379
+
380
+ // Connect to Claude events via WebSocket context
381
+ useEffect(() => {
382
+ if (!isConnected) {
383
+ console.log('[MessagePanel] Claude WebSocket not connected');
384
+ return;
385
+ }
386
+
387
+ const cleanupMessage = onMessage(handleSDKMessage);
388
+ const cleanupComplete = onComplete(handleComplete);
389
+ const cleanupError = onError(handleError);
390
+
391
+ // Cleanup listeners on unmount or dependency change to prevent duplicates
392
+ return () => {
393
+ cleanupMessage();
394
+ cleanupComplete();
395
+ cleanupError();
396
+ };
397
+ }, [isConnected, onMessage, onComplete, onError, handleSDKMessage, handleComplete, handleError]);
398
+
399
+ // Handle editor submit
400
+ const handleSubmit = useCallback((text: string, images: PastedImage[]) => {
401
+ // User message display is handled by onUserMessage subscription
402
+ setIsProcessing(true);
403
+
404
+ // Send to Claude via WebSocket
405
+ send(text, images);
406
+ }, [send]);
407
+
408
+ // Update submit ref for turn complete
409
+ useEffect(() => {
410
+ submitRef.current = handleSubmit;
411
+ }, [handleSubmit]);
412
+
413
+ // Pause queue on stop (prevent auto-advance after abort)
414
+ const handleStopWithPause = useCallback(() => {
415
+ pauseQueue();
416
+ handleStop();
417
+ }, [pauseQueue, handleStop]);
418
+
419
+ // Resizable editor panel
420
+ const [editorHeight, setEditorHeight] = useState<number | null>(null);
421
+ const panelRef = useRef<HTMLDivElement>(null);
422
+ const isDragging = useRef(false);
423
+
424
+ const handleResizeStart = useCallback((e: React.MouseEvent) => {
425
+ e.preventDefault();
426
+ isDragging.current = true;
427
+ const startY = e.clientY;
428
+ const panelEl = panelRef.current;
429
+ if (!panelEl) return;
430
+ const panelRect = panelEl.getBoundingClientRect();
431
+ const startEditorHeight = editorHeight ?? panelEl.querySelector('.message-panel-editor')?.getBoundingClientRect().height ?? 150;
432
+
433
+ const onMouseMove = (ev: MouseEvent) => {
434
+ if (!isDragging.current) return;
435
+ const delta = startY - ev.clientY;
436
+ const newHeight = Math.max(80, Math.min(startEditorHeight + delta, panelRect.height * 0.7));
437
+ setEditorHeight(newHeight);
438
+ };
439
+ const onMouseUp = () => {
440
+ isDragging.current = false;
441
+ document.removeEventListener('mousemove', onMouseMove);
442
+ document.removeEventListener('mouseup', onMouseUp);
443
+ document.body.style.cursor = '';
444
+ document.body.style.userSelect = '';
445
+ };
446
+ document.addEventListener('mousemove', onMouseMove);
447
+ document.addEventListener('mouseup', onMouseUp);
448
+ document.body.style.cursor = 'row-resize';
449
+ document.body.style.userSelect = 'none';
450
+ }, [editorHeight]);
451
+
452
+ return (
453
+ <div className="message-panel" data-testid="message-panel" ref={panelRef}>
454
+ <PersonaHeader />
455
+ <div className="message-panel-content">
456
+ <MessageView messages={messages} />
457
+ </div>
458
+ <div
459
+ className="message-panel-resize-handle"
460
+ onMouseDown={handleResizeStart}
461
+ />
462
+ <div
463
+ className="message-panel-editor"
464
+ style={editorHeight != null ? { height: editorHeight, flexShrink: 0 } : undefined}
465
+ >
466
+ <div className="editor-with-controls">
467
+ <div className="editor-area">
468
+ <Editor
469
+ onSubmit={handleSubmit}
470
+ isProcessing={isProcessing || isRunning}
471
+ placeholder="Send a message..."
472
+ onInject={(index) => injectMessage(index, injectDeps)}
473
+ />
474
+ </div>
475
+ <ControlBar
476
+ isRunning={isRunning || isProcessing}
477
+ isStopping={isStopping}
478
+ onStop={handleStopWithPause}
479
+ onForceStop={handleForceStop}
480
+ onReset={handleReset}
481
+ bellMode={bellMode}
482
+ relayMode={relayMode}
483
+ onBellModeChange={handleBellModeChange}
484
+ onRelayModeChange={handleRelayModeChange}
485
+ contextPercent={contextPercent}
486
+ currentAgent={currentAgent}
487
+ onTirePump={handleTirePump}
488
+ onAgentSwitch={handleAgentSwitch}
489
+ />
490
+ </div>
491
+ </div>
492
+ <StatsStrip />
493
+ </div>
494
+ );
495
+ }
496
+
497
+ export default MessagePanel;
@@ -0,0 +1,189 @@
1
+ /**
2
+ * ProgressPanel - At-a-glance story dashboard
3
+ *
4
+ * Unified panel combining story context, workflow phase, AC completion,
5
+ * todo status, git changes, and context window usage.
6
+ *
7
+ * Story: MSSCI-14966 / 103-11
8
+ * Epic: 103 - BikeRack TUI
9
+ */
10
+
11
+ import React from 'react';
12
+ import { useStory } from '../../hooks/useStory';
13
+ import { useSprint } from '../../hooks/useSprint';
14
+ import { useTodos } from '../../hooks/useTodos';
15
+ import { useGitStatus } from '../../hooks/useGitStatus';
16
+ import { useStatsStrip } from '../../hooks/useStatsStrip';
17
+
18
+ export function ProgressPanel(): React.ReactElement {
19
+ const { story, isLoading: storyLoading, error: storyError } = useStory();
20
+ const { data: sprintData, isLoading: sprintLoading } = useSprint();
21
+ const { todos } = useTodos();
22
+ const { gitStatus } = useGitStatus();
23
+ const { context } = useStatsStrip();
24
+
25
+ // Loading state
26
+ if (storyLoading || sprintLoading) {
27
+ return (
28
+ <div className="progress-panel loading" data-testid="progress-panel">
29
+ <div className="progress-loading">Loading...</div>
30
+ </div>
31
+ );
32
+ }
33
+
34
+ // Error state
35
+ if (storyError) {
36
+ return (
37
+ <div className="progress-panel" data-testid="progress-panel">
38
+ <div className="error-message">{storyError.message}</div>
39
+ </div>
40
+ );
41
+ }
42
+
43
+ // Empty state
44
+ const currentStory = sprintData?.currentStory;
45
+ if (!story && !currentStory) {
46
+ return (
47
+ <div className="progress-panel" data-testid="progress-panel">
48
+ <div className="empty-state">
49
+ <div>No active story</div>
50
+ <div className="empty-hint">Run /sprint work to start</div>
51
+ </div>
52
+ </div>
53
+ );
54
+ }
55
+
56
+ // Merge data from story + sprint hooks
57
+ const storyId = story?.id ?? currentStory?.id ?? '';
58
+ const storyTitle = story?.title ?? currentStory?.title ?? '';
59
+ const storyPoints = story?.points ?? currentStory?.points ?? 0;
60
+ const jiraKey = currentStory?.jiraKey ?? '';
61
+ const storyStatus = story?.status ?? currentStory?.status ?? '';
62
+ const epic = story?.epic ?? currentStory?.epic ?? '';
63
+ const displayStatus = storyStatus.replace(/_/g, ' ');
64
+
65
+ // Workflow
66
+ const workflowType = story?.workflow ?? null;
67
+ const phases = story?.workflowPhases ?? null;
68
+ const hasWorkflow = workflowType || (phases && phases.length > 0);
69
+
70
+ // AC
71
+ const criteria = story?.criteria ?? null;
72
+ const hasCriteria = criteria != null && criteria.length > 0;
73
+ const completedAC = hasCriteria ? criteria.filter((c: any) => c.completed).length : 0;
74
+ const totalAC = hasCriteria ? criteria.length : 0;
75
+ const acPercent = totalAC > 0 ? Math.round((completedAC / totalAC) * 100) : 0;
76
+
77
+ // Todos
78
+ const hasTodos = todos && todos.length > 0;
79
+ const completedTodos = hasTodos ? todos.filter((t: any) => t.status === 'completed').length : 0;
80
+ const totalTodos = hasTodos ? todos.length : 0;
81
+ const activeTodo = hasTodos ? todos.find((t: any) => t.status === 'in_progress') : null;
82
+
83
+ // Git
84
+ const branch = gitStatus?.branch ?? '';
85
+ const modified = gitStatus?.modified ?? 0;
86
+ const untracked = gitStatus?.untracked ?? 0;
87
+ const ahead = gitStatus?.ahead ?? 0;
88
+ const behind = gitStatus?.behind ?? 0;
89
+
90
+ // Context
91
+ const contextPercent = context?.percent ?? 0;
92
+
93
+ return (
94
+ <div className="progress-panel" data-testid="progress-panel">
95
+ {/* Story Header (AC3) */}
96
+ <div className="progress-section" data-testid="progress-story-header">
97
+ <div className="story-header-row">
98
+ <span className="story-id">{storyId}</span>
99
+ <span className="story-points">{storyPoints}pt</span>
100
+ <span className="story-status">{displayStatus}</span>
101
+ </div>
102
+ <div className="story-title">{storyTitle}</div>
103
+ <div className="story-meta">
104
+ <span className="story-jira">{jiraKey}</span>
105
+ <span className="story-epic">{epic}</span>
106
+ </div>
107
+ </div>
108
+
109
+ {/* Workflow Row (AC4) */}
110
+ {hasWorkflow && (
111
+ <div className="progress-section" data-testid="progress-workflow-row">
112
+ {workflowType && (
113
+ <span className="workflow-badge">
114
+ {workflowType.toUpperCase() === 'TDD' || workflowType.toUpperCase() === 'BDD'
115
+ ? workflowType.toUpperCase()
116
+ : workflowType.charAt(0).toUpperCase() + workflowType.slice(1)}
117
+ </span>
118
+ )}
119
+ {phases && phases.length > 0 && (
120
+ <div className="phase-progress">
121
+ {phases.map((phase: any) => (
122
+ <div key={phase.name} className={`phase-step ${phase.status}`}>
123
+ <span className="phase-label">{phase.label}</span>
124
+ </div>
125
+ ))}
126
+ </div>
127
+ )}
128
+ </div>
129
+ )}
130
+
131
+ {/* AC Row (AC5) */}
132
+ {hasCriteria && (
133
+ <div className="progress-section" data-testid="progress-ac-row">
134
+ <span className="row-label">AC</span>
135
+ <span className="progress-count">{completedAC}/{totalAC}</span>
136
+ <div
137
+ className="progress-bar-container"
138
+ role="progressbar"
139
+ aria-valuenow={completedAC}
140
+ aria-valuemin={0}
141
+ aria-valuemax={totalAC}
142
+ >
143
+ <div className="progress-bar" style={{ width: `${acPercent}%` }} />
144
+ </div>
145
+ </div>
146
+ )}
147
+
148
+ {/* Todo Row (AC6) */}
149
+ {hasTodos && (
150
+ <div className="progress-section" data-testid="progress-todo-row">
151
+ <span className="row-label">Tasks</span>
152
+ <span className="progress-count">{completedTodos}/{totalTodos}</span>
153
+ <div className="progress-bar-container">
154
+ <div
155
+ className="progress-bar"
156
+ style={{ width: `${totalTodos > 0 ? Math.round((completedTodos / totalTodos) * 100) : 0}%` }}
157
+ />
158
+ </div>
159
+ {activeTodo && (
160
+ <span className="active-task">{activeTodo.activeForm || activeTodo.content}</span>
161
+ )}
162
+ </div>
163
+ )}
164
+
165
+ {/* Git Row (AC7) */}
166
+ <div className="progress-section" data-testid="progress-git-row">
167
+ <span className="git-branch">{branch}</span>
168
+ <span className="git-stats">{modified}M {untracked}U ↑{ahead} ↓{behind}</span>
169
+ </div>
170
+
171
+ {/* Context Row (AC8) */}
172
+ <div className="progress-section" data-testid="progress-context-row">
173
+ <span className="row-label">Context</span>
174
+ <span className="context-percent">{contextPercent}%</span>
175
+ <div
176
+ className="progress-bar-container"
177
+ role="progressbar"
178
+ aria-valuenow={contextPercent}
179
+ aria-valuemin={0}
180
+ aria-valuemax={100}
181
+ >
182
+ <div className="progress-bar" style={{ width: `${contextPercent}%` }} />
183
+ </div>
184
+ </div>
185
+ </div>
186
+ );
187
+ }
188
+
189
+ export default ProgressPanel;