@pennyfarthing/core 11.3.8 → 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.
- package/README.md +1 -1
- package/package.json +4 -1
- package/packages/core/dist/public/css/react.css +1 -1
- package/packages/core/dist/public/js/react/react.js +24 -24
- package/packages/core/src/public/App.tsx +356 -0
- package/packages/core/src/public/components/AgentLoadDialog.tsx +202 -0
- package/packages/core/src/public/components/AgentPopup.tsx +308 -0
- package/packages/core/src/public/components/ApprovalModal/ApprovalModal.css +35 -0
- package/packages/core/src/public/components/ApprovalModal/index.tsx +632 -0
- package/packages/core/src/public/components/BikeRackIndex.tsx +53 -0
- package/packages/core/src/public/components/BikeRackWorkspace.tsx +217 -0
- package/packages/core/src/public/components/CommandPalette.tsx +554 -0
- package/packages/core/src/public/components/ConfirmDialog.tsx +168 -0
- package/packages/core/src/public/components/ContextIndicator/ContextIndicator.css +85 -0
- package/packages/core/src/public/components/ContextIndicator/index.tsx +330 -0
- package/packages/core/src/public/components/ContextSparkline.tsx +56 -0
- package/packages/core/src/public/components/ControlBar.tsx +636 -0
- package/packages/core/src/public/components/DeadCodeDialog.tsx +169 -0
- package/packages/core/src/public/components/DiffViewer.tsx +585 -0
- package/packages/core/src/public/components/DockviewWorkspace.tsx +749 -0
- package/packages/core/src/public/components/Editor.tsx +630 -0
- package/packages/core/src/public/components/ErrorBoundary.tsx +67 -0
- package/packages/core/src/public/components/FileTree.tsx +379 -0
- package/packages/core/src/public/components/FontPicker/FontPicker.css +276 -0
- package/packages/core/src/public/components/FontPicker/index.tsx +430 -0
- package/packages/core/src/public/components/FullFileTree.tsx +237 -0
- package/packages/core/src/public/components/HealthGauge.tsx +181 -0
- package/packages/core/src/public/components/Message.tsx +225 -0
- package/packages/core/src/public/components/MessageList.tsx +98 -0
- package/packages/core/src/public/components/MessageView.tsx +400 -0
- package/packages/core/src/public/components/ModeSwitch/ModeSwitch.css +165 -0
- package/packages/core/src/public/components/ModeSwitch/index.tsx +372 -0
- package/packages/core/src/public/components/PersonaHeader.tsx +242 -0
- package/packages/core/src/public/components/ProjectInfoBar.tsx +45 -0
- package/packages/core/src/public/components/QuickActions.tsx +267 -0
- package/packages/core/src/public/components/SpanTimeline.tsx +352 -0
- package/packages/core/src/public/components/StandalonePanel.tsx +82 -0
- package/packages/core/src/public/components/StatsStrip.tsx +162 -0
- package/packages/core/src/public/components/StreamingContent.tsx +77 -0
- package/packages/core/src/public/components/SubagentSpan.tsx +180 -0
- package/packages/core/src/public/components/TandemPortrait.tsx +72 -0
- package/packages/core/src/public/components/ThemePalette/ThemePalette.css +179 -0
- package/packages/core/src/public/components/ThemePalette/index.tsx +326 -0
- package/packages/core/src/public/components/ToolCallBlock.tsx +252 -0
- package/packages/core/src/public/components/ToolStack.tsx +209 -0
- package/packages/core/src/public/components/ToolStatus.tsx +57 -0
- package/packages/core/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
- package/packages/core/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
- package/packages/core/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
- package/packages/core/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
- package/packages/core/src/public/components/dialogs/ToolDialog.tsx +43 -0
- package/packages/core/src/public/components/panel-registry.ts +13 -0
- package/packages/core/src/public/components/panels/ACPanel.tsx +93 -0
- package/packages/core/src/public/components/panels/AcceptanceCriteriaPanel.tsx +104 -0
- package/packages/core/src/public/components/panels/AuditLogPanel.tsx +489 -0
- package/packages/core/src/public/components/panels/BackgroundPanel.tsx +115 -0
- package/packages/core/src/public/components/panels/BikeLanePanel.tsx +214 -0
- package/packages/core/src/public/components/panels/DebugPanel.tsx +344 -0
- package/packages/core/src/public/components/panels/DiffView.tsx +109 -0
- package/packages/core/src/public/components/panels/DiffsPanel.tsx +56 -0
- package/packages/core/src/public/components/panels/GitPanel.tsx +260 -0
- package/packages/core/src/public/components/panels/HotspotsPanel.tsx +365 -0
- package/packages/core/src/public/components/panels/MessageFeed.tsx +39 -0
- package/packages/core/src/public/components/panels/MessagePanel.tsx +497 -0
- package/packages/core/src/public/components/panels/ProgressPanel.tsx +189 -0
- package/packages/core/src/public/components/panels/SettingsPanel.tsx +361 -0
- package/packages/core/src/public/components/panels/SprintPanel.tsx +723 -0
- package/packages/core/src/public/components/panels/TandemPanel.tsx +104 -0
- package/packages/core/src/public/components/panels/TaskTracker.tsx +48 -0
- package/packages/core/src/public/components/panels/TeamPanel.tsx +64 -0
- package/packages/core/src/public/components/panels/TeamRoster.tsx +67 -0
- package/packages/core/src/public/components/panels/TodoPanel.tsx +142 -0
- package/packages/core/src/public/components/panels/WorkflowPanel.tsx +224 -0
- package/packages/core/src/public/components/panels/index.ts +24 -0
- package/packages/core/src/public/components/ui/alert-dialog.tsx +139 -0
- package/packages/core/src/public/components/ui/badge.tsx +36 -0
- package/packages/core/src/public/components/ui/button.tsx +57 -0
- package/packages/core/src/public/components/ui/checkbox.tsx +28 -0
- package/packages/core/src/public/components/ui/collapsible.tsx +9 -0
- package/packages/core/src/public/components/ui/command.tsx +151 -0
- package/packages/core/src/public/components/ui/dialog.tsx +120 -0
- package/packages/core/src/public/components/ui/popover.tsx +31 -0
- package/packages/core/src/public/components/ui/progress.tsx +28 -0
- package/packages/core/src/public/components/ui/scroll-area.tsx +46 -0
- package/packages/core/src/public/components/ui/select.tsx +157 -0
- package/packages/core/src/public/components/ui/separator.tsx +29 -0
- package/packages/core/src/public/components/ui/skeleton.tsx +15 -0
- package/packages/core/src/public/components/ui/switch.tsx +27 -0
- package/packages/core/src/public/components/ui/toggle-group.tsx +59 -0
- package/packages/core/src/public/components/ui/toggle.tsx +43 -0
- package/packages/core/src/public/components/ui/tooltip.tsx +30 -0
- package/packages/core/src/public/contexts/ClaudeContext.tsx +311 -0
- package/packages/core/src/public/contexts/MessageQueueContext.tsx +143 -0
- package/packages/core/src/public/css/theme-browser.css +550 -0
- package/packages/core/src/public/css/theme-system.css +630 -0
- package/packages/core/src/public/hooks/index.ts +49 -0
- package/packages/core/src/public/hooks/useAgentLoad.ts +105 -0
- package/packages/core/src/public/hooks/useBackgroundTasks.ts +131 -0
- package/packages/core/src/public/hooks/useClaude.ts +234 -0
- package/packages/core/src/public/hooks/useCodeMarkers.ts +101 -0
- package/packages/core/src/public/hooks/useColorScheme.ts +42 -0
- package/packages/core/src/public/hooks/useCommandHistory.ts +99 -0
- package/packages/core/src/public/hooks/useComplexity.ts +80 -0
- package/packages/core/src/public/hooks/useDeadCode.ts +99 -0
- package/packages/core/src/public/hooks/useDependencies.ts +82 -0
- package/packages/core/src/public/hooks/useDiffs.ts +143 -0
- package/packages/core/src/public/hooks/useFileBrowser.ts +73 -0
- package/packages/core/src/public/hooks/useFocusPanel.ts +137 -0
- package/packages/core/src/public/hooks/useGitStatus.ts +233 -0
- package/packages/core/src/public/hooks/useHealthScore.ts +71 -0
- package/packages/core/src/public/hooks/useHotspots.ts +123 -0
- package/packages/core/src/public/hooks/useLayoutPersistence.ts +141 -0
- package/packages/core/src/public/hooks/useMarkdownParser.ts +36 -0
- package/packages/core/src/public/hooks/useMarkerActions.ts +234 -0
- package/packages/core/src/public/hooks/useMessageQueue.ts +380 -0
- package/packages/core/src/public/hooks/useMessageStream.ts +131 -0
- package/packages/core/src/public/hooks/usePersona.ts +112 -0
- package/packages/core/src/public/hooks/usePlanModeExit.ts +105 -0
- package/packages/core/src/public/hooks/useResponsiveLayout.ts +173 -0
- package/packages/core/src/public/hooks/useSprint.ts +157 -0
- package/packages/core/src/public/hooks/useStatsStrip.ts +204 -0
- package/packages/core/src/public/hooks/useStory.ts +135 -0
- package/packages/core/src/public/hooks/useSubagentHelper.ts +64 -0
- package/packages/core/src/public/hooks/useSyntaxHighlighter.ts +52 -0
- package/packages/core/src/public/hooks/useTabCompletion.ts +124 -0
- package/packages/core/src/public/hooks/useTandemObservations.ts +165 -0
- package/packages/core/src/public/hooks/useTeamMembers.ts +273 -0
- package/packages/core/src/public/hooks/useTodos.ts +93 -0
- package/packages/core/src/public/hooks/useUserAvatar.ts +54 -0
- package/packages/core/src/public/images/cyclist-dark.png +0 -0
- package/packages/core/src/public/images/cyclist-light.png +0 -0
- package/packages/core/src/public/index.html +14 -0
- package/packages/core/src/public/index.tsx +10 -0
- package/packages/core/src/public/lib/utils.ts +6 -0
- package/packages/core/src/public/styles/dockview-theme.css +376 -0
- package/packages/core/src/public/styles/tailwind.css +4454 -0
- package/packages/core/src/public/types/message.ts +51 -0
- package/packages/core/src/public/utils/avatar-service.ts +73 -0
- package/packages/core/src/public/utils/color-presets.ts +940 -0
- package/packages/core/src/public/utils/font-presets.ts +362 -0
- package/packages/core/src/public/utils/formatDuration.ts +14 -0
- package/packages/core/src/public/utils/markdown.ts +249 -0
- package/packages/core/src/public/utils/messageFilters.ts +128 -0
- package/packages/core/src/public/utils/slash-commands.ts +341 -0
- package/packages/core/src/public/utils/subagent-display.ts +146 -0
- package/packages/core/src/public/utils/syntax.ts +219 -0
- package/packages/core/src/public/utils/toolIntentSummarizer.ts +199 -0
- package/packages/core/src/public/utils/toolStackGrouper.ts +106 -0
- package/packages/core/src/public/utils/toolTypeColors.ts +45 -0
- package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/cli.py +0 -1
- package/pennyfarthing-dist/pf/bc/focus.py +0 -1
- package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/base_panel.py +0 -1
- package/pennyfarthing-dist/pf/bikerack/events.py +1 -7
- package/pennyfarthing-dist/pf/bikerack/git_panel.py +273 -10
- package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +21 -0
- package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +58 -1
- package/pennyfarthing-dist/pf/bikerack/tui.py +5 -20
- package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/__pycache__/parser.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/parser.py +15 -9
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/pr_config.py +27 -2
- package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/settings.py +44 -8
- package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/story_finish.py +14 -2
- package/pennyfarthing-dist/pf/sprint/validator.py +7 -7
- package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/test_sprint_validator.py +44 -0
- package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
- package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
- package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
- package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +15 -2
- package/packages/core/dist/workflow/__test_context_watch__/.session/.tandem-turn-counter +0 -1
- package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-session.md +0 -3
- package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_file_watch__/.session/95-4-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_file_watch__/workdir/trigger.ts +0 -1
- package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-toolcalls.jsonl +0 -1
- package/pennyfarthing-dist/pf/bikerack/changed_panel.py +0 -201
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TandemPanel - Tandem consultation dialogue viewer
|
|
3
|
+
*
|
|
4
|
+
* Story 86-11: Cyclist: Tandem dialogue panel
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Display tandem observation exchanges with agent portraits
|
|
8
|
+
* - Real-time updates via WebSocket
|
|
9
|
+
* - Outcome badges (applied/deferred/rejected)
|
|
10
|
+
* - Metrics summary (exchange count, token overhead, confidence)
|
|
11
|
+
* - Empty state when no tandem activity
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React, { useState } from 'react';
|
|
15
|
+
import { useTandemObservations } from '../../hooks/useTandemObservations';
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// TandemPanel Component
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
export function TandemPanel(): React.ReactElement {
|
|
22
|
+
const { header, observations, metrics, isLoading, error } = useTandemObservations();
|
|
23
|
+
const [portraitError, setPortraitError] = useState(false);
|
|
24
|
+
|
|
25
|
+
const showEmptyState = !isLoading && !error && !header && observations.length === 0;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="tandem-panel">
|
|
29
|
+
{/* Error state */}
|
|
30
|
+
{error && (
|
|
31
|
+
<div data-testid="tandem-error" className="tandem-error">
|
|
32
|
+
Connection failed — tandem data unavailable
|
|
33
|
+
</div>
|
|
34
|
+
)}
|
|
35
|
+
|
|
36
|
+
{/* Observer header with portrait and role */}
|
|
37
|
+
{header && (
|
|
38
|
+
<div className="tandem-header">
|
|
39
|
+
{!portraitError ? (
|
|
40
|
+
<img
|
|
41
|
+
data-testid="observer-portrait"
|
|
42
|
+
src={`/portraits/${header.theme}/medium/${header.slug}.png`}
|
|
43
|
+
alt={header.character}
|
|
44
|
+
className="observer-portrait"
|
|
45
|
+
onError={() => setPortraitError(true)}
|
|
46
|
+
/>
|
|
47
|
+
) : (
|
|
48
|
+
<span data-testid="observer-portrait" className="observer-portrait-fallback" aria-hidden="true">🤖</span>
|
|
49
|
+
)}
|
|
50
|
+
<div className="tandem-header-info">
|
|
51
|
+
<span className="observer-name">{header.character}</span>
|
|
52
|
+
<span data-testid="observer-role-badge" className="observer-role-badge">
|
|
53
|
+
{header.observer}
|
|
54
|
+
</span>
|
|
55
|
+
<span data-testid="observer-phase" className="observer-phase">{header.phase}</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
|
|
60
|
+
{/* Empty state */}
|
|
61
|
+
{showEmptyState && (
|
|
62
|
+
<div data-testid="tandem-empty-state" className="tandem-empty-state">
|
|
63
|
+
No tandem activity
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{/* Observation cards */}
|
|
68
|
+
{observations.map((obs, i) => (
|
|
69
|
+
<div key={obs.timestamp + i} data-testid="tandem-observation" className="tandem-observation">
|
|
70
|
+
<div className="observation-time">{obs.time}</div>
|
|
71
|
+
<div className="observation-trigger">
|
|
72
|
+
<span className="trigger-scope">{obs.trigger.scope}</span>
|
|
73
|
+
<span className="trigger-detail">{obs.trigger.detail}</span>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="observation-content">{obs.content}</div>
|
|
76
|
+
{obs.outcome && (
|
|
77
|
+
<span
|
|
78
|
+
data-testid="outcome-badge"
|
|
79
|
+
data-outcome={obs.outcome}
|
|
80
|
+
className={`outcome-badge outcome-${obs.outcome}`}
|
|
81
|
+
>
|
|
82
|
+
{obs.outcome}
|
|
83
|
+
</span>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
))}
|
|
87
|
+
|
|
88
|
+
{/* Metrics summary */}
|
|
89
|
+
{metrics && (
|
|
90
|
+
<div data-testid="tandem-metrics" className="tandem-metrics">
|
|
91
|
+
<div className="metric">{metrics.exchangeCount} exchanges</div>
|
|
92
|
+
<div className="metric">{metrics.tokenOverhead}% overhead</div>
|
|
93
|
+
<div className="confidence-distribution">
|
|
94
|
+
<span>High: {metrics.confidenceDistribution.high}</span>
|
|
95
|
+
<span>Medium: {metrics.confidenceDistribution.medium}</span>
|
|
96
|
+
<span>Low: {metrics.confidenceDistribution.low}</span>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default TandemPanel;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TaskTracker - Shared task list with dependencies
|
|
3
|
+
*
|
|
4
|
+
* Story 86-12: Cyclist: Native team panel
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import type { TaskListItem } from '../../hooks/useTeamMembers';
|
|
9
|
+
|
|
10
|
+
export interface TaskTrackerProps {
|
|
11
|
+
tasks: TaskListItem[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function TaskTracker({ tasks }: TaskTrackerProps): React.ReactElement {
|
|
15
|
+
if (tasks.length === 0) {
|
|
16
|
+
return (
|
|
17
|
+
<div data-testid="task-tracker">
|
|
18
|
+
<div data-testid="tasks-empty">No tasks</div>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const completed = tasks.filter(t => t.status === 'completed').length;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div data-testid="task-tracker">
|
|
27
|
+
<div data-testid="task-progress-summary">
|
|
28
|
+
{completed} of {tasks.length} completed
|
|
29
|
+
</div>
|
|
30
|
+
{tasks.map(task => (
|
|
31
|
+
<div key={task.id} data-testid="task-item">
|
|
32
|
+
<span>{task.title}</span>
|
|
33
|
+
<span data-testid="task-owner">{task.owner ?? 'unassigned'}</span>
|
|
34
|
+
<span data-testid="task-status" data-status={task.status}>
|
|
35
|
+
{task.status}
|
|
36
|
+
</span>
|
|
37
|
+
{task.blockedBy && task.blockedBy.length > 0 && (
|
|
38
|
+
<span data-testid="task-blocked-indicator">
|
|
39
|
+
Blocked by: {task.blockedBy.join(', ')}
|
|
40
|
+
</span>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default TaskTracker;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeamPanel - Native team visualization panel
|
|
3
|
+
*
|
|
4
|
+
* Story 86-12: Cyclist: Native team panel
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Team member roster with persona portraits and status
|
|
8
|
+
* - Task list with completion progress and dependencies
|
|
9
|
+
* - Message feed between agents
|
|
10
|
+
* - Click agent to view output
|
|
11
|
+
* - Hidden when native teams not active
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React, { useState } from 'react';
|
|
15
|
+
import { useTeamMembers } from '../../hooks/useTeamMembers';
|
|
16
|
+
import { TeamRoster } from './TeamRoster';
|
|
17
|
+
import { TaskTracker } from './TaskTracker';
|
|
18
|
+
import { MessageFeed } from './MessageFeed';
|
|
19
|
+
import type { TeamMember } from '../../hooks/useTeamMembers';
|
|
20
|
+
|
|
21
|
+
export function TeamPanel(): React.ReactElement {
|
|
22
|
+
const { isActive, teamName, members, tasks, messages, isLoading, error } = useTeamMembers();
|
|
23
|
+
const [selectedMember, setSelectedMember] = useState<TeamMember | null>(null);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div data-testid="team-panel" className="team-panel">
|
|
27
|
+
{isLoading && (
|
|
28
|
+
<div data-testid="team-loading">Connecting to team...</div>
|
|
29
|
+
)}
|
|
30
|
+
|
|
31
|
+
{error && (
|
|
32
|
+
<div data-testid="team-error">Connection failed — team data unavailable</div>
|
|
33
|
+
)}
|
|
34
|
+
|
|
35
|
+
{!isActive && (
|
|
36
|
+
<div data-testid="team-empty-state">No active team</div>
|
|
37
|
+
)}
|
|
38
|
+
|
|
39
|
+
{teamName && (
|
|
40
|
+
<div data-testid="team-name">{teamName}</div>
|
|
41
|
+
)}
|
|
42
|
+
|
|
43
|
+
<TeamRoster members={members} onMemberClick={setSelectedMember} />
|
|
44
|
+
<TaskTracker tasks={tasks} />
|
|
45
|
+
<MessageFeed messages={messages} />
|
|
46
|
+
|
|
47
|
+
{selectedMember && (
|
|
48
|
+
<div data-testid="agent-output-view">
|
|
49
|
+
<div>{selectedMember.name}</div>
|
|
50
|
+
<div>Role: {selectedMember.agentType}</div>
|
|
51
|
+
<div>Status: {selectedMember.status}</div>
|
|
52
|
+
<button
|
|
53
|
+
data-testid="agent-output-close"
|
|
54
|
+
onClick={() => setSelectedMember(null)}
|
|
55
|
+
>
|
|
56
|
+
Close
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default TeamPanel;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeamRoster - Agent member list with portraits and status
|
|
3
|
+
*
|
|
4
|
+
* Story 86-12: Cyclist: Native team panel
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState } from 'react';
|
|
8
|
+
import type { TeamMember } from '../../hooks/useTeamMembers';
|
|
9
|
+
|
|
10
|
+
export interface TeamRosterProps {
|
|
11
|
+
members: TeamMember[];
|
|
12
|
+
onMemberClick?: (member: TeamMember) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function MemberPortrait({ member }: { member: TeamMember }) {
|
|
16
|
+
const [imgError, setImgError] = useState(false);
|
|
17
|
+
const src = member.portrait
|
|
18
|
+
? `/portraits/${member.portrait.theme}/medium/${member.portrait.slug}.png`
|
|
19
|
+
: '';
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div data-testid="member-portrait">
|
|
23
|
+
{!imgError && src ? (
|
|
24
|
+
<img
|
|
25
|
+
src={src}
|
|
26
|
+
alt={member.name}
|
|
27
|
+
onError={() => setImgError(true)}
|
|
28
|
+
/>
|
|
29
|
+
) : (
|
|
30
|
+
<span>🤖</span>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function TeamRoster({ members, onMemberClick }: TeamRosterProps): React.ReactElement {
|
|
37
|
+
if (members.length === 0) {
|
|
38
|
+
return (
|
|
39
|
+
<div data-testid="team-roster">
|
|
40
|
+
<div data-testid="roster-empty">No team members</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div data-testid="team-roster">
|
|
47
|
+
{members.map(member => (
|
|
48
|
+
<div
|
|
49
|
+
key={member.name}
|
|
50
|
+
data-testid="team-member"
|
|
51
|
+
role="button"
|
|
52
|
+
onClick={() => onMemberClick?.(member)}
|
|
53
|
+
>
|
|
54
|
+
<MemberPortrait member={member} />
|
|
55
|
+
<span>{member.name}</span>
|
|
56
|
+
<span data-testid="member-role" title={member.agentType} className={`role-badge role-${member.agentType}`} />
|
|
57
|
+
<span data-testid="member-status" data-status={member.status}>
|
|
58
|
+
{member.status}
|
|
59
|
+
</span>
|
|
60
|
+
{member.currentTask && <span>{member.currentTask}</span>}
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default TeamRoster;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TodoPanel - Task list with progress panel
|
|
3
|
+
*
|
|
4
|
+
* Extracted from ProgressPanel as part of MSSCI-14188.
|
|
5
|
+
* Shows todos grouped by status with progress bar.
|
|
6
|
+
*
|
|
7
|
+
* Story: MSSCI-14188 - Split Progress panel into Workflow, AC, and Todo panels
|
|
8
|
+
* Epic: epic-76 (Dockview Panel Migration)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
13
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
14
|
+
import { useTodos, TodoItem } from '../../hooks/useTodos';
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Todo Item Component
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
function TodoItemView({ todo }: { todo: TodoItem }): React.ReactElement {
|
|
21
|
+
const statusIcon = {
|
|
22
|
+
pending: '\u25CB',
|
|
23
|
+
in_progress: '\u25CF',
|
|
24
|
+
completed: '\u2713',
|
|
25
|
+
}[todo.status];
|
|
26
|
+
|
|
27
|
+
const statusClass = `todo-item todo-${todo.status}`;
|
|
28
|
+
|
|
29
|
+
// Use activeForm for in_progress (more descriptive), content for others
|
|
30
|
+
const displayText = todo.status === 'in_progress' && todo.activeForm
|
|
31
|
+
? todo.activeForm
|
|
32
|
+
: todo.content;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className={statusClass} data-testid={`todo-${todo.id}`}>
|
|
36
|
+
<span className="todo-status">{statusIcon}</span>
|
|
37
|
+
<span className="todo-subject">{displayText}</span>
|
|
38
|
+
{todo.blockedBy && todo.blockedBy.length > 0 && (
|
|
39
|
+
<TooltipProvider delayDuration={300}>
|
|
40
|
+
<Tooltip>
|
|
41
|
+
<TooltipTrigger asChild>
|
|
42
|
+
<span className="todo-blocked">
|
|
43
|
+
(blocked)
|
|
44
|
+
</span>
|
|
45
|
+
</TooltipTrigger>
|
|
46
|
+
<TooltipContent>{`Blocked by: ${todo.blockedBy.join(', ')}`}</TooltipContent>
|
|
47
|
+
</Tooltip>
|
|
48
|
+
</TooltipProvider>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// TodoPanel Component
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
export function TodoPanel(): React.ReactElement {
|
|
59
|
+
const { todos, isLoading, error } = useTodos();
|
|
60
|
+
|
|
61
|
+
if (isLoading) {
|
|
62
|
+
return (
|
|
63
|
+
<div className="todo-panel loading" data-testid="todo-panel">
|
|
64
|
+
<div className="space-y-2 p-2">
|
|
65
|
+
<Skeleton className="h-3 w-full" />
|
|
66
|
+
<Skeleton className="h-4 w-5/6" />
|
|
67
|
+
<Skeleton className="h-4 w-3/4" />
|
|
68
|
+
<Skeleton className="h-4 w-4/5" />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (error) {
|
|
75
|
+
return (
|
|
76
|
+
<div className="todo-panel error" data-testid="todo-panel">
|
|
77
|
+
<div className="error-message">{error.message}</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (todos.length === 0) {
|
|
83
|
+
return (
|
|
84
|
+
<div className="todo-panel" data-testid="todo-panel">
|
|
85
|
+
<div className="placeholder">No active tasks</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Group by status
|
|
91
|
+
const inProgress = todos.filter(t => t.status === 'in_progress');
|
|
92
|
+
const pending = todos.filter(t => t.status === 'pending');
|
|
93
|
+
const completed = todos.filter(t => t.status === 'completed');
|
|
94
|
+
|
|
95
|
+
// Calculate progress
|
|
96
|
+
const totalCount = todos.length;
|
|
97
|
+
const completedCount = completed.length;
|
|
98
|
+
const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className="todo-panel" data-testid="todo-panel">
|
|
102
|
+
<div className="todo-content">
|
|
103
|
+
<div className="progress-bar-container">
|
|
104
|
+
<div
|
|
105
|
+
className="progress-bar"
|
|
106
|
+
style={{ width: `${progressPercent}%` }}
|
|
107
|
+
/>
|
|
108
|
+
<span className="progress-text">{completedCount}/{totalCount}</span>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{inProgress.length > 0 && (
|
|
112
|
+
<div className="todo-section">
|
|
113
|
+
<h4>In Progress</h4>
|
|
114
|
+
{inProgress.map(todo => (
|
|
115
|
+
<TodoItemView key={todo.id} todo={todo} />
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{pending.length > 0 && (
|
|
121
|
+
<div className="todo-section">
|
|
122
|
+
<h4>Pending</h4>
|
|
123
|
+
{pending.map(todo => (
|
|
124
|
+
<TodoItemView key={todo.id} todo={todo} />
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{completed.length > 0 && (
|
|
130
|
+
<div className="todo-section todo-completed">
|
|
131
|
+
<h4>Completed ({completed.length})</h4>
|
|
132
|
+
{completed.map(todo => (
|
|
133
|
+
<TodoItemView key={todo.id} todo={todo} />
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default TodoPanel;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkflowPanel - BikeLane phase visualization panel
|
|
3
|
+
*
|
|
4
|
+
* Extracted from ProgressPanel as part of MSSCI-14188.
|
|
5
|
+
* Shows workflow type badge (TDD/BDD/Trivial) and phase progress.
|
|
6
|
+
* MSSCI-14300: Added stepped workflow "Step N of M" display.
|
|
7
|
+
* MSSCI-14301: Added available workflows discovery list.
|
|
8
|
+
*
|
|
9
|
+
* Story: MSSCI-14188 - Split Progress panel into Workflow, AC, and Todo panels
|
|
10
|
+
* Epic: epic-76 (Dockview Panel Migration)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import React, { useCallback } from 'react';
|
|
14
|
+
import { Badge } from '@/components/ui/badge';
|
|
15
|
+
import { Button } from '@/components/ui/button';
|
|
16
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
17
|
+
import { useClaudeContext } from '../../contexts/ClaudeContext';
|
|
18
|
+
import { useStory } from '../../hooks/useStory';
|
|
19
|
+
import type { WorkflowPhase, AvailableWorkflow } from '../../../story-parser.js';
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Helper Functions
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
function getPhaseIcon(status: 'done' | 'current' | 'pending'): string {
|
|
26
|
+
switch (status) {
|
|
27
|
+
case 'done':
|
|
28
|
+
return '\u2713';
|
|
29
|
+
case 'current':
|
|
30
|
+
return '\u25CF';
|
|
31
|
+
case 'pending':
|
|
32
|
+
default:
|
|
33
|
+
return '\u25CB';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatWorkflowType(type: string | null): string {
|
|
38
|
+
if (!type) return '\u2014';
|
|
39
|
+
|
|
40
|
+
const upperTypes = ['tdd', 'bdd'];
|
|
41
|
+
if (upperTypes.includes(type.toLowerCase())) {
|
|
42
|
+
return type.toUpperCase();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Phase Step Component (for phased workflows)
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
function PhaseStep({ phase, isLast }: { phase: WorkflowPhase; isLast: boolean }): React.ReactElement {
|
|
53
|
+
const icon = getPhaseIcon(phase.status);
|
|
54
|
+
const statusClass = `phase-step ${phase.status}`;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<div className={statusClass}>
|
|
59
|
+
<span className="phase-icon">{icon}</span>
|
|
60
|
+
<span className="phase-label">{phase.label}</span>
|
|
61
|
+
</div>
|
|
62
|
+
{!isLast && <span className="phase-arrow">{'\u2192'}</span>}
|
|
63
|
+
</>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// =============================================================================
|
|
68
|
+
// Stepped Progress Component (for stepped workflows)
|
|
69
|
+
// =============================================================================
|
|
70
|
+
|
|
71
|
+
function SteppedProgress({ phases }: { phases: WorkflowPhase[] }): React.ReactElement {
|
|
72
|
+
const total = phases.length;
|
|
73
|
+
const currentIndex = phases.findIndex(p => p.status === 'current');
|
|
74
|
+
const doneCount = phases.filter(p => p.status === 'done').length;
|
|
75
|
+
|
|
76
|
+
// If all done, current step = total; otherwise use 1-based index of current
|
|
77
|
+
const currentStep = currentIndex >= 0 ? currentIndex + 1 : (doneCount === total ? total : 1);
|
|
78
|
+
const currentPhase = currentIndex >= 0 ? phases[currentIndex] : null;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="stepped-progress">
|
|
82
|
+
<span className="stepped-counter">Step {currentStep} of {total}</span>
|
|
83
|
+
{currentPhase && (
|
|
84
|
+
<span className="stepped-current-label">{currentPhase.label}</span>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// =============================================================================
|
|
91
|
+
// Available Workflows List (MSSCI-14301)
|
|
92
|
+
// =============================================================================
|
|
93
|
+
|
|
94
|
+
function AvailableWorkflowsList({ workflows, onStart }: {
|
|
95
|
+
workflows: AvailableWorkflow[];
|
|
96
|
+
onStart?: (workflow: AvailableWorkflow) => void;
|
|
97
|
+
}): React.ReactElement {
|
|
98
|
+
return (
|
|
99
|
+
<div className="available-workflows">
|
|
100
|
+
<div className="available-workflows-header">
|
|
101
|
+
<span className="available-workflows-title">Available Workflows ({workflows.length})</span>
|
|
102
|
+
</div>
|
|
103
|
+
<div className="available-workflows-list">
|
|
104
|
+
{workflows.map((wf) => (
|
|
105
|
+
<div
|
|
106
|
+
key={wf.name}
|
|
107
|
+
className="workflow-entry"
|
|
108
|
+
data-testid="workflow-entry"
|
|
109
|
+
data-workflow-entry-type={wf.type}
|
|
110
|
+
>
|
|
111
|
+
<div className="workflow-entry-header">
|
|
112
|
+
<span className="workflow-entry-name">{wf.name}</span>
|
|
113
|
+
<Badge variant="outline" className="workflow-entry-type-badge">
|
|
114
|
+
{wf.type}
|
|
115
|
+
</Badge>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="workflow-entry-description">{wf.description}</div>
|
|
118
|
+
{wf.type === 'stepped' && (
|
|
119
|
+
<div className="workflow-entry-hint">/workflow start {wf.name}</div>
|
|
120
|
+
)}
|
|
121
|
+
<div className="workflow-entry-footer">
|
|
122
|
+
<Button
|
|
123
|
+
variant="ghost"
|
|
124
|
+
size="sm"
|
|
125
|
+
className="workflow-start-button"
|
|
126
|
+
data-testid="workflow-start-button"
|
|
127
|
+
onClick={() => onStart?.(wf)}
|
|
128
|
+
>
|
|
129
|
+
Start
|
|
130
|
+
</Button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// WorkflowPanel Component
|
|
141
|
+
// =============================================================================
|
|
142
|
+
|
|
143
|
+
export function WorkflowPanel(): React.ReactElement {
|
|
144
|
+
const { story, isLoading, error, availableWorkflows } = useStory();
|
|
145
|
+
const { send, isConnected } = useClaudeContext();
|
|
146
|
+
|
|
147
|
+
const handleStartWorkflow = useCallback((wf: AvailableWorkflow) => {
|
|
148
|
+
if (!isConnected) return;
|
|
149
|
+
send(`/workflow start ${wf.name}`);
|
|
150
|
+
}, [send, isConnected]);
|
|
151
|
+
|
|
152
|
+
if (isLoading) {
|
|
153
|
+
return (
|
|
154
|
+
<div className="workflow-panel loading" data-testid="workflow-panel">
|
|
155
|
+
<div className="space-y-2 p-2">
|
|
156
|
+
<Skeleton className="h-6 w-16 rounded-full" />
|
|
157
|
+
<div className="flex gap-2 items-center">
|
|
158
|
+
<Skeleton className="h-4 w-12" />
|
|
159
|
+
<Skeleton className="h-4 w-12" />
|
|
160
|
+
<Skeleton className="h-4 w-12" />
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (error) {
|
|
168
|
+
return (
|
|
169
|
+
<div className="workflow-panel error" data-testid="workflow-panel">
|
|
170
|
+
<div className="error-message">{error.message}</div>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const workflowType = story?.workflow ?? null;
|
|
176
|
+
const phases = story?.workflowPhases ?? null;
|
|
177
|
+
const isStepped = story?.workflowType === 'stepped';
|
|
178
|
+
|
|
179
|
+
if (!workflowType && (!phases || phases.length === 0)) {
|
|
180
|
+
// MSSCI-14301: Show available workflows when no active workflow
|
|
181
|
+
if (availableWorkflows && availableWorkflows.length > 0) {
|
|
182
|
+
return (
|
|
183
|
+
<div className="workflow-panel" data-testid="workflow-panel">
|
|
184
|
+
<AvailableWorkflowsList workflows={availableWorkflows} onStart={handleStartWorkflow} />
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return (
|
|
189
|
+
<div className="workflow-panel" data-testid="workflow-panel">
|
|
190
|
+
<div className="placeholder">No active workflow</div>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const formattedType = formatWorkflowType(workflowType);
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div className="workflow-panel" data-testid="workflow-panel">
|
|
199
|
+
<div className="workflow-content">
|
|
200
|
+
<Badge variant="secondary" className="workflow-type-badge" data-workflow-type={workflowType || ''}>
|
|
201
|
+
{formattedType}
|
|
202
|
+
</Badge>
|
|
203
|
+
|
|
204
|
+
{phases && phases.length > 0 && (
|
|
205
|
+
isStepped ? (
|
|
206
|
+
<SteppedProgress phases={phases} />
|
|
207
|
+
) : (
|
|
208
|
+
<div className="phase-progress">
|
|
209
|
+
{phases.map((phase, index) => (
|
|
210
|
+
<PhaseStep
|
|
211
|
+
key={phase.name}
|
|
212
|
+
phase={phase}
|
|
213
|
+
isLast={index === phases.length - 1}
|
|
214
|
+
/>
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
)
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export default WorkflowPanel;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel Components Index
|
|
3
|
+
*
|
|
4
|
+
* Story MSSCI-12717 - React Migration
|
|
5
|
+
* Story MSSCI-14188 - Split Progress panel into Workflow, AC, and Todo panels
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { MessagePanel } from './MessagePanel';
|
|
9
|
+
export { SprintPanel, EnhancedSprintPanel } from './SprintPanel';
|
|
10
|
+
export { GitPanel } from './GitPanel';
|
|
11
|
+
export { WorkflowPanel } from './WorkflowPanel';
|
|
12
|
+
export { ACPanel } from './ACPanel';
|
|
13
|
+
export { TodoPanel } from './TodoPanel';
|
|
14
|
+
export { BackgroundPanel } from './BackgroundPanel';
|
|
15
|
+
export { DiffsPanel } from './DiffsPanel';
|
|
16
|
+
export { DiffView } from './DiffView';
|
|
17
|
+
export { DebugPanel } from './DebugPanel';
|
|
18
|
+
export { SettingsPanel } from './SettingsPanel';
|
|
19
|
+
export { AuditLogPanel } from './AuditLogPanel';
|
|
20
|
+
export { ProgressPanel } from './ProgressPanel';
|
|
21
|
+
export { TandemPanel } from './TandemPanel';
|
|
22
|
+
// Legacy exports - kept for backwards compatibility and tests
|
|
23
|
+
export { AcceptanceCriteriaPanel, ConnectedAcceptanceCriteriaPanel } from './AcceptanceCriteriaPanel';
|
|
24
|
+
export { BikeLanePanel, ConnectedBikeLanePanel } from './BikeLanePanel';
|