@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.
- package/README.md +1 -1
- package/package.json +17 -16
- 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/hooks/cyclist-pretooluse-hook.sh +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/core/agent-session.sh +0 -0
- package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
- package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +0 -0
- package/pennyfarthing-dist/scripts/core/pf.sh +0 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
- package/pennyfarthing-dist/scripts/core/prime.sh +0 -0
- package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +0 -0
- package/pennyfarthing-dist/scripts/git/release.sh +0 -0
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/dispatcher-template.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
- package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/env.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
- package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/run-pf.sh +0 -0
- package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
- package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
- package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/statusline.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +0 -0
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
- package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +0 -0
- package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
- package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
- package/pennyfarthing-dist/skills/pf-story/scripts/create-story.sh +0 -0
- package/pennyfarthing-dist/skills/pf-story/scripts/size-story.sh +0 -0
- package/pennyfarthing-dist/skills/pf-story/scripts/story-template.sh +0 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
- package/pennyfarthing-dist/workflows/release/steps/step-10-publish.md +41 -9
- 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
- package/scripts/README.md +0 -41
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useGitStatus Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for subscribing to git status data.
|
|
5
|
+
* Story MSSCI-12717 - React Migration
|
|
6
|
+
* Story MSSCI-12781 - Fixed to handle multi-repo response format
|
|
7
|
+
* Story MSSCI-12798 - Expose full repo array for stacked display
|
|
8
|
+
* Story MSSCI-12860 - IPC to WebSocket Migration (Phase 1)
|
|
9
|
+
*
|
|
10
|
+
* Uses WebSocket /ws/git for real-time updates (no polling).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useState, useEffect, useRef } from 'react';
|
|
14
|
+
|
|
15
|
+
export interface GitStatusData {
|
|
16
|
+
branch: string;
|
|
17
|
+
ahead?: number;
|
|
18
|
+
behind?: number;
|
|
19
|
+
staged?: number;
|
|
20
|
+
modified?: number;
|
|
21
|
+
untracked?: number;
|
|
22
|
+
isDirty?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Per-repo status data for stacked display */
|
|
26
|
+
export interface RepoStatusData {
|
|
27
|
+
name: string;
|
|
28
|
+
path: string;
|
|
29
|
+
branch: string;
|
|
30
|
+
ahead?: number;
|
|
31
|
+
behind?: number;
|
|
32
|
+
/** Commits origin/develop has that this branch doesn't (needs pull/rebase) */
|
|
33
|
+
developBehind?: number;
|
|
34
|
+
staged: number;
|
|
35
|
+
modified: number;
|
|
36
|
+
untracked: number;
|
|
37
|
+
isDirty: boolean;
|
|
38
|
+
files: DirtyFile[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Dirty file from git status --porcelain */
|
|
42
|
+
export interface DirtyFile {
|
|
43
|
+
status: string; // M, A, D, ?, etc.
|
|
44
|
+
path: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Raw repo git info from server */
|
|
48
|
+
interface RepoGitInfo {
|
|
49
|
+
name: string;
|
|
50
|
+
path: string;
|
|
51
|
+
branch: string;
|
|
52
|
+
clean: boolean;
|
|
53
|
+
ahead: number | null;
|
|
54
|
+
behind: number | null;
|
|
55
|
+
developBehind: number | null;
|
|
56
|
+
dirtyFiles: DirtyFile[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** WebSocket message format from /ws/git */
|
|
60
|
+
interface GitMessage {
|
|
61
|
+
type: 'init' | 'update';
|
|
62
|
+
repos: RepoGitInfo[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Count files by type from dirty files array
|
|
67
|
+
*/
|
|
68
|
+
function countFilesByType(dirtyFiles: DirtyFile[]): { staged: number; modified: number; untracked: number } {
|
|
69
|
+
let staged = 0;
|
|
70
|
+
let modified = 0;
|
|
71
|
+
let untracked = 0;
|
|
72
|
+
|
|
73
|
+
for (const file of dirtyFiles) {
|
|
74
|
+
// Git status codes:
|
|
75
|
+
// First char = staging area, Second char = working tree
|
|
76
|
+
// M = modified, A = added (staged), D = deleted, ? = untracked
|
|
77
|
+
const indexStatus = file.status[0] || ' ';
|
|
78
|
+
const workTreeStatus = file.status[1] || ' ';
|
|
79
|
+
|
|
80
|
+
// Staged files (anything in the index that's not space or ?)
|
|
81
|
+
if (indexStatus !== ' ' && indexStatus !== '?') {
|
|
82
|
+
staged++;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Modified in working tree (not staged)
|
|
86
|
+
if (workTreeStatus === 'M' || workTreeStatus === 'D') {
|
|
87
|
+
modified++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Untracked files
|
|
91
|
+
if (indexStatus === '?' && workTreeStatus === '?') {
|
|
92
|
+
untracked++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { staged, modified, untracked };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Transform raw git response to per-repo status array for stacked display
|
|
101
|
+
*/
|
|
102
|
+
function transformToRepoArray(repos: RepoGitInfo[]): RepoStatusData[] {
|
|
103
|
+
if (!repos?.length) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return repos.map(repo => {
|
|
108
|
+
const counts = countFilesByType(repo.dirtyFiles);
|
|
109
|
+
return {
|
|
110
|
+
name: repo.name,
|
|
111
|
+
path: repo.path,
|
|
112
|
+
branch: repo.branch,
|
|
113
|
+
ahead: repo.ahead ?? undefined,
|
|
114
|
+
behind: repo.behind ?? undefined,
|
|
115
|
+
developBehind: repo.developBehind ?? undefined,
|
|
116
|
+
staged: counts.staged,
|
|
117
|
+
modified: counts.modified,
|
|
118
|
+
untracked: counts.untracked,
|
|
119
|
+
isDirty: !repo.clean,
|
|
120
|
+
files: repo.dirtyFiles,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Transform raw git response to GitStatusData for display (legacy aggregated view)
|
|
127
|
+
* Uses first repo for branch info, aggregates file counts across all repos
|
|
128
|
+
*/
|
|
129
|
+
function transformGitResponse(repos: RepoGitInfo[]): GitStatusData | null {
|
|
130
|
+
if (!repos?.length) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Use first repo as primary (usually the orchestrator or main repo)
|
|
135
|
+
const primaryRepo = repos[0];
|
|
136
|
+
|
|
137
|
+
// Count file types across all repos
|
|
138
|
+
let staged = 0;
|
|
139
|
+
let modified = 0;
|
|
140
|
+
let untracked = 0;
|
|
141
|
+
|
|
142
|
+
for (const repo of repos) {
|
|
143
|
+
const counts = countFilesByType(repo.dirtyFiles);
|
|
144
|
+
staged += counts.staged;
|
|
145
|
+
modified += counts.modified;
|
|
146
|
+
untracked += counts.untracked;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if any repo is dirty
|
|
150
|
+
const isDirty = repos.some(repo => !repo.clean);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
branch: primaryRepo.branch,
|
|
154
|
+
ahead: primaryRepo.ahead ?? undefined,
|
|
155
|
+
behind: primaryRepo.behind ?? undefined,
|
|
156
|
+
staged: staged > 0 ? staged : undefined,
|
|
157
|
+
modified: modified > 0 ? modified : undefined,
|
|
158
|
+
untracked: untracked > 0 ? untracked : undefined,
|
|
159
|
+
isDirty,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
interface UseGitStatusResult {
|
|
164
|
+
gitStatus: GitStatusData | null;
|
|
165
|
+
repos: RepoStatusData[];
|
|
166
|
+
isLoading: boolean;
|
|
167
|
+
error: Error | null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function useGitStatus(): UseGitStatusResult {
|
|
171
|
+
const [gitStatus, setGitStatus] = useState<GitStatusData | null>(null);
|
|
172
|
+
const [repos, setRepos] = useState<RepoStatusData[]>([]);
|
|
173
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
174
|
+
const [error, setError] = useState<Error | null>(null);
|
|
175
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
176
|
+
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
177
|
+
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
180
|
+
const wsUrl = `${protocol}//${window.location.host}/ws/git`;
|
|
181
|
+
|
|
182
|
+
const connect = () => {
|
|
183
|
+
try {
|
|
184
|
+
wsRef.current = new WebSocket(wsUrl);
|
|
185
|
+
|
|
186
|
+
wsRef.current.onopen = () => {
|
|
187
|
+
console.debug('[useGitStatus] WebSocket connected');
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
wsRef.current.onmessage = (event) => {
|
|
191
|
+
try {
|
|
192
|
+
const msg = JSON.parse(event.data) as GitMessage;
|
|
193
|
+
if (msg.type === 'init' || msg.type === 'update') {
|
|
194
|
+
setGitStatus(transformGitResponse(msg.repos));
|
|
195
|
+
setRepos(transformToRepoArray(msg.repos));
|
|
196
|
+
setIsLoading(false);
|
|
197
|
+
setError(null);
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error('[useGitStatus] Failed to parse message:', err);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
wsRef.current.onclose = () => {
|
|
205
|
+
console.debug('[useGitStatus] WebSocket closed, reconnecting...');
|
|
206
|
+
reconnectTimeoutRef.current = setTimeout(connect, 2000);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
wsRef.current.onerror = (err) => {
|
|
210
|
+
console.error('[useGitStatus] WebSocket error:', err);
|
|
211
|
+
setError(new Error('WebSocket connection failed'));
|
|
212
|
+
};
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.error('[useGitStatus] WebSocket init failed:', err);
|
|
215
|
+
setError(err instanceof Error ? err : new Error('Failed to connect'));
|
|
216
|
+
setIsLoading(false);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
connect();
|
|
221
|
+
|
|
222
|
+
return () => {
|
|
223
|
+
if (reconnectTimeoutRef.current) {
|
|
224
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
225
|
+
}
|
|
226
|
+
if (wsRef.current) {
|
|
227
|
+
wsRef.current.close();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}, []);
|
|
231
|
+
|
|
232
|
+
return { gitStatus, repos, isLoading, error };
|
|
233
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface HealthScoreDimension {
|
|
4
|
+
name: string;
|
|
5
|
+
score: number | null;
|
|
6
|
+
weight: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface HealthScoreData {
|
|
10
|
+
success: boolean;
|
|
11
|
+
composite_score: number;
|
|
12
|
+
dimensions: HealthScoreDimension[];
|
|
13
|
+
cached: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UseHealthScoreReturn {
|
|
18
|
+
data: HealthScoreData | null;
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
error: Error | null;
|
|
21
|
+
lastFetchedAt: number | null;
|
|
22
|
+
refresh: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useHealthScore(): UseHealthScoreReturn {
|
|
26
|
+
const [data, setData] = useState<HealthScoreData | null>(null);
|
|
27
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
28
|
+
const [error, setError] = useState<Error | null>(null);
|
|
29
|
+
const [lastFetchedAt, setLastFetchedAt] = useState<number | null>(null);
|
|
30
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
31
|
+
|
|
32
|
+
const refresh = useCallback(() => {
|
|
33
|
+
if (abortRef.current) {
|
|
34
|
+
abortRef.current.abort();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
abortRef.current = controller;
|
|
39
|
+
|
|
40
|
+
setIsLoading(true);
|
|
41
|
+
setError(null);
|
|
42
|
+
|
|
43
|
+
fetch('/api/health-score', { signal: controller.signal })
|
|
44
|
+
.then((res) => {
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
47
|
+
}
|
|
48
|
+
return res.json();
|
|
49
|
+
})
|
|
50
|
+
.then((json: HealthScoreData) => {
|
|
51
|
+
setData(json);
|
|
52
|
+
setLastFetchedAt(Date.now());
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
})
|
|
55
|
+
.catch((err) => {
|
|
56
|
+
if (err.name === 'AbortError') return;
|
|
57
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
58
|
+
setIsLoading(false);
|
|
59
|
+
});
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
// Auto-fetch on mount
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
refresh();
|
|
65
|
+
return () => {
|
|
66
|
+
abortRef.current?.abort();
|
|
67
|
+
};
|
|
68
|
+
}, [refresh]);
|
|
69
|
+
|
|
70
|
+
return { data, isLoading, error, lastFetchedAt, refresh };
|
|
71
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// Types matching Python HotspotResult / MultiRepoHotspotResult
|
|
4
|
+
export interface FileHotspot {
|
|
5
|
+
path: string;
|
|
6
|
+
change_count: number;
|
|
7
|
+
bug_fix_count: number;
|
|
8
|
+
author_count: number;
|
|
9
|
+
lines_added: number;
|
|
10
|
+
lines_deleted: number;
|
|
11
|
+
churn: number;
|
|
12
|
+
last_changed: string;
|
|
13
|
+
hotspot_score: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DirectoryHotspot {
|
|
17
|
+
path: string;
|
|
18
|
+
file_count: number;
|
|
19
|
+
total_changes: number;
|
|
20
|
+
total_bug_fixes: number;
|
|
21
|
+
avg_author_count: number;
|
|
22
|
+
hotspot_score: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface HotspotRepoResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
repo_name: string;
|
|
28
|
+
repo_path: string;
|
|
29
|
+
time_window_days: number;
|
|
30
|
+
commit_count: number;
|
|
31
|
+
file_hotspots: FileHotspot[];
|
|
32
|
+
directory_hotspots: DirectoryHotspot[];
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface HotspotData {
|
|
37
|
+
success: boolean;
|
|
38
|
+
// Single-repo result fields (when --path is used)
|
|
39
|
+
repo_name?: string;
|
|
40
|
+
repo_path?: string;
|
|
41
|
+
time_window_days?: number;
|
|
42
|
+
commit_count?: number;
|
|
43
|
+
file_hotspots?: FileHotspot[];
|
|
44
|
+
directory_hotspots?: DirectoryHotspot[];
|
|
45
|
+
// Multi-repo result fields
|
|
46
|
+
repo_results?: HotspotRepoResult[];
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UseHotspotsOptions {
|
|
51
|
+
days: number;
|
|
52
|
+
repo?: string;
|
|
53
|
+
skipTypes?: string[];
|
|
54
|
+
includeOrchestrator?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface UseHotspotsReturn {
|
|
58
|
+
data: HotspotData | null;
|
|
59
|
+
isLoading: boolean;
|
|
60
|
+
error: Error | null;
|
|
61
|
+
refresh: () => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function useHotspots(options: UseHotspotsOptions): UseHotspotsReturn {
|
|
65
|
+
const [data, setData] = useState<HotspotData | null>(null);
|
|
66
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
67
|
+
const [error, setError] = useState<Error | null>(null);
|
|
68
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
69
|
+
|
|
70
|
+
const fetchHotspots = useCallback(() => {
|
|
71
|
+
// Cancel any in-flight request
|
|
72
|
+
if (abortRef.current) {
|
|
73
|
+
abortRef.current.abort();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
abortRef.current = controller;
|
|
78
|
+
|
|
79
|
+
setIsLoading(true);
|
|
80
|
+
setError(null);
|
|
81
|
+
|
|
82
|
+
const params = new URLSearchParams({ days: String(options.days) });
|
|
83
|
+
if (options.repo) {
|
|
84
|
+
params.set('repo', options.repo);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Determine skip_type values: use explicit skipTypes, or default to ['orchestrator']
|
|
88
|
+
// unless includeOrchestrator is true
|
|
89
|
+
const skipTypes = options.skipTypes ??
|
|
90
|
+
(options.includeOrchestrator ? [] : ['orchestrator']);
|
|
91
|
+
for (const st of skipTypes) {
|
|
92
|
+
params.append('skip_type', st);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fetch(`/api/hotspots?${params}`, { signal: controller.signal })
|
|
96
|
+
.then((res) => {
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
99
|
+
}
|
|
100
|
+
return res.json();
|
|
101
|
+
})
|
|
102
|
+
.then((json: HotspotData) => {
|
|
103
|
+
setData(json);
|
|
104
|
+
setIsLoading(false);
|
|
105
|
+
})
|
|
106
|
+
.catch((err) => {
|
|
107
|
+
if (err.name === 'AbortError') return;
|
|
108
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
109
|
+
setIsLoading(false);
|
|
110
|
+
});
|
|
111
|
+
}, [options.days, options.repo, options.skipTypes, options.includeOrchestrator]);
|
|
112
|
+
|
|
113
|
+
// Cleanup abort controller on unmount
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
return () => {
|
|
116
|
+
if (abortRef.current) {
|
|
117
|
+
abortRef.current.abort();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}, []);
|
|
121
|
+
|
|
122
|
+
return { data, isLoading, error, refresh: fetchHotspots };
|
|
123
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLayoutPersistence Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for saving and restoring layout state to config.local.yaml.
|
|
5
|
+
* Story MSSCI-12706 - Layout Persistence
|
|
6
|
+
*
|
|
7
|
+
* Uses native Dockview SerializedDockview format for complete layout fidelity.
|
|
8
|
+
*
|
|
9
|
+
* REST API:
|
|
10
|
+
* - GET /api/settings/layout - Load layout
|
|
11
|
+
* - PATCH /api/settings/layout - Save layout
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Load layout from config on mount
|
|
15
|
+
* - Save layout on changes (debounced)
|
|
16
|
+
* - Per-project independent layouts
|
|
17
|
+
* - Graceful handling of corrupted/missing config
|
|
18
|
+
* - Native Dockview serialization for perfect restore
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
22
|
+
import type { SerializedDockview } from 'dockview-react';
|
|
23
|
+
|
|
24
|
+
const DEBOUNCE_DELAY = 300;
|
|
25
|
+
|
|
26
|
+
interface UseLayoutPersistenceResult {
|
|
27
|
+
layout: SerializedDockview | null;
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
isSaving: boolean;
|
|
30
|
+
error: Error | null;
|
|
31
|
+
saveLayout: (layout: SerializedDockview) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate that the layout has the native Dockview structure
|
|
36
|
+
*/
|
|
37
|
+
function isValidDockviewLayout(layout: unknown): layout is SerializedDockview {
|
|
38
|
+
if (!layout || typeof layout !== 'object') return false;
|
|
39
|
+
const layoutObj = layout as Record<string, unknown>;
|
|
40
|
+
|
|
41
|
+
// Check for native Dockview structure: grid and panels are required
|
|
42
|
+
if (!layoutObj.grid || typeof layoutObj.grid !== 'object') return false;
|
|
43
|
+
if (!layoutObj.panels || typeof layoutObj.panels !== 'object') return false;
|
|
44
|
+
|
|
45
|
+
// A layout with zero panels is empty — treat as invalid so default panels get created
|
|
46
|
+
if (Object.keys(layoutObj.panels as Record<string, unknown>).length === 0) return false;
|
|
47
|
+
|
|
48
|
+
const grid = layoutObj.grid as Record<string, unknown>;
|
|
49
|
+
// Grid should have root, width, height, orientation
|
|
50
|
+
if (!grid.root || typeof grid.width !== 'number' || typeof grid.height !== 'number') return false;
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useLayoutPersistence(endpoint: string = '/api/settings/layout'): UseLayoutPersistenceResult {
|
|
56
|
+
const [layout, setLayout] = useState<SerializedDockview | null>(null);
|
|
57
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
58
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
59
|
+
const [error, setError] = useState<Error | null>(null);
|
|
60
|
+
|
|
61
|
+
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
62
|
+
const pendingLayoutRef = useRef<SerializedDockview | null>(null);
|
|
63
|
+
|
|
64
|
+
// Load layout on mount via REST API
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const loadLayout = async () => {
|
|
67
|
+
try {
|
|
68
|
+
const response = await fetch(endpoint);
|
|
69
|
+
if (response.ok) {
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
if (data.layout && isValidDockviewLayout(data.layout)) {
|
|
72
|
+
// Use native Dockview layout directly
|
|
73
|
+
setLayout(data.layout as SerializedDockview);
|
|
74
|
+
} else {
|
|
75
|
+
// No saved layout or invalid format - let DockviewWorkspace build default
|
|
76
|
+
setLayout(null);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
// API error - let DockviewWorkspace build default
|
|
80
|
+
setLayout(null);
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
// On error, let DockviewWorkspace build default
|
|
84
|
+
console.error('[useLayoutPersistence] Failed to load layout:', err);
|
|
85
|
+
setLayout(null);
|
|
86
|
+
setError(err instanceof Error ? err : new Error('Failed to load layout'));
|
|
87
|
+
} finally {
|
|
88
|
+
setIsLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
loadLayout();
|
|
93
|
+
}, [endpoint]);
|
|
94
|
+
|
|
95
|
+
// Debounced save function via REST API - saves native Dockview format directly
|
|
96
|
+
const saveLayout = useCallback((newLayout: SerializedDockview) => {
|
|
97
|
+
pendingLayoutRef.current = newLayout;
|
|
98
|
+
|
|
99
|
+
// Clear existing debounce timer
|
|
100
|
+
if (debounceRef.current) {
|
|
101
|
+
clearTimeout(debounceRef.current);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Set new debounce timer
|
|
105
|
+
debounceRef.current = setTimeout(async () => {
|
|
106
|
+
const layoutToSave = pendingLayoutRef.current;
|
|
107
|
+
if (!layoutToSave) return;
|
|
108
|
+
|
|
109
|
+
setIsSaving(true);
|
|
110
|
+
try {
|
|
111
|
+
// Save native Dockview format directly - no conversion needed
|
|
112
|
+
const response = await fetch(endpoint, {
|
|
113
|
+
method: 'PATCH',
|
|
114
|
+
headers: { 'Content-Type': 'application/json' },
|
|
115
|
+
body: JSON.stringify(layoutToSave),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
throw new Error('Failed to save layout');
|
|
120
|
+
}
|
|
121
|
+
setError(null);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error('[useLayoutPersistence] Failed to save layout:', err);
|
|
124
|
+
setError(err instanceof Error ? err : new Error('Failed to save layout'));
|
|
125
|
+
} finally {
|
|
126
|
+
setIsSaving(false);
|
|
127
|
+
}
|
|
128
|
+
}, DEBOUNCE_DELAY);
|
|
129
|
+
}, [endpoint]);
|
|
130
|
+
|
|
131
|
+
// Cleanup debounce timer on unmount
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
return () => {
|
|
134
|
+
if (debounceRef.current) {
|
|
135
|
+
clearTimeout(debounceRef.current);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
return { layout, isLoading, isSaving, error, saveLayout };
|
|
141
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useMarkdownParser Hook
|
|
3
|
+
*
|
|
4
|
+
* Story MSSCI-13969: React hook for parsing markdown to HTML with security.
|
|
5
|
+
* Extracted from js/components/message-view/markdown-parser.js
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - XSS prevention via HTML escaping
|
|
9
|
+
* - CYCLIST marker stripping
|
|
10
|
+
* - Memoization for performance
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useMemo } from 'react';
|
|
14
|
+
import { parseMarkdown } from '../utils/markdown.js';
|
|
15
|
+
|
|
16
|
+
export interface UseMarkdownParserResult {
|
|
17
|
+
html: string;
|
|
18
|
+
isLoading: boolean;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* React hook for parsing markdown to HTML with security and memoization.
|
|
24
|
+
* @param markdown - Raw markdown text (or null)
|
|
25
|
+
* @returns Object with html string, loading state, and error
|
|
26
|
+
*/
|
|
27
|
+
export function useMarkdownParser(
|
|
28
|
+
markdown: string | null
|
|
29
|
+
): UseMarkdownParserResult {
|
|
30
|
+
const html = useMemo(() => {
|
|
31
|
+
if (!markdown) return '';
|
|
32
|
+
return parseMarkdown(markdown);
|
|
33
|
+
}, [markdown]);
|
|
34
|
+
|
|
35
|
+
return { html, isLoading: false, error: null };
|
|
36
|
+
}
|