@pennyfarthing/core 11.1.1 → 11.2.1
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 +8 -8
- package/package.json +2 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +381 -66
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/init.js +4 -4
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/update.js +4 -5
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/utils/constants.d.ts +3 -8
- package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/constants.js +3 -4
- package/packages/core/dist/cli/utils/constants.js.map +1 -1
- package/packages/core/dist/cli/utils/settings.d.ts +11 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/settings.js +65 -29
- package/packages/core/dist/cli/utils/settings.js.map +1 -1
- package/packages/core/dist/cli/utils/symlinks.js +16 -16
- package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
- package/packages/core/dist/consultation/tandem-metrics.d.ts +91 -0
- package/packages/core/dist/consultation/tandem-metrics.d.ts.map +1 -0
- package/packages/core/dist/consultation/tandem-metrics.js +131 -0
- package/packages/core/dist/consultation/tandem-metrics.js.map +1 -0
- package/packages/core/dist/consultation/tandem-metrics.test.d.ts +18 -0
- package/packages/core/dist/consultation/tandem-metrics.test.d.ts.map +1 -0
- package/packages/core/dist/consultation/tandem-metrics.test.js +457 -0
- package/packages/core/dist/consultation/tandem-metrics.test.js.map +1 -0
- package/packages/core/dist/public/js/react/react.js +14 -14
- package/packages/core/dist/scripts/benchmark-integration.d.ts +182 -0
- package/packages/core/dist/scripts/benchmark-integration.d.ts.map +1 -0
- package/packages/core/dist/scripts/benchmark-integration.js +691 -0
- package/packages/core/dist/scripts/benchmark-integration.js.map +1 -0
- package/packages/core/dist/scripts/job-fair-aggregator.d.ts +150 -0
- package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +1 -0
- package/packages/core/dist/scripts/job-fair-aggregator.js +547 -0
- package/packages/core/dist/scripts/job-fair-aggregator.js.map +1 -0
- package/packages/core/dist/server/api/agent-load.js +1 -1
- package/packages/core/dist/server/api/agent-load.js.map +1 -1
- package/packages/core/dist/server/otlp-receiver.d.ts +16 -11
- package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
- package/packages/core/dist/server/otlp-receiver.js +185 -24
- package/packages/core/dist/server/otlp-receiver.js.map +1 -1
- package/packages/core/dist/server/otlp-receiver.test.d.ts +21 -0
- package/packages/core/dist/server/otlp-receiver.test.d.ts.map +1 -0
- package/packages/core/dist/server/otlp-receiver.test.js +446 -0
- package/packages/core/dist/server/otlp-receiver.test.js.map +1 -0
- package/packages/core/dist/server/server.d.ts +0 -3
- package/packages/core/dist/server/server.d.ts.map +1 -1
- package/packages/core/dist/server/server.js +3 -37
- package/packages/core/dist/server/server.js.map +1 -1
- package/packages/core/dist/server/server.test.d.ts +1 -1
- package/packages/core/dist/server/server.test.js +12 -23
- package/packages/core/dist/server/server.test.js.map +1 -1
- package/packages/core/dist/shared/capabilities.d.ts +88 -0
- package/packages/core/dist/shared/capabilities.d.ts.map +1 -0
- package/packages/core/dist/shared/capabilities.js +133 -0
- package/packages/core/dist/shared/capabilities.js.map +1 -0
- package/packages/core/dist/shared/capabilities.test.d.ts +2 -0
- package/packages/core/dist/shared/capabilities.test.d.ts.map +1 -0
- package/packages/core/dist/shared/capabilities.test.js +217 -0
- package/packages/core/dist/shared/capabilities.test.js.map +1 -0
- package/packages/core/dist/shared/portrait-resolver.d.ts +9 -0
- package/packages/core/dist/shared/portrait-resolver.d.ts.map +1 -1
- package/packages/core/dist/shared/portrait-resolver.js +27 -0
- package/packages/core/dist/shared/portrait-resolver.js.map +1 -1
- package/packages/core/dist/shared/portrait-resolver.test.js +47 -1
- package/packages/core/dist/shared/portrait-resolver.test.js.map +1 -1
- package/packages/core/dist/shared/spawn-prompt.d.ts +47 -0
- package/packages/core/dist/shared/spawn-prompt.d.ts.map +1 -0
- package/packages/core/dist/shared/spawn-prompt.js +82 -0
- package/packages/core/dist/shared/spawn-prompt.js.map +1 -0
- package/packages/core/dist/shared/spawn-prompt.test.d.ts +2 -0
- package/packages/core/dist/shared/spawn-prompt.test.d.ts.map +1 -0
- package/packages/core/dist/shared/spawn-prompt.test.js +251 -0
- package/packages/core/dist/shared/spawn-prompt.test.js.map +1 -0
- package/packages/core/dist/shared/tandem-portrait-inventory.test.d.ts +13 -0
- package/packages/core/dist/shared/tandem-portrait-inventory.test.d.ts.map +1 -0
- package/packages/core/dist/shared/tandem-portrait-inventory.test.js +126 -0
- package/packages/core/dist/shared/tandem-portrait-inventory.test.js.map +1 -0
- package/packages/core/dist/workflow/tandem-workflow-templates.test.d.ts +18 -0
- package/packages/core/dist/workflow/tandem-workflow-templates.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/tandem-workflow-templates.test.js +434 -0
- package/packages/core/dist/workflow/tandem-workflow-templates.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-schema.d.ts +32 -0
- package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.js +120 -0
- package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.js +570 -1
- package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
- package/pennyfarthing-dist/agents/dev.md +7 -11
- package/pennyfarthing-dist/agents/reviewer.md +9 -3
- package/pennyfarthing-dist/agents/sm-finish.md +18 -1
- package/pennyfarthing-dist/agents/sm-setup.md +1 -1
- package/pennyfarthing-dist/agents/sm.md +2 -2
- package/pennyfarthing-dist/agents/tea.md +1 -1
- package/pennyfarthing-dist/agents/testing-runner.md +2 -1
- package/pennyfarthing-dist/commands/pf-chore.md +2 -2
- package/pennyfarthing-dist/commands/pf-git.md +4 -2
- package/pennyfarthing-dist/commands/pf-standalone.md +7 -2
- package/pennyfarthing-dist/gates/approval.md +63 -0
- package/pennyfarthing-dist/gates/confidence-sm.md +71 -0
- package/pennyfarthing-dist/gates/context-ok.md +56 -0
- package/pennyfarthing-dist/gates/evaluations/confidence-sm.md +54 -0
- package/pennyfarthing-dist/gates/quality-pass.md +67 -0
- package/pennyfarthing-dist/gates/tests-fail.md +84 -0
- package/pennyfarthing-dist/gates/tests-pass.md +79 -0
- package/pennyfarthing-dist/guides/agent-behavior.md +23 -19
- package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +1 -1
- package/pennyfarthing-dist/guides/bell-mode.md +1 -1
- package/pennyfarthing-dist/guides/bikerack.md +3 -3
- package/pennyfarthing-dist/guides/hooks.md +29 -29
- package/pennyfarthing-dist/guides/reflector.md +1 -1
- package/pennyfarthing-dist/guides/tandem-protocol.md +3 -3
- package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
- package/pennyfarthing-dist/guides/xml-tags.md +2 -2
- package/pennyfarthing-dist/scripts/README.md +1 -1
- package/pennyfarthing-dist/scripts/core/check-context.sh +3 -1
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +5 -87
- package/pennyfarthing-dist/scripts/git/README.md +24 -14
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +5 -266
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +5 -151
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +6 -144
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +5 -496
- package/pennyfarthing-dist/scripts/hooks/README.md +6 -6
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +4 -183
- package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +4 -95
- package/pennyfarthing-dist/scripts/hooks/context-warning.sh +4 -65
- package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +3 -31
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +9 -11
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +27 -33
- package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +4 -71
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +3 -19
- package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +4 -30
- package/pennyfarthing-dist/scripts/hooks/session-start.sh +3 -32
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +4 -65
- package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +4 -78
- package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +4 -93
- package/pennyfarthing-dist/scripts/misc/README.md +1 -1
- package/pennyfarthing-dist/scripts/misc/statusline.sh +4 -301
- package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +76 -0
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +4 -221
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +5 -13
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +4 -123
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +4 -33
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +4 -156
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +4 -131
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +4 -249
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +4 -160
- package/pennyfarthing-dist/skills/pf-bc/usage.md +1 -1
- package/pennyfarthing-dist/skills/pf-jira/examples.md +5 -2
- package/pennyfarthing-dist/skills/pf-workflow/examples.md +27 -16
- package/pennyfarthing-dist/skills/pf-workflow/skill.md +9 -12
- package/pennyfarthing-dist/skills/pf-workflow/usage.md +33 -8
- package/pennyfarthing-dist/templates/settings.local.json.template +19 -10
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +18 -6
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +1 -1
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +1 -1
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
- package/pennyfarthing-dist/workflows/review-tandem.yaml +65 -0
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +16 -8
- package/pennyfarthing-dist/workflows/tdd.yaml +11 -2
- package/pennyfarthing_scripts/CLAUDE.md +45 -14
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/cli.py +3 -5
- package/pennyfarthing_scripts/bellmode_hook.py +12 -296
- package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/audit_log_panel.py +119 -0
- package/pennyfarthing_scripts/bikerack/background_panel.py +86 -5
- package/pennyfarthing_scripts/bikerack/base_panel.py +87 -2
- package/pennyfarthing_scripts/bikerack/changed_panel.py +125 -29
- package/pennyfarthing_scripts/bikerack/context_meter_footer.py +88 -0
- package/pennyfarthing_scripts/bikerack/debug_panel.py +32 -2
- package/pennyfarthing_scripts/bikerack/diffs_panel.py +104 -17
- package/pennyfarthing_scripts/bikerack/events.py +28 -0
- package/pennyfarthing_scripts/bikerack/git_panel.py +103 -33
- package/pennyfarthing_scripts/bikerack/launcher.py +15 -15
- package/pennyfarthing_scripts/bikerack/portrait_resolver.py +139 -0
- package/pennyfarthing_scripts/bikerack/progress_panel.py +315 -0
- package/pennyfarthing_scripts/bikerack/sprint_panel.py +395 -32
- package/pennyfarthing_scripts/bikerack/story_detail_data.py +244 -0
- package/pennyfarthing_scripts/bikerack/story_detail_screen.py +176 -0
- package/pennyfarthing_scripts/bikerack/tui.py +575 -37
- package/pennyfarthing_scripts/bikerack/ws_client.py +2 -2
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/cli.py +42 -65
- package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/pr_config.py +38 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__init__.py +1 -0
- package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/dialogue_manager.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/cli.py +149 -0
- package/pennyfarthing_scripts/consultation/dialogue_manager.py +417 -0
- package/pennyfarthing_scripts/context.py +3 -3
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__init__.py +12 -1
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/create_branches.py +3 -4
- package/pennyfarthing_scripts/git/hooks_installer.py +152 -0
- package/pennyfarthing_scripts/git/repos.py +196 -0
- package/pennyfarthing_scripts/git/status_all.py +27 -11
- package/pennyfarthing_scripts/git/worktree.py +302 -0
- package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/cli.py +143 -40
- package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/cli.py +33 -1
- package/pennyfarthing_scripts/handoff/complete_phase.py +40 -0
- package/pennyfarthing_scripts/handoff/marker.py +15 -15
- package/pennyfarthing_scripts/handoff/phase_check.py +96 -0
- package/pennyfarthing_scripts/handoff/resolve_gate.py +18 -15
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__init__.py +437 -0
- package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/bell_mode.py +215 -0
- package/pennyfarthing_scripts/hooks/cli.py +96 -0
- package/pennyfarthing_scripts/hooks/context_breaker.py +104 -0
- package/pennyfarthing_scripts/hooks/context_warning.py +66 -0
- package/pennyfarthing_scripts/hooks/cyclist_pretooluse.py +129 -0
- package/pennyfarthing_scripts/hooks/pre_edit_check.py +78 -0
- package/pennyfarthing_scripts/hooks/reflector_check.py +271 -0
- package/pennyfarthing_scripts/hooks/schema_validation.py +203 -0
- package/pennyfarthing_scripts/hooks/session_start.py +296 -0
- package/pennyfarthing_scripts/hooks/session_stop.py +111 -0
- package/pennyfarthing_scripts/hooks/sprint_yaml_validation.py +97 -0
- package/pennyfarthing_scripts/hooks/statusline.py +420 -0
- package/pennyfarthing_scripts/hooks.py +27 -446
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/pretooluse_hook.py +3 -185
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/heatmap.py +655 -0
- package/pennyfarthing_scripts/prime/workflow.py +2 -1
- package/pennyfarthing_scripts/schema_validation_hook.py +3 -298
- package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/session_start_hook.py +4 -186
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/loader.py +15 -1
- package/pennyfarthing_scripts/sprint/story_update.py +19 -0
- package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_108_1_gate_migration.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_dialogue_manager.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_bikerack.py +51 -51
- package/pennyfarthing_scripts/tests/test_dialogue_manager.py +811 -0
- package/pennyfarthing_scripts/tests/test_handoff_cli.py +16 -11
- package/pennyfarthing_scripts/tests/test_sprint_panel.py +344 -265
- package/pennyfarthing_scripts/tests/test_workflow_check.py +2 -3
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/tandem_awareness.py +254 -0
- package/pennyfarthing_scripts/validate/adapters/workflow.py +19 -0
- package/pennyfarthing_scripts/validate/cli.py +17 -5
- package/pennyfarthing_scripts/welcome_hook.py +3 -149
- package/pennyfarthing_scripts/workflow/__init__.py +40 -0
- package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/cli.py +1100 -0
- package/pennyfarthing_scripts/workflow/helpers.py +241 -0
- package/pennyfarthing_scripts/{workflow.py → workflow/scale.py} +0 -104
- package/pennyfarthing_scripts/workflow/state.py +112 -0
- package/pennyfarthing_scripts/workflow/team_lifecycle.py +257 -0
- package/packages/core/dist/scripts/theme-detail.test.d.ts +0 -10
- package/packages/core/dist/scripts/theme-detail.test.js +0 -199
- package/pennyfarthing-dist/skills/pf-workflow/scripts/list-workflows.sh +0 -91
- package/pennyfarthing-dist/skills/pf-workflow/scripts/resume-workflow.sh +0 -163
- package/pennyfarthing-dist/skills/pf-workflow/scripts/show-workflow.sh +0 -138
- package/pennyfarthing-dist/skills/pf-workflow/scripts/start-workflow.sh +0 -273
- package/pennyfarthing-dist/skills/pf-workflow/scripts/workflow-status.sh +0 -167
- package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
|
@@ -5,21 +5,34 @@ Story 103-4: Connection status indicator in TUI header.
|
|
|
5
5
|
Story 103-6: SprintPanel as default panel on launch.
|
|
6
6
|
Story 103-7: /bc TUI panel focus — subscribe to /ws/focus, switch panels.
|
|
7
7
|
Story 103-9: Panel header chrome — icon + name indicator for active panel.
|
|
8
|
+
Panel navigation: Mount all panels, tab bar, keyboard switching, command palette.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from __future__ import annotations
|
|
11
12
|
|
|
13
|
+
from functools import partial
|
|
12
14
|
from pathlib import Path
|
|
13
15
|
from typing import Any
|
|
14
16
|
|
|
15
17
|
from textual.app import App, ComposeResult
|
|
16
18
|
from textual.binding import Binding
|
|
17
|
-
from textual.
|
|
19
|
+
from textual.command import Hit, Hits, Provider
|
|
20
|
+
from textual.containers import Horizontal, VerticalScroll
|
|
21
|
+
from textual.message import Message
|
|
18
22
|
from textual.reactive import reactive
|
|
19
|
-
from textual.widgets import Footer, Header, Static
|
|
23
|
+
from textual.widgets import Footer, Header, Static, Tab, Tabs
|
|
20
24
|
|
|
21
25
|
from pennyfarthing_scripts.bc.focus import get_last_panel, save_last_panel
|
|
26
|
+
from pennyfarthing_scripts.bikerack.audit_log_panel import AuditLogPanel
|
|
27
|
+
from pennyfarthing_scripts.bikerack.background_panel import BackgroundPanel
|
|
22
28
|
from pennyfarthing_scripts.bikerack.base_panel import get_panel_icon
|
|
29
|
+
from pennyfarthing_scripts.bikerack.changed_panel import ChangedPanel
|
|
30
|
+
from pennyfarthing_scripts.bikerack.context_meter_footer import ContextMeterFooter
|
|
31
|
+
from pennyfarthing_scripts.bikerack.debug_panel import DebugPanel
|
|
32
|
+
from pennyfarthing_scripts.bikerack.diffs_panel import DiffsPanel
|
|
33
|
+
from pennyfarthing_scripts.bikerack.events import NavigateToFile
|
|
34
|
+
from pennyfarthing_scripts.bikerack.git_panel import GitPanel
|
|
35
|
+
from pennyfarthing_scripts.bikerack.progress_panel import ProgressPanel
|
|
23
36
|
from pennyfarthing_scripts.bikerack.sprint_panel import SprintPanel
|
|
24
37
|
from pennyfarthing_scripts.bikerack.ws_client import ConnectionState, WheelHubClient
|
|
25
38
|
|
|
@@ -30,7 +43,49 @@ STATE_DISPLAY: dict[ConnectionState, str] = {
|
|
|
30
43
|
ConnectionState.CONNECTING: "[yellow]● Connecting…[/yellow]",
|
|
31
44
|
}
|
|
32
45
|
|
|
33
|
-
#
|
|
46
|
+
# Agent role colors for Rich markup (mapped from React AGENT_COLORS)
|
|
47
|
+
AGENT_ROLE_COLORS: dict[str, str] = {
|
|
48
|
+
"pm": "purple",
|
|
49
|
+
"sm": "blue",
|
|
50
|
+
"dev": "green",
|
|
51
|
+
"tea": "cyan",
|
|
52
|
+
"reviewer": "red",
|
|
53
|
+
"architect": "dark_orange",
|
|
54
|
+
"devops": "bright_cyan",
|
|
55
|
+
"ux-designer": "magenta",
|
|
56
|
+
"tech-writer": "white",
|
|
57
|
+
"orchestrator": "bright_magenta",
|
|
58
|
+
"ba": "bright_green",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
AGENT_ABBREV: dict[str, str] = {
|
|
62
|
+
"pm": "PM",
|
|
63
|
+
"sm": "SM",
|
|
64
|
+
"dev": "DEV",
|
|
65
|
+
"tea": "TEA",
|
|
66
|
+
"reviewer": "REV",
|
|
67
|
+
"architect": "ARC",
|
|
68
|
+
"devops": "OPS",
|
|
69
|
+
"ux-designer": "UX",
|
|
70
|
+
"tech-writer": "TW",
|
|
71
|
+
"orchestrator": "ORC",
|
|
72
|
+
"ba": "BA",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Ordered panel registry: (key, display_name, widget_class)
|
|
76
|
+
# Only panels with implemented widget classes are included.
|
|
77
|
+
PANEL_REGISTRY: list[tuple[str, str]] = [
|
|
78
|
+
("sprint", "Sprint"),
|
|
79
|
+
("git", "Git"),
|
|
80
|
+
("diffs", "Diffs"),
|
|
81
|
+
("changed", "Changed"),
|
|
82
|
+
("background", "Background"),
|
|
83
|
+
("audit-log", "Audit Log"),
|
|
84
|
+
("debug", "Debug"),
|
|
85
|
+
("progress", "Progress"),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
# Human-readable display names for panels (full set for external focus messages)
|
|
34
89
|
PANEL_DISPLAY_NAMES: dict[str, str] = {
|
|
35
90
|
"sprint": "Sprint",
|
|
36
91
|
"git": "Git",
|
|
@@ -42,24 +97,223 @@ PANEL_DISPLAY_NAMES: dict[str, str] = {
|
|
|
42
97
|
"changed": "Changed",
|
|
43
98
|
"ac": "Acceptance Criteria",
|
|
44
99
|
"debug": "Debug",
|
|
100
|
+
"progress": "Progress",
|
|
45
101
|
"settings": "Settings",
|
|
46
102
|
"tty": "TTY",
|
|
47
103
|
}
|
|
48
104
|
|
|
105
|
+
# Keys from PANEL_REGISTRY for fast lookup
|
|
106
|
+
_PANEL_KEYS = [key for key, _ in PANEL_REGISTRY]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class BindingFooter(Footer):
|
|
110
|
+
"""Footer subclass that exposes active binding text via render().
|
|
111
|
+
|
|
112
|
+
Textual's Footer uses compose() for visual content, so render() returns
|
|
113
|
+
Blank. This override makes binding descriptions available through
|
|
114
|
+
str(footer.render()) for programmatic inspection.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def render(self) -> Any:
|
|
118
|
+
try:
|
|
119
|
+
bindings = self.screen.active_bindings
|
|
120
|
+
parts: list[str] = []
|
|
121
|
+
for _, binding, _enabled, _tooltip in bindings.values():
|
|
122
|
+
if binding.show:
|
|
123
|
+
parts.append(f"{binding.key}:{binding.description}")
|
|
124
|
+
if parts:
|
|
125
|
+
return " ".join(parts)
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
return super().render()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _build_panel_tabs() -> list[Tab]:
|
|
132
|
+
"""Build Tab widgets for each panel in the registry."""
|
|
133
|
+
tabs: list[Tab] = []
|
|
134
|
+
for panel_key, display_name in PANEL_REGISTRY:
|
|
135
|
+
icon = get_panel_icon(panel_key)
|
|
136
|
+
label = f"{icon} {display_name}" if icon else display_name
|
|
137
|
+
tabs.append(Tab(label, id=f"tab-{panel_key}"))
|
|
138
|
+
return tabs
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
PORTRAIT_SKELETON = """\
|
|
142
|
+
[dim]┌────────┐
|
|
143
|
+
│░░░░░░░░│
|
|
144
|
+
│░░░▓▓░░░│
|
|
145
|
+
│░░░░░░░░│
|
|
146
|
+
└────────┘[/dim]"""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class AgentHeader(Static):
|
|
150
|
+
"""Displays current agent persona from WheelHub /ws/persona channel.
|
|
151
|
+
|
|
152
|
+
When a portrait image is available (resolved locally or provided via
|
|
153
|
+
portraitPath in persona data), mounts a Horizontal layout container.
|
|
154
|
+
Shows a skeleton placeholder while the image loads.
|
|
155
|
+
Falls back to text-only when no portrait is found.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
class PortraitLayoutUpdate(Message):
|
|
159
|
+
"""Internal message to update portrait layout asynchronously."""
|
|
160
|
+
|
|
161
|
+
def __init__(self, portrait_path: Path | None) -> None:
|
|
162
|
+
super().__init__()
|
|
163
|
+
self.has_portrait = portrait_path is not None
|
|
164
|
+
self.portrait_path = portrait_path
|
|
165
|
+
|
|
166
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
167
|
+
super().__init__(**kwargs)
|
|
168
|
+
self._is_streaming: bool = False
|
|
169
|
+
self._persona_data: dict[str, Any] = {}
|
|
170
|
+
self._header_text: str = ""
|
|
171
|
+
self._current_portrait: Path | None = None
|
|
172
|
+
|
|
173
|
+
def _apply_persona(self, data: dict[str, Any]) -> None:
|
|
174
|
+
"""Render persona data into the header."""
|
|
175
|
+
if data.get("type") == "streaming":
|
|
176
|
+
self._is_streaming = bool(data.get("isStreaming", False))
|
|
177
|
+
self._render_header()
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
self._persona_data = data
|
|
181
|
+
self._is_streaming = bool(data.get("isStreaming", False))
|
|
182
|
+
self._render_header()
|
|
183
|
+
|
|
184
|
+
def _resolve_portrait(self, data: dict[str, Any]) -> Path | None:
|
|
185
|
+
"""Get portrait path from persona data or resolve locally."""
|
|
186
|
+
portrait_path = data.get("portraitPath")
|
|
187
|
+
if portrait_path:
|
|
188
|
+
p = Path(portrait_path)
|
|
189
|
+
if p.exists():
|
|
190
|
+
return p
|
|
191
|
+
theme = data.get("theme", "")
|
|
192
|
+
role = data.get("role", "")
|
|
193
|
+
if theme and role:
|
|
194
|
+
from pennyfarthing_scripts.bikerack import portrait_resolver
|
|
195
|
+
|
|
196
|
+
return portrait_resolver.resolve_portrait_path(theme, role)
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
def _render_header(self) -> None:
|
|
200
|
+
"""Re-render the header from stored state."""
|
|
201
|
+
data = self._persona_data
|
|
202
|
+
char = data.get("character", "")
|
|
203
|
+
role = data.get("role", "")
|
|
204
|
+
role_desc = data.get("roleDescription", "")
|
|
205
|
+
quote = data.get("quote", "")
|
|
206
|
+
theme = data.get("theme", "")
|
|
207
|
+
|
|
208
|
+
if not char:
|
|
209
|
+
self.update("[dim]Waiting for agent...[/dim]")
|
|
210
|
+
self.post_message(self.PortraitLayoutUpdate(portrait_path=None))
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
parts: list[str] = []
|
|
214
|
+
|
|
215
|
+
# Role badge — escape brackets so Rich doesn't eat them as tags
|
|
216
|
+
if role:
|
|
217
|
+
abbrev = AGENT_ABBREV.get(role, role.upper()[:3])
|
|
218
|
+
color = AGENT_ROLE_COLORS.get(role, "bright_magenta")
|
|
219
|
+
parts.append(f"[bold {color}]\\[{abbrev}][/bold {color}]")
|
|
220
|
+
|
|
221
|
+
# Character name
|
|
222
|
+
parts.append(f"[bold]{char}[/bold]")
|
|
223
|
+
|
|
224
|
+
# Theme name
|
|
225
|
+
if theme:
|
|
226
|
+
from pennyfarthing_scripts.bikerack.base_panel import humanize_theme
|
|
227
|
+
|
|
228
|
+
parts.append(f"[dim]{humanize_theme(theme)}[/dim]")
|
|
229
|
+
|
|
230
|
+
# Streaming indicator
|
|
231
|
+
if self._is_streaming:
|
|
232
|
+
parts.append("[bold yellow]⚡[/bold yellow]")
|
|
233
|
+
|
|
234
|
+
line = " ".join(parts)
|
|
235
|
+
|
|
236
|
+
# Catchphrase subtitle (quote is a random catchphrase from the theme)
|
|
237
|
+
if quote:
|
|
238
|
+
line += f"\n[italic dim]\"{quote}\"[/italic dim]"
|
|
239
|
+
elif role_desc:
|
|
240
|
+
line += f"\n[dim]{role_desc}[/dim]"
|
|
241
|
+
|
|
242
|
+
self._header_text = line
|
|
49
243
|
|
|
50
|
-
|
|
51
|
-
|
|
244
|
+
# Check portrait and schedule layout update
|
|
245
|
+
portrait = self._resolve_portrait(data)
|
|
246
|
+
self.post_message(self.PortraitLayoutUpdate(portrait_path=portrait))
|
|
52
247
|
|
|
53
|
-
|
|
248
|
+
async def on_agent_header_portrait_layout_update(
|
|
249
|
+
self, event: PortraitLayoutUpdate
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Mount or remove Horizontal portrait layout with text beside image.
|
|
54
252
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
253
|
+
Shows a skeleton placeholder immediately while the real image loads
|
|
254
|
+
to avoid a visible blank gap during image decode/render.
|
|
255
|
+
"""
|
|
256
|
+
if event.has_portrait and event.portrait_path:
|
|
257
|
+
if self._current_portrait == event.portrait_path:
|
|
258
|
+
# Same portrait — just update text label if it exists
|
|
259
|
+
try:
|
|
260
|
+
text_widget = self.query_one("#agent-text", Static)
|
|
261
|
+
text_widget.update(self._header_text)
|
|
262
|
+
except Exception:
|
|
263
|
+
pass
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
# New portrait or first time — full layout rebuild
|
|
267
|
+
for child in list(self.query("Horizontal")):
|
|
268
|
+
await child.remove()
|
|
269
|
+
|
|
270
|
+
# Mount skeleton + text immediately so there's no blank gap
|
|
271
|
+
self.update("")
|
|
272
|
+
self._current_portrait = event.portrait_path
|
|
273
|
+
skeleton = Static(PORTRAIT_SKELETON, id="portrait-skeleton")
|
|
274
|
+
text = Static(self._header_text, id="agent-text")
|
|
275
|
+
row = Horizontal(skeleton, text, id="portrait-row")
|
|
276
|
+
await self.mount(row)
|
|
277
|
+
|
|
278
|
+
# Now try to load the real image and swap it in
|
|
279
|
+
try:
|
|
280
|
+
from pennyfarthing_scripts.bikerack.portrait_resolver import (
|
|
281
|
+
detect_image_protocol,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
protocol = detect_image_protocol()
|
|
285
|
+
if protocol is None:
|
|
286
|
+
# No image protocol — remove skeleton, fall back to text-only
|
|
287
|
+
for child in list(self.query("Horizontal")):
|
|
288
|
+
await child.remove()
|
|
289
|
+
self.update(self._header_text)
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
if protocol == "kitty":
|
|
293
|
+
from textual_image.widget import TGPImage as ImageWidget
|
|
294
|
+
elif protocol == "sixel":
|
|
295
|
+
from textual_image.widget import SixelImage as ImageWidget
|
|
296
|
+
else:
|
|
297
|
+
from textual_image.widget import HalfcellImage as ImageWidget
|
|
298
|
+
|
|
299
|
+
img = ImageWidget(str(event.portrait_path), id="portrait-img")
|
|
300
|
+
try:
|
|
301
|
+
skel = self.query_one("#portrait-skeleton")
|
|
302
|
+
await skel.remove()
|
|
303
|
+
except Exception:
|
|
304
|
+
pass
|
|
305
|
+
await row.mount(img, before=0)
|
|
306
|
+
except (ImportError, Exception):
|
|
307
|
+
# textual-image not available — remove skeleton, text-only
|
|
308
|
+
for child in list(self.query("Horizontal")):
|
|
309
|
+
await child.remove()
|
|
310
|
+
self.update(self._header_text)
|
|
61
311
|
else:
|
|
62
|
-
|
|
312
|
+
# No portrait — text-only
|
|
313
|
+
for child in list(self.query("Horizontal")):
|
|
314
|
+
await child.remove()
|
|
315
|
+
self._current_portrait = None
|
|
316
|
+
self.update(self._header_text)
|
|
63
317
|
|
|
64
318
|
|
|
65
319
|
class ConnectionStatus(Static):
|
|
@@ -74,58 +328,309 @@ class ConnectionStatus(Static):
|
|
|
74
328
|
self.update(STATE_DISPLAY.get(state, "● Unknown"))
|
|
75
329
|
|
|
76
330
|
|
|
331
|
+
class PanelCommands(Provider):
|
|
332
|
+
"""Command palette provider for panel switching."""
|
|
333
|
+
|
|
334
|
+
async def search(self, query: str) -> Hits:
|
|
335
|
+
matcher = self.matcher(query)
|
|
336
|
+
for panel_key, display_name in PANEL_REGISTRY:
|
|
337
|
+
icon = get_panel_icon(panel_key)
|
|
338
|
+
label = f"{icon} {display_name}" if icon else display_name
|
|
339
|
+
score = matcher.match(display_name)
|
|
340
|
+
if score > 0:
|
|
341
|
+
yield Hit(
|
|
342
|
+
score,
|
|
343
|
+
matcher.highlight(label),
|
|
344
|
+
partial(self.app.action_switch_panel, panel_key),
|
|
345
|
+
help=f"Switch to {display_name} panel",
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
77
349
|
class BikeRackApp(App):
|
|
78
350
|
"""BikeRack TUI application shell."""
|
|
79
351
|
|
|
352
|
+
class PersonaUpdate(Message, bubble=False):
|
|
353
|
+
"""Persona data from WS — routed through Textual message system."""
|
|
354
|
+
|
|
355
|
+
def __init__(self, data: dict[str, Any]) -> None:
|
|
356
|
+
super().__init__()
|
|
357
|
+
self.data = data
|
|
358
|
+
|
|
359
|
+
class FocusUpdate(Message, bubble=False):
|
|
360
|
+
"""Focus change from WS — routed through Textual message system."""
|
|
361
|
+
|
|
362
|
+
def __init__(self, focus: str | None) -> None:
|
|
363
|
+
super().__init__()
|
|
364
|
+
self.focus = focus
|
|
365
|
+
|
|
366
|
+
class WsStateUpdate(Message, bubble=False):
|
|
367
|
+
"""WS connection state change — routed through Textual message system."""
|
|
368
|
+
|
|
369
|
+
def __init__(self, state: ConnectionState) -> None:
|
|
370
|
+
super().__init__()
|
|
371
|
+
self.state = state
|
|
372
|
+
|
|
80
373
|
TITLE = "BikeRack"
|
|
81
374
|
|
|
375
|
+
CSS = """
|
|
376
|
+
#agent-header {
|
|
377
|
+
height: auto;
|
|
378
|
+
max-height: 7;
|
|
379
|
+
padding: 0 1;
|
|
380
|
+
border-bottom: solid $accent;
|
|
381
|
+
}
|
|
382
|
+
#portrait-row {
|
|
383
|
+
height: 5;
|
|
384
|
+
width: 100%;
|
|
385
|
+
}
|
|
386
|
+
#portrait-img {
|
|
387
|
+
width: 10;
|
|
388
|
+
height: 5;
|
|
389
|
+
margin: 0 1 0 0;
|
|
390
|
+
}
|
|
391
|
+
#agent-text {
|
|
392
|
+
height: auto;
|
|
393
|
+
width: 1fr;
|
|
394
|
+
}
|
|
395
|
+
Tabs {
|
|
396
|
+
dock: top;
|
|
397
|
+
}
|
|
398
|
+
Tab.-active {
|
|
399
|
+
color: $text;
|
|
400
|
+
}
|
|
401
|
+
Tab {
|
|
402
|
+
color: $text-muted;
|
|
403
|
+
}
|
|
404
|
+
#connection-status {
|
|
405
|
+
height: 1;
|
|
406
|
+
}
|
|
407
|
+
ContextMeterFooter {
|
|
408
|
+
height: 1;
|
|
409
|
+
}
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
COMMANDS = App.COMMANDS | {PanelCommands}
|
|
413
|
+
|
|
82
414
|
BINDINGS = [
|
|
83
415
|
Binding("q", "quit", "Quit"),
|
|
416
|
+
Binding("1", "switch_panel('sprint')", "Sprint", show=False),
|
|
417
|
+
Binding("2", "switch_panel('git')", "Git", show=False),
|
|
418
|
+
Binding("3", "switch_panel('diffs')", "Diffs", show=False),
|
|
419
|
+
Binding("4", "switch_panel('changed')", "Changed", show=False),
|
|
420
|
+
Binding("5", "switch_panel('background')", "Background", show=False),
|
|
421
|
+
Binding("6", "switch_panel('audit-log')", "Audit Log", show=False),
|
|
422
|
+
Binding("7", "switch_panel('debug')", "Debug", show=False),
|
|
423
|
+
Binding("8", "switch_panel('progress')", "Progress", show=False),
|
|
424
|
+
Binding("bracketright", "next_panel", "]Next"),
|
|
425
|
+
Binding("bracketleft", "prev_panel", "[Prev"),
|
|
426
|
+
Binding("tab", "next_panel", show=False),
|
|
427
|
+
Binding("shift+tab", "prev_panel", show=False),
|
|
428
|
+
Binding("n", "next_diff_file", "Next file", show=False),
|
|
429
|
+
Binding("p", "prev_diff_file", "Prev file", show=False),
|
|
430
|
+
Binding("j", "next_epic", show=False),
|
|
431
|
+
Binding("k", "prev_epic", show=False),
|
|
432
|
+
Binding("e", "toggle_epic", show=False),
|
|
84
433
|
]
|
|
85
434
|
|
|
435
|
+
def _get_dom_base(self):
|
|
436
|
+
"""Query the active screen so app.query() finds pushed screen widgets."""
|
|
437
|
+
return self.screen
|
|
438
|
+
|
|
86
439
|
def __init__(self, client=None, **kwargs):
|
|
87
440
|
super().__init__(**kwargs)
|
|
88
441
|
self._client = client
|
|
89
|
-
self._focused_panel: str
|
|
442
|
+
self._focused_panel: str = "sprint"
|
|
90
443
|
self._previous_panel: str | None = None
|
|
444
|
+
self._programmatic_tab_count: int = 0
|
|
91
445
|
|
|
92
446
|
def compose(self) -> ComposeResult:
|
|
93
447
|
yield Header()
|
|
94
|
-
yield
|
|
448
|
+
yield AgentHeader(id="agent-header")
|
|
449
|
+
yield Tabs(*_build_panel_tabs(), id="tab-bar")
|
|
95
450
|
yield ConnectionStatus(
|
|
96
451
|
STATE_DISPLAY[ConnectionState.DISCONNECTED],
|
|
97
452
|
id="connection-status",
|
|
98
453
|
)
|
|
99
454
|
with VerticalScroll(id="main-content"):
|
|
100
|
-
yield SprintPanel(client=self._client, id="sprint
|
|
101
|
-
|
|
455
|
+
yield SprintPanel(client=self._client, id="panel-sprint")
|
|
456
|
+
yield GitPanel(client=self._client, id="panel-git")
|
|
457
|
+
yield DiffsPanel(client=self._client, id="panel-diffs")
|
|
458
|
+
yield ChangedPanel(client=self._client, id="panel-changed")
|
|
459
|
+
yield BackgroundPanel(client=self._client, id="panel-background")
|
|
460
|
+
yield AuditLogPanel(client=self._client, id="panel-audit-log")
|
|
461
|
+
yield DebugPanel(client=self._client, id="panel-debug")
|
|
462
|
+
yield ProgressPanel(client=self._client, id="panel-progress")
|
|
463
|
+
yield ContextMeterFooter(client=self._client)
|
|
464
|
+
yield BindingFooter()
|
|
102
465
|
|
|
103
466
|
async def on_mount(self) -> None:
|
|
467
|
+
# Restore last panel or default to sprint
|
|
104
468
|
result = get_last_panel()
|
|
469
|
+
initial = "sprint"
|
|
105
470
|
if result.get("success") and result.get("last_panel"):
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
471
|
+
last = result["last_panel"]
|
|
472
|
+
if last in _PANEL_KEYS:
|
|
473
|
+
initial = last
|
|
474
|
+
|
|
475
|
+
self._focused_panel = initial
|
|
476
|
+
|
|
477
|
+
# Hide all panels except the active one
|
|
478
|
+
for panel_key in _PANEL_KEYS:
|
|
479
|
+
widget_id = f"panel-{panel_key}"
|
|
480
|
+
try:
|
|
481
|
+
widget = self.query_one(f"#{widget_id}")
|
|
482
|
+
widget.display = (panel_key == initial)
|
|
483
|
+
except Exception:
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
# Set tab bar active state and focus initial panel
|
|
487
|
+
self._update_tab_bar(initial)
|
|
488
|
+
try:
|
|
489
|
+
initial_widget = self.query_one(f"#panel-{initial}")
|
|
490
|
+
initial_widget.focus()
|
|
491
|
+
except Exception:
|
|
492
|
+
pass
|
|
110
493
|
|
|
111
494
|
if self._client is not None:
|
|
112
495
|
self._client.on_state_change(self._on_ws_state_change)
|
|
113
496
|
self._client.subscribe("focus", self._handle_focus_message)
|
|
497
|
+
self._client.subscribe("persona", self._handle_persona_message)
|
|
114
498
|
self.run_worker(self._client.connect(), exclusive=True, name="ws-client")
|
|
115
499
|
|
|
116
|
-
def
|
|
117
|
-
"""
|
|
500
|
+
def on_navigate_to_file(self, event: NavigateToFile) -> None:
|
|
501
|
+
"""Handle NavigateToFile — switch to diffs and navigate to file."""
|
|
502
|
+
self.action_switch_panel("diffs")
|
|
503
|
+
try:
|
|
504
|
+
diffs = self.query_one("#panel-diffs", DiffsPanel)
|
|
505
|
+
diffs.navigate_to_file(event.path)
|
|
506
|
+
except Exception:
|
|
507
|
+
pass
|
|
508
|
+
|
|
509
|
+
def action_switch_panel(self, key: str) -> None:
|
|
510
|
+
"""Switch to a panel by key."""
|
|
511
|
+
if key not in _PANEL_KEYS:
|
|
512
|
+
return
|
|
513
|
+
if key == self._focused_panel:
|
|
514
|
+
return
|
|
515
|
+
|
|
516
|
+
# Hide current panel
|
|
517
|
+
try:
|
|
518
|
+
current = self.query_one(f"#panel-{self._focused_panel}")
|
|
519
|
+
current.display = False
|
|
520
|
+
except Exception:
|
|
521
|
+
pass
|
|
522
|
+
|
|
523
|
+
# Show target panel and focus it
|
|
524
|
+
try:
|
|
525
|
+
target = self.query_one(f"#panel-{key}")
|
|
526
|
+
target.display = True
|
|
527
|
+
target.focus()
|
|
528
|
+
except Exception:
|
|
529
|
+
pass
|
|
530
|
+
|
|
531
|
+
self._previous_panel = self._focused_panel
|
|
532
|
+
self._focused_panel = key
|
|
533
|
+
save_last_panel(key, project_dir=None)
|
|
534
|
+
self._update_tab_bar(key)
|
|
535
|
+
|
|
536
|
+
def action_next_panel(self) -> None:
|
|
537
|
+
"""Cycle to the next panel."""
|
|
118
538
|
try:
|
|
119
|
-
|
|
120
|
-
|
|
539
|
+
idx = _PANEL_KEYS.index(self._focused_panel)
|
|
540
|
+
except ValueError:
|
|
541
|
+
idx = 0
|
|
542
|
+
next_idx = (idx + 1) % len(_PANEL_KEYS)
|
|
543
|
+
self.action_switch_panel(_PANEL_KEYS[next_idx])
|
|
544
|
+
|
|
545
|
+
def action_prev_panel(self) -> None:
|
|
546
|
+
"""Cycle to the previous panel."""
|
|
547
|
+
try:
|
|
548
|
+
idx = _PANEL_KEYS.index(self._focused_panel)
|
|
549
|
+
except ValueError:
|
|
550
|
+
idx = 0
|
|
551
|
+
prev_idx = (idx - 1) % len(_PANEL_KEYS)
|
|
552
|
+
self.action_switch_panel(_PANEL_KEYS[prev_idx])
|
|
553
|
+
|
|
554
|
+
def action_next_diff_file(self) -> None:
|
|
555
|
+
"""Advance to next file in diffs panel."""
|
|
556
|
+
if self._focused_panel == "diffs":
|
|
557
|
+
try:
|
|
558
|
+
panel = self.query_one("#panel-diffs", DiffsPanel)
|
|
559
|
+
panel.next_file()
|
|
560
|
+
except Exception:
|
|
561
|
+
pass
|
|
562
|
+
|
|
563
|
+
def action_prev_diff_file(self) -> None:
|
|
564
|
+
"""Go to previous file in diffs panel."""
|
|
565
|
+
if self._focused_panel == "diffs":
|
|
566
|
+
try:
|
|
567
|
+
panel = self.query_one("#panel-diffs", DiffsPanel)
|
|
568
|
+
panel.prev_file()
|
|
569
|
+
except Exception:
|
|
570
|
+
pass
|
|
571
|
+
|
|
572
|
+
def action_next_epic(self) -> None:
|
|
573
|
+
"""Move to next epic in sprint panel."""
|
|
574
|
+
if self._focused_panel == "sprint":
|
|
575
|
+
try:
|
|
576
|
+
panel = self.query_one("#panel-sprint", SprintPanel)
|
|
577
|
+
panel.next_epic()
|
|
578
|
+
except Exception:
|
|
579
|
+
pass
|
|
580
|
+
|
|
581
|
+
def action_prev_epic(self) -> None:
|
|
582
|
+
"""Move to previous epic in sprint panel."""
|
|
583
|
+
if self._focused_panel == "sprint":
|
|
584
|
+
try:
|
|
585
|
+
panel = self.query_one("#panel-sprint", SprintPanel)
|
|
586
|
+
panel.prev_epic()
|
|
587
|
+
except Exception:
|
|
588
|
+
pass
|
|
589
|
+
|
|
590
|
+
def action_toggle_epic(self) -> None:
|
|
591
|
+
"""Toggle expand/collapse on selected epic in sprint panel."""
|
|
592
|
+
if self._focused_panel == "sprint":
|
|
593
|
+
try:
|
|
594
|
+
panel = self.query_one("#panel-sprint", SprintPanel)
|
|
595
|
+
panel.toggle_epic()
|
|
596
|
+
except Exception:
|
|
597
|
+
pass
|
|
598
|
+
|
|
599
|
+
def _update_tab_bar(self, panel_key: str) -> None:
|
|
600
|
+
"""Update the tab bar widget with the given panel key.
|
|
601
|
+
|
|
602
|
+
Increments _programmatic_tab_count so the async TabActivated
|
|
603
|
+
handler knows to ignore the event (prevents infinite ping-pong).
|
|
604
|
+
"""
|
|
605
|
+
try:
|
|
606
|
+
tab_bar = self.query_one("#tab-bar", Tabs)
|
|
607
|
+
tab_id = f"tab-{panel_key}"
|
|
608
|
+
if tab_bar.active != tab_id:
|
|
609
|
+
self._programmatic_tab_count += 1
|
|
610
|
+
tab_bar.active = tab_id
|
|
121
611
|
except Exception:
|
|
122
612
|
pass
|
|
123
613
|
|
|
614
|
+
def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
|
|
615
|
+
"""Handle tab activation from the Tabs widget.
|
|
616
|
+
|
|
617
|
+
Programmatic tabs.active changes fire TabActivated asynchronously.
|
|
618
|
+
We use a counter to skip those and only react to genuine user clicks.
|
|
619
|
+
"""
|
|
620
|
+
if self._programmatic_tab_count > 0:
|
|
621
|
+
self._programmatic_tab_count -= 1
|
|
622
|
+
return
|
|
623
|
+
tab_id = event.tab.id or ""
|
|
624
|
+
panel_key = tab_id.removeprefix("tab-")
|
|
625
|
+
if panel_key in _PANEL_KEYS and panel_key != self._focused_panel:
|
|
626
|
+
self.action_switch_panel(panel_key)
|
|
627
|
+
|
|
124
628
|
def _handle_focus_message(self, message: dict[str, Any] | None) -> None:
|
|
125
629
|
"""Handle incoming focus channel messages.
|
|
126
630
|
|
|
127
631
|
Expected format: {type: 'init'|'update', focus: '<panel>'|null}
|
|
128
632
|
Only 'update' messages trigger panel switches (matching React hook).
|
|
633
|
+
Routes through Textual message system via post_message for proper repaint.
|
|
129
634
|
"""
|
|
130
635
|
if message is None or not isinstance(message, dict):
|
|
131
636
|
return
|
|
@@ -133,22 +638,47 @@ class BikeRackApp(App):
|
|
|
133
638
|
return
|
|
134
639
|
if "focus" not in message:
|
|
135
640
|
return
|
|
641
|
+
self.post_message(self.FocusUpdate(message["focus"]))
|
|
642
|
+
|
|
643
|
+
def _handle_persona_message(self, message: dict[str, Any] | None) -> None:
|
|
644
|
+
"""Handle incoming persona channel messages.
|
|
645
|
+
|
|
646
|
+
Routes through Textual message system via post_message for proper repaint.
|
|
647
|
+
"""
|
|
648
|
+
if message is None or not isinstance(message, dict):
|
|
649
|
+
return
|
|
650
|
+
self.post_message(self.PersonaUpdate(message))
|
|
136
651
|
|
|
137
|
-
|
|
138
|
-
|
|
652
|
+
def _on_ws_state_change(self, state: ConnectionState) -> None:
|
|
653
|
+
"""Handle WheelHub connection state changes.
|
|
654
|
+
|
|
655
|
+
Routes through Textual message system via post_message for proper repaint.
|
|
656
|
+
"""
|
|
657
|
+
self.post_message(self.WsStateUpdate(state))
|
|
658
|
+
|
|
659
|
+
def on_bike_rack_app_persona_update(self, event: PersonaUpdate) -> None:
|
|
660
|
+
"""Apply persona data in Textual message context."""
|
|
661
|
+
try:
|
|
662
|
+
header = self.query_one("#agent-header", AgentHeader)
|
|
663
|
+
header._apply_persona(event.data)
|
|
664
|
+
except Exception:
|
|
665
|
+
pass
|
|
666
|
+
|
|
667
|
+
def on_bike_rack_app_focus_update(self, event: FocusUpdate) -> None:
|
|
668
|
+
"""Apply focus change in Textual message context."""
|
|
669
|
+
focus = event.focus
|
|
670
|
+
if focus is not None and focus in _PANEL_KEYS:
|
|
671
|
+
self.action_switch_panel(focus)
|
|
672
|
+
elif focus is not None:
|
|
139
673
|
self._previous_panel = self._focused_panel
|
|
140
674
|
self._focused_panel = focus
|
|
141
675
|
save_last_panel(focus, project_dir=None)
|
|
142
|
-
self._update_panel_indicator(focus)
|
|
143
|
-
else:
|
|
144
|
-
self._focused_panel = None
|
|
145
|
-
self._previous_panel = None
|
|
146
676
|
|
|
147
|
-
def
|
|
148
|
-
"""
|
|
677
|
+
def on_bike_rack_app_ws_state_update(self, event: WsStateUpdate) -> None:
|
|
678
|
+
"""Apply connection state in Textual message context."""
|
|
149
679
|
try:
|
|
150
680
|
widget = self.query_one("#connection-status", ConnectionStatus)
|
|
151
|
-
widget.connection_state = state
|
|
681
|
+
widget.connection_state = event.state
|
|
152
682
|
except Exception:
|
|
153
683
|
pass
|
|
154
684
|
|
|
@@ -156,16 +686,24 @@ class BikeRackApp(App):
|
|
|
156
686
|
DEFAULT_PORT = 2898
|
|
157
687
|
|
|
158
688
|
|
|
159
|
-
def main(
|
|
689
|
+
def main(
|
|
690
|
+
port: int | None = None,
|
|
691
|
+
project_dir: Path | None = None,
|
|
692
|
+
) -> None:
|
|
160
693
|
"""Launch BikeRack TUI as a standalone application.
|
|
161
694
|
|
|
162
695
|
Args:
|
|
163
|
-
port: Explicit WheelHub port. If None, reads from .
|
|
696
|
+
port: Explicit WheelHub port. If None, reads from .wheelhub-port file.
|
|
164
697
|
project_dir: Project directory for port file discovery. Defaults to cwd.
|
|
165
698
|
"""
|
|
699
|
+
# Detect terminal image protocol BEFORE App.run() claims the terminal
|
|
700
|
+
from pennyfarthing_scripts.bikerack import portrait_resolver
|
|
701
|
+
|
|
702
|
+
portrait_resolver.detect_image_protocol()
|
|
703
|
+
|
|
166
704
|
if port is None:
|
|
167
705
|
if project_dir is not None:
|
|
168
|
-
port_file = project_dir / ".
|
|
706
|
+
port_file = project_dir / ".wheelhub-port"
|
|
169
707
|
if port_file.exists():
|
|
170
708
|
try:
|
|
171
709
|
port = int(port_file.read_text().strip())
|