@pennyfarthing/core 11.2.0 → 11.2.2
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 +100 -40
- 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 +474 -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 +7 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/settings.js +70 -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/dialogue-manager.d.ts +1 -1
- package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -1
- package/packages/core/dist/consultation/dialogue-manager.js +1 -1
- package/packages/core/dist/consultation/dialogue-manager.js.map +1 -1
- package/packages/core/dist/consultation/dialogue-manager.test.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/css/react.css +1 -1
- package/packages/core/dist/public/js/react/react.js +14 -14
- 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/api/git.d.ts.map +1 -1
- package/packages/core/dist/server/api/git.js +0 -1
- package/packages/core/dist/server/api/git.js.map +1 -1
- package/packages/core/dist/server/api/index.d.ts +2 -0
- package/packages/core/dist/server/api/index.d.ts.map +1 -1
- package/packages/core/dist/server/api/index.js +2 -0
- package/packages/core/dist/server/api/index.js.map +1 -1
- package/packages/core/dist/server/api/project-info.d.ts +11 -0
- package/packages/core/dist/server/api/project-info.d.ts.map +1 -0
- package/packages/core/dist/server/api/project-info.js +18 -0
- package/packages/core/dist/server/api/project-info.js.map +1 -0
- package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
- package/packages/core/dist/server/otlp-receiver.js +18 -1
- package/packages/core/dist/server/otlp-receiver.js.map +1 -1
- package/packages/core/dist/server/otlp-receiver.test.js +1 -1
- package/packages/core/dist/server/otlp-receiver.test.js.map +1 -1
- 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 +5 -38
- 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/server/settings.d.ts +1 -0
- package/packages/core/dist/server/settings.d.ts.map +1 -1
- package/packages/core/dist/server/settings.js +13 -0
- package/packages/core/dist/server/settings.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/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/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/team-lifecycle.d.ts +169 -0
- package/packages/core/dist/workflow/team-lifecycle.d.ts.map +1 -0
- package/packages/core/dist/workflow/team-lifecycle.js +217 -0
- package/packages/core/dist/workflow/team-lifecycle.js.map +1 -0
- package/packages/core/dist/workflow/team-lifecycle.test.d.ts +20 -0
- package/packages/core/dist/workflow/team-lifecycle.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/team-lifecycle.test.js +966 -0
- package/packages/core/dist/workflow/team-lifecycle.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/packages/core/dist/workflow/workflow-team-templates.test.d.ts +17 -0
- package/packages/core/dist/workflow/workflow-team-templates.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-team-templates.test.js +275 -0
- package/packages/core/dist/workflow/workflow-team-templates.test.js.map +1 -0
- package/pennyfarthing-dist/agents/dev.md +21 -12
- package/pennyfarthing-dist/agents/reviewer.md +23 -4
- package/pennyfarthing-dist/agents/sm-finish.md +19 -2
- package/pennyfarthing-dist/agents/sm-setup.md +7 -7
- package/pennyfarthing-dist/agents/sm.md +12 -12
- package/pennyfarthing-dist/agents/tea.md +2 -2
- package/pennyfarthing-dist/agents/testing-runner.md +1 -1
- package/pennyfarthing-dist/commands/pf-architect.md +1 -1
- package/pennyfarthing-dist/commands/pf-ba.md +1 -1
- package/pennyfarthing-dist/commands/pf-chore.md +2 -2
- package/pennyfarthing-dist/commands/pf-dev.md +1 -1
- package/pennyfarthing-dist/commands/pf-devops.md +1 -1
- package/pennyfarthing-dist/commands/pf-epic.md +6 -6
- package/pennyfarthing-dist/commands/pf-git.md +12 -10
- package/pennyfarthing-dist/commands/pf-health-check.md +1 -1
- package/pennyfarthing-dist/commands/pf-help.md +12 -12
- package/pennyfarthing-dist/commands/pf-orchestrator.md +1 -1
- package/pennyfarthing-dist/commands/pf-pm.md +1 -1
- package/pennyfarthing-dist/commands/pf-prime.md +8 -8
- package/pennyfarthing-dist/commands/pf-reviewer.md +1 -1
- package/pennyfarthing-dist/commands/pf-session.md +7 -7
- package/pennyfarthing-dist/commands/pf-sm.md +1 -1
- package/pennyfarthing-dist/commands/pf-sprint.md +7 -7
- package/pennyfarthing-dist/commands/pf-tea.md +1 -1
- package/pennyfarthing-dist/commands/pf-tech-writer.md +1 -1
- package/pennyfarthing-dist/commands/pf-theme.md +9 -9
- package/pennyfarthing-dist/commands/pf-ux-designer.md +1 -1
- package/pennyfarthing-dist/commands/pf-work.md +1 -1
- 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 +84 -29
- package/pennyfarthing-dist/guides/agent-coordination.md +10 -10
- package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +6 -6
- package/pennyfarthing-dist/guides/agent-template-tactical.md +1 -1
- package/pennyfarthing-dist/guides/bell-mode.md +1 -1
- package/pennyfarthing-dist/guides/bikerack.md +10 -10
- package/pennyfarthing-dist/guides/brownfield-tools.md +24 -24
- package/pennyfarthing-dist/guides/command-tag-taxonomy.md +1 -1
- package/pennyfarthing-dist/guides/gate-schema.md +2 -2
- package/pennyfarthing-dist/guides/gates.md +3 -3
- package/pennyfarthing-dist/guides/handoff-cli.md +8 -8
- package/pennyfarthing-dist/guides/hooks.md +29 -29
- package/pennyfarthing-dist/guides/prime.md +2 -2
- package/pennyfarthing-dist/guides/reflector.md +1 -1
- package/pennyfarthing-dist/guides/skill-schema.md +6 -6
- package/pennyfarthing-dist/guides/tandem-protocol.md +3 -3
- package/pennyfarthing-dist/guides/workflow-schema.md +1 -1
- package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
- package/pennyfarthing-dist/guides/xml-tags.md +8 -8
- package/pennyfarthing-dist/scripts/README.md +4 -4
- package/pennyfarthing-dist/scripts/core/agent-session.sh +2 -5
- package/pennyfarthing-dist/scripts/core/check-context.sh +3 -1
- package/pennyfarthing-dist/scripts/core/pf.sh +5 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +4 -89
- package/pennyfarthing-dist/scripts/core/prime.sh +2 -25
- package/pennyfarthing-dist/scripts/git/README.md +14 -14
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +2 -3
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +2 -3
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +2 -3
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +2 -4
- package/pennyfarthing-dist/scripts/hooks/README.md +6 -6
- 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 +5 -4
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +29 -34
- 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 +3 -93
- package/pennyfarthing-dist/scripts/lib/env.sh +34 -0
- package/pennyfarthing-dist/scripts/lib/run-pf.sh +39 -0
- package/pennyfarthing-dist/scripts/misc/README.md +1 -1
- package/pennyfarthing-dist/scripts/misc/statusline.sh +4 -301
- package/pennyfarthing-dist/scripts/sprint/README.md +21 -21
- package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +2 -16
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +3 -3
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +3 -3
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +3 -3
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +3 -3
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +3 -3
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +3 -3
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +3 -3
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +3 -3
- package/pennyfarthing-dist/skills/pf-bc/examples.md +23 -23
- package/pennyfarthing-dist/skills/pf-bc/skill.md +17 -17
- package/pennyfarthing-dist/skills/pf-bc/usage.md +8 -8
- package/pennyfarthing-dist/skills/pf-jira/SKILL.md +15 -15
- package/pennyfarthing-dist/skills/pf-jira/examples.md +48 -48
- package/pennyfarthing-dist/skills/pf-jira/usage.md +15 -15
- package/pennyfarthing-dist/skills/pf-sprint/examples.md +80 -80
- package/pennyfarthing-dist/skills/pf-sprint/skill.md +35 -35
- package/pennyfarthing-dist/skills/pf-sprint/usage.md +30 -30
- package/pennyfarthing-dist/skills/pf-theme/examples.md +15 -15
- package/pennyfarthing-dist/skills/pf-theme/skill.md +6 -6
- package/pennyfarthing-dist/skills/pf-theme/usage.md +5 -5
- package/pennyfarthing-dist/skills/pf-workflow/examples.md +27 -27
- package/pennyfarthing-dist/skills/pf-workflow/skill.md +11 -11
- package/pennyfarthing-dist/skills/pf-workflow/usage.md +11 -11
- package/pennyfarthing-dist/skills/skill-registry.yaml +19 -19
- package/pennyfarthing-dist/templates/settings.local.json.template +19 -10
- package/pennyfarthing-dist/workflows/bdd-team.yaml +89 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +1 -1
- 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/project-setup/steps/step-01-discover.md +47 -0
- package/pennyfarthing-dist/workflows/tdd-team.yaml +80 -0
- package/pennyfarthing-dist/workflows/tdd.yaml +11 -2
- package/pennyfarthing_scripts/CLAUDE.md +19 -10
- package/pennyfarthing_scripts/__init__.py +1 -1
- 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__/context.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.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/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/split.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/cli.py +2 -2
- package/pennyfarthing_scripts/bellmode_hook.py +9 -296
- 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__/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 +161 -0
- package/pennyfarthing_scripts/bikerack/base_panel.py +27 -4
- package/pennyfarthing_scripts/bikerack/changed_panel.py +96 -4
- package/pennyfarthing_scripts/bikerack/context_meter_footer.py +88 -0
- package/pennyfarthing_scripts/bikerack/debug_panel.py +1 -1
- package/pennyfarthing_scripts/bikerack/diffs_panel.py +30 -0
- package/pennyfarthing_scripts/bikerack/events.py +28 -0
- package/pennyfarthing_scripts/bikerack/launcher.py +6 -6
- package/pennyfarthing_scripts/bikerack/portrait_resolver.py +139 -0
- package/pennyfarthing_scripts/bikerack/progress_panel.py +0 -1
- package/pennyfarthing_scripts/bikerack/sprint_panel.py +373 -142
- package/pennyfarthing_scripts/bikerack/story_detail_data.py +247 -0
- package/pennyfarthing_scripts/bikerack/story_detail_screen.py +177 -0
- package/pennyfarthing_scripts/bikerack/tui.py +304 -62
- package/pennyfarthing_scripts/bikerack/ws_client.py +2 -2
- package/pennyfarthing_scripts/cli.py +5 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +29 -2
- package/pennyfarthing_scripts/common/pr_config.py +38 -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/cli.py +3 -3
- package/pennyfarthing_scripts/context.py +3 -3
- 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__/repos.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/hooks_installer.py +2 -3
- package/pennyfarthing_scripts/git/status_all.py +1 -1
- package/pennyfarthing_scripts/git/worktree.py +2 -2
- package/pennyfarthing_scripts/git_group/__pycache__/cli.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__/marker.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/phase_check.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 +28 -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 +13 -1
- package/pennyfarthing_scripts/hooks/__init__.py +442 -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 +214 -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 +77 -0
- package/pennyfarthing_scripts/hooks/reflector_check.py +270 -0
- package/pennyfarthing_scripts/hooks/schema_validation.py +202 -0
- package/pennyfarthing_scripts/hooks/session_start.py +294 -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 +429 -0
- package/pennyfarthing_scripts/hooks.py +27 -432
- package/pennyfarthing_scripts/pretooluse_hook.py +3 -185
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/heatmap.py +3 -15
- package/pennyfarthing_scripts/prime/workflow.py +2 -1
- package/pennyfarthing_scripts/schema_validation_hook.py +3 -298
- package/pennyfarthing_scripts/session_start_hook.py +4 -186
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.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/cli.py +121 -0
- package/pennyfarthing_scripts/sprint/loader.py +154 -3
- package/pennyfarthing_scripts/sprint/story_update.py +26 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.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_workflow_list_team.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_bikerack.py +26 -26
- package/pennyfarthing_scripts/tests/test_dialogue_manager.py +0 -1
- package/pennyfarthing_scripts/tests/test_sprint_panel.py +344 -265
- package/pennyfarthing_scripts/tests/test_workflow_list_team.py +147 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/tandem_awareness.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/team_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/team_mode.py +323 -0
- package/pennyfarthing_scripts/validate/adapters/workflow.py +19 -0
- package/pennyfarthing_scripts/welcome_hook.py +3 -149
- 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/__pycache__/team_lifecycle.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/cli.py +22 -20
- package/pennyfarthing_scripts/workflow/state.py +0 -1
- package/pennyfarthing_scripts/workflow/team_lifecycle.py +256 -0
- package/packages/core/dist/cli/cyclist-migration.test.d.ts +0 -16
- package/packages/core/dist/cli/cyclist-migration.test.d.ts.map +0 -1
- package/packages/core/dist/cli/cyclist-migration.test.js +0 -229
- package/packages/core/dist/cli/cyclist-migration.test.js.map +0 -1
- package/packages/core/dist/scripts/theme-detail.test.d.ts +0 -10
- package/packages/core/dist/scripts/theme-detail.test.d.ts.map +0 -1
- package/packages/core/dist/scripts/theme-detail.test.js +0 -199
- package/packages/core/dist/scripts/theme-detail.test.js.map +0 -1
|
@@ -10,6 +10,7 @@ Panel navigation: Mount all panels, tab bar, keyboard switching, command palette
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import os
|
|
13
14
|
from functools import partial
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Any
|
|
@@ -17,16 +18,20 @@ from typing import Any
|
|
|
17
18
|
from textual.app import App, ComposeResult
|
|
18
19
|
from textual.binding import Binding
|
|
19
20
|
from textual.command import Hit, Hits, Provider
|
|
20
|
-
from textual.containers import VerticalScroll
|
|
21
|
+
from textual.containers import Horizontal, VerticalScroll
|
|
22
|
+
from textual.message import Message
|
|
21
23
|
from textual.reactive import reactive
|
|
22
|
-
from textual.widgets import Footer, Header, Static
|
|
24
|
+
from textual.widgets import Footer, Header, Static, Tab, Tabs
|
|
23
25
|
|
|
24
26
|
from pennyfarthing_scripts.bc.focus import get_last_panel, save_last_panel
|
|
27
|
+
from pennyfarthing_scripts.bikerack.audit_log_panel import AuditLogPanel
|
|
25
28
|
from pennyfarthing_scripts.bikerack.background_panel import BackgroundPanel
|
|
26
29
|
from pennyfarthing_scripts.bikerack.base_panel import get_panel_icon
|
|
27
30
|
from pennyfarthing_scripts.bikerack.changed_panel import ChangedPanel
|
|
31
|
+
from pennyfarthing_scripts.bikerack.context_meter_footer import ContextMeterFooter
|
|
28
32
|
from pennyfarthing_scripts.bikerack.debug_panel import DebugPanel
|
|
29
33
|
from pennyfarthing_scripts.bikerack.diffs_panel import DiffsPanel
|
|
34
|
+
from pennyfarthing_scripts.bikerack.events import NavigateToFile
|
|
30
35
|
from pennyfarthing_scripts.bikerack.git_panel import GitPanel
|
|
31
36
|
from pennyfarthing_scripts.bikerack.progress_panel import ProgressPanel
|
|
32
37
|
from pennyfarthing_scripts.bikerack.sprint_panel import SprintPanel
|
|
@@ -76,6 +81,7 @@ PANEL_REGISTRY: list[tuple[str, str]] = [
|
|
|
76
81
|
("diffs", "Diffs"),
|
|
77
82
|
("changed", "Changed"),
|
|
78
83
|
("background", "Background"),
|
|
84
|
+
("audit-log", "Audit Log"),
|
|
79
85
|
("debug", "Debug"),
|
|
80
86
|
("progress", "Progress"),
|
|
81
87
|
]
|
|
@@ -101,38 +107,69 @@ PANEL_DISPLAY_NAMES: dict[str, str] = {
|
|
|
101
107
|
_PANEL_KEYS = [key for key, _ in PANEL_REGISTRY]
|
|
102
108
|
|
|
103
109
|
|
|
104
|
-
class
|
|
105
|
-
"""
|
|
110
|
+
class BindingFooter(Footer):
|
|
111
|
+
"""Footer subclass that exposes active binding text via render().
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
Textual's Footer uses compose() for visual content, so render() returns
|
|
114
|
+
Blank. This override makes binding descriptions available through
|
|
115
|
+
str(footer.render()) for programmatic inspection.
|
|
116
|
+
"""
|
|
108
117
|
|
|
109
|
-
def
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
def render(self) -> Any:
|
|
119
|
+
try:
|
|
120
|
+
bindings = self.screen.active_bindings
|
|
121
|
+
parts: list[str] = []
|
|
122
|
+
for _, binding, _enabled, _tooltip in bindings.values():
|
|
123
|
+
if binding.show:
|
|
124
|
+
parts.append(f"{binding.key}:{binding.description}")
|
|
125
|
+
if parts:
|
|
126
|
+
return " ".join(parts)
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
return super().render()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _build_panel_tabs() -> list[Tab]:
|
|
133
|
+
"""Build Tab widgets for each panel in the registry."""
|
|
134
|
+
tabs: list[Tab] = []
|
|
135
|
+
for panel_key, display_name in PANEL_REGISTRY:
|
|
136
|
+
icon = get_panel_icon(panel_key)
|
|
137
|
+
label = f"{icon} {display_name}" if icon else display_name
|
|
138
|
+
tabs.append(Tab(label, id=f"tab-{panel_key}"))
|
|
139
|
+
return tabs
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
PORTRAIT_SKELETON = """\
|
|
143
|
+
[dim]┌────────┐
|
|
144
|
+
│░░░░░░░░│
|
|
145
|
+
│░░░▓▓░░░│
|
|
146
|
+
│░░░░░░░░│
|
|
147
|
+
└────────┘[/dim]"""
|
|
127
148
|
|
|
128
149
|
|
|
129
150
|
class AgentHeader(Static):
|
|
130
|
-
"""Displays current agent persona from WheelHub /ws/persona channel.
|
|
151
|
+
"""Displays current agent persona from WheelHub /ws/persona channel.
|
|
152
|
+
|
|
153
|
+
When a portrait image is available (resolved locally or provided via
|
|
154
|
+
portraitPath in persona data), mounts a Horizontal layout container.
|
|
155
|
+
Shows a skeleton placeholder while the image loads.
|
|
156
|
+
Falls back to text-only when no portrait is found.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
class PortraitLayoutUpdate(Message):
|
|
160
|
+
"""Internal message to update portrait layout asynchronously."""
|
|
161
|
+
|
|
162
|
+
def __init__(self, portrait_path: Path | None) -> None:
|
|
163
|
+
super().__init__()
|
|
164
|
+
self.has_portrait = portrait_path is not None
|
|
165
|
+
self.portrait_path = portrait_path
|
|
131
166
|
|
|
132
167
|
def __init__(self, **kwargs: Any) -> None:
|
|
133
168
|
super().__init__(**kwargs)
|
|
134
169
|
self._is_streaming: bool = False
|
|
135
170
|
self._persona_data: dict[str, Any] = {}
|
|
171
|
+
self._header_text: str = ""
|
|
172
|
+
self._current_portrait: Path | None = None
|
|
136
173
|
|
|
137
174
|
def _apply_persona(self, data: dict[str, Any]) -> None:
|
|
138
175
|
"""Render persona data into the header."""
|
|
@@ -145,6 +182,21 @@ class AgentHeader(Static):
|
|
|
145
182
|
self._is_streaming = bool(data.get("isStreaming", False))
|
|
146
183
|
self._render_header()
|
|
147
184
|
|
|
185
|
+
def _resolve_portrait(self, data: dict[str, Any]) -> Path | None:
|
|
186
|
+
"""Get portrait path from persona data or resolve locally."""
|
|
187
|
+
portrait_path = data.get("portraitPath")
|
|
188
|
+
if portrait_path:
|
|
189
|
+
p = Path(portrait_path)
|
|
190
|
+
if p.exists():
|
|
191
|
+
return p
|
|
192
|
+
theme = data.get("theme", "")
|
|
193
|
+
role = data.get("role", "")
|
|
194
|
+
if theme and role:
|
|
195
|
+
from pennyfarthing_scripts.bikerack import portrait_resolver
|
|
196
|
+
|
|
197
|
+
return portrait_resolver.resolve_portrait_path(theme, role)
|
|
198
|
+
return None
|
|
199
|
+
|
|
148
200
|
def _render_header(self) -> None:
|
|
149
201
|
"""Re-render the header from stored state."""
|
|
150
202
|
data = self._persona_data
|
|
@@ -152,20 +204,20 @@ class AgentHeader(Static):
|
|
|
152
204
|
role = data.get("role", "")
|
|
153
205
|
role_desc = data.get("roleDescription", "")
|
|
154
206
|
quote = data.get("quote", "")
|
|
155
|
-
style = data.get("style", "")
|
|
156
207
|
theme = data.get("theme", "")
|
|
157
208
|
|
|
158
209
|
if not char:
|
|
159
210
|
self.update("[dim]Waiting for agent...[/dim]")
|
|
211
|
+
self.post_message(self.PortraitLayoutUpdate(portrait_path=None))
|
|
160
212
|
return
|
|
161
213
|
|
|
162
214
|
parts: list[str] = []
|
|
163
215
|
|
|
164
|
-
# Role badge
|
|
216
|
+
# Role badge — escape brackets so Rich doesn't eat them as tags
|
|
165
217
|
if role:
|
|
166
218
|
abbrev = AGENT_ABBREV.get(role, role.upper()[:3])
|
|
167
219
|
color = AGENT_ROLE_COLORS.get(role, "bright_magenta")
|
|
168
|
-
parts.append(f"[bold {color}][{abbrev}][/bold {color}]")
|
|
220
|
+
parts.append(f"[bold {color}]\\[{abbrev}][/bold {color}]")
|
|
169
221
|
|
|
170
222
|
# Character name
|
|
171
223
|
parts.append(f"[bold]{char}[/bold]")
|
|
@@ -173,6 +225,7 @@ class AgentHeader(Static):
|
|
|
173
225
|
# Theme name
|
|
174
226
|
if theme:
|
|
175
227
|
from pennyfarthing_scripts.bikerack.base_panel import humanize_theme
|
|
228
|
+
|
|
176
229
|
parts.append(f"[dim]{humanize_theme(theme)}[/dim]")
|
|
177
230
|
|
|
178
231
|
# Streaming indicator
|
|
@@ -181,17 +234,87 @@ class AgentHeader(Static):
|
|
|
181
234
|
|
|
182
235
|
line = " ".join(parts)
|
|
183
236
|
|
|
184
|
-
#
|
|
185
|
-
if role_desc:
|
|
186
|
-
line += f"\n[dim]{role_desc}[/dim]"
|
|
187
|
-
elif style:
|
|
188
|
-
line += f"\n[dim]{style}[/dim]"
|
|
189
|
-
|
|
190
|
-
# Quote
|
|
237
|
+
# Catchphrase subtitle (quote is a random catchphrase from the theme)
|
|
191
238
|
if quote:
|
|
192
239
|
line += f"\n[italic dim]\"{quote}\"[/italic dim]"
|
|
240
|
+
elif role_desc:
|
|
241
|
+
line += f"\n[dim]{role_desc}[/dim]"
|
|
242
|
+
|
|
243
|
+
self._header_text = line
|
|
244
|
+
|
|
245
|
+
# Check portrait and schedule layout update
|
|
246
|
+
portrait = self._resolve_portrait(data)
|
|
247
|
+
self.post_message(self.PortraitLayoutUpdate(portrait_path=portrait))
|
|
248
|
+
|
|
249
|
+
async def on_agent_header_portrait_layout_update(
|
|
250
|
+
self, event: PortraitLayoutUpdate
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Mount or remove Horizontal portrait layout with text beside image.
|
|
193
253
|
|
|
194
|
-
|
|
254
|
+
Shows a skeleton placeholder immediately while the real image loads
|
|
255
|
+
to avoid a visible blank gap during image decode/render.
|
|
256
|
+
"""
|
|
257
|
+
if event.has_portrait and event.portrait_path:
|
|
258
|
+
if self._current_portrait == event.portrait_path:
|
|
259
|
+
# Same portrait — just update text label if it exists
|
|
260
|
+
try:
|
|
261
|
+
text_widget = self.query_one("#agent-text", Static)
|
|
262
|
+
text_widget.update(self._header_text)
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
# New portrait or first time — full layout rebuild
|
|
268
|
+
for child in list(self.query("Horizontal")):
|
|
269
|
+
await child.remove()
|
|
270
|
+
|
|
271
|
+
# Mount skeleton + text immediately so there's no blank gap
|
|
272
|
+
self.update("")
|
|
273
|
+
self._current_portrait = event.portrait_path
|
|
274
|
+
skeleton = Static(PORTRAIT_SKELETON, id="portrait-skeleton")
|
|
275
|
+
text = Static(self._header_text, id="agent-text")
|
|
276
|
+
row = Horizontal(skeleton, text, id="portrait-row")
|
|
277
|
+
await self.mount(row)
|
|
278
|
+
|
|
279
|
+
# Now try to load the real image and swap it in
|
|
280
|
+
try:
|
|
281
|
+
from pennyfarthing_scripts.bikerack.portrait_resolver import (
|
|
282
|
+
detect_image_protocol,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
protocol = detect_image_protocol()
|
|
286
|
+
if protocol is None:
|
|
287
|
+
# No image protocol — remove skeleton, fall back to text-only
|
|
288
|
+
for child in list(self.query("Horizontal")):
|
|
289
|
+
await child.remove()
|
|
290
|
+
self.update(self._header_text)
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
if protocol == "kitty":
|
|
294
|
+
from textual_image.widget import TGPImage as ImageWidget
|
|
295
|
+
elif protocol == "sixel":
|
|
296
|
+
from textual_image.widget import SixelImage as ImageWidget
|
|
297
|
+
else:
|
|
298
|
+
from textual_image.widget import HalfcellImage as ImageWidget
|
|
299
|
+
|
|
300
|
+
img = ImageWidget(str(event.portrait_path), id="portrait-img")
|
|
301
|
+
try:
|
|
302
|
+
skel = self.query_one("#portrait-skeleton")
|
|
303
|
+
await skel.remove()
|
|
304
|
+
except Exception:
|
|
305
|
+
pass
|
|
306
|
+
await row.mount(img, before=0)
|
|
307
|
+
except (ImportError, Exception):
|
|
308
|
+
# textual-image not available — remove skeleton, text-only
|
|
309
|
+
for child in list(self.query("Horizontal")):
|
|
310
|
+
await child.remove()
|
|
311
|
+
self.update(self._header_text)
|
|
312
|
+
else:
|
|
313
|
+
# No portrait — text-only
|
|
314
|
+
for child in list(self.query("Horizontal")):
|
|
315
|
+
await child.remove()
|
|
316
|
+
self._current_portrait = None
|
|
317
|
+
self.update(self._header_text)
|
|
195
318
|
|
|
196
319
|
|
|
197
320
|
class ConnectionStatus(Static):
|
|
@@ -227,20 +350,69 @@ class PanelCommands(Provider):
|
|
|
227
350
|
class BikeRackApp(App):
|
|
228
351
|
"""BikeRack TUI application shell."""
|
|
229
352
|
|
|
353
|
+
class PersonaUpdate(Message, bubble=False):
|
|
354
|
+
"""Persona data from WS — routed through Textual message system."""
|
|
355
|
+
|
|
356
|
+
def __init__(self, data: dict[str, Any]) -> None:
|
|
357
|
+
super().__init__()
|
|
358
|
+
self.data = data
|
|
359
|
+
|
|
360
|
+
class FocusUpdate(Message, bubble=False):
|
|
361
|
+
"""Focus change from WS — routed through Textual message system."""
|
|
362
|
+
|
|
363
|
+
def __init__(self, focus: str | None) -> None:
|
|
364
|
+
super().__init__()
|
|
365
|
+
self.focus = focus
|
|
366
|
+
|
|
367
|
+
class WsStateUpdate(Message, bubble=False):
|
|
368
|
+
"""WS connection state change — routed through Textual message system."""
|
|
369
|
+
|
|
370
|
+
def __init__(self, state: ConnectionState) -> None:
|
|
371
|
+
super().__init__()
|
|
372
|
+
self.state = state
|
|
373
|
+
|
|
230
374
|
TITLE = "BikeRack"
|
|
231
375
|
|
|
232
376
|
CSS = """
|
|
233
377
|
#agent-header {
|
|
234
378
|
height: auto;
|
|
235
|
-
max-height:
|
|
379
|
+
max-height: 7;
|
|
236
380
|
padding: 0 1;
|
|
381
|
+
border-bottom: solid $accent;
|
|
382
|
+
}
|
|
383
|
+
#portrait-row {
|
|
384
|
+
height: 5;
|
|
385
|
+
width: 100%;
|
|
386
|
+
}
|
|
387
|
+
#portrait-img {
|
|
388
|
+
width: 10;
|
|
389
|
+
height: 5;
|
|
390
|
+
margin: 0 1 0 0;
|
|
237
391
|
}
|
|
238
|
-
#
|
|
392
|
+
#agent-text {
|
|
393
|
+
height: auto;
|
|
394
|
+
width: 1fr;
|
|
395
|
+
}
|
|
396
|
+
#project-dir {
|
|
239
397
|
height: 1;
|
|
398
|
+
padding: 0 1;
|
|
399
|
+
color: $text-muted;
|
|
400
|
+
}
|
|
401
|
+
Tabs {
|
|
402
|
+
dock: top;
|
|
403
|
+
}
|
|
404
|
+
Tab.-active {
|
|
405
|
+
color: $text;
|
|
406
|
+
}
|
|
407
|
+
Tab {
|
|
408
|
+
color: $text-muted;
|
|
240
409
|
}
|
|
241
410
|
#connection-status {
|
|
242
411
|
height: 1;
|
|
243
412
|
}
|
|
413
|
+
ContextMeterFooter {
|
|
414
|
+
height: 1;
|
|
415
|
+
}
|
|
244
416
|
"""
|
|
245
417
|
|
|
246
418
|
COMMANDS = App.COMMANDS | {PanelCommands}
|
|
@@ -252,8 +424,9 @@ class BikeRackApp(App):
|
|
|
252
424
|
Binding("3", "switch_panel('diffs')", "Diffs", show=False),
|
|
253
425
|
Binding("4", "switch_panel('changed')", "Changed", show=False),
|
|
254
426
|
Binding("5", "switch_panel('background')", "Background", show=False),
|
|
255
|
-
Binding("6", "switch_panel('
|
|
256
|
-
Binding("7", "switch_panel('
|
|
427
|
+
Binding("6", "switch_panel('audit-log')", "Audit Log", show=False),
|
|
428
|
+
Binding("7", "switch_panel('debug')", "Debug", show=False),
|
|
429
|
+
Binding("8", "switch_panel('progress')", "Progress", show=False),
|
|
257
430
|
Binding("bracketright", "next_panel", "]Next"),
|
|
258
431
|
Binding("bracketleft", "prev_panel", "[Prev"),
|
|
259
432
|
Binding("tab", "next_panel", show=False),
|
|
@@ -265,16 +438,25 @@ class BikeRackApp(App):
|
|
|
265
438
|
Binding("e", "toggle_epic", show=False),
|
|
266
439
|
]
|
|
267
440
|
|
|
441
|
+
def _get_dom_base(self):
|
|
442
|
+
"""Query the active screen so app.query() finds pushed screen widgets."""
|
|
443
|
+
return self.screen
|
|
444
|
+
|
|
268
445
|
def __init__(self, client=None, **kwargs):
|
|
269
446
|
super().__init__(**kwargs)
|
|
270
447
|
self._client = client
|
|
271
448
|
self._focused_panel: str = "sprint"
|
|
272
449
|
self._previous_panel: str | None = None
|
|
450
|
+
self._programmatic_tab_count: int = 0
|
|
273
451
|
|
|
274
452
|
def compose(self) -> ComposeResult:
|
|
453
|
+
project_dir_name = Path(
|
|
454
|
+
os.environ.get("CYCLIST_PROJECT_DIR", os.getcwd())
|
|
455
|
+
).name
|
|
275
456
|
yield Header()
|
|
276
457
|
yield AgentHeader(id="agent-header")
|
|
277
|
-
yield
|
|
458
|
+
yield Static(f"[dim]{project_dir_name}[/dim]", id="project-dir")
|
|
459
|
+
yield Tabs(*_build_panel_tabs(), id="tab-bar")
|
|
278
460
|
yield ConnectionStatus(
|
|
279
461
|
STATE_DISPLAY[ConnectionState.DISCONNECTED],
|
|
280
462
|
id="connection-status",
|
|
@@ -285,9 +467,11 @@ class BikeRackApp(App):
|
|
|
285
467
|
yield DiffsPanel(client=self._client, id="panel-diffs")
|
|
286
468
|
yield ChangedPanel(client=self._client, id="panel-changed")
|
|
287
469
|
yield BackgroundPanel(client=self._client, id="panel-background")
|
|
470
|
+
yield AuditLogPanel(client=self._client, id="panel-audit-log")
|
|
288
471
|
yield DebugPanel(client=self._client, id="panel-debug")
|
|
289
472
|
yield ProgressPanel(client=self._client, id="panel-progress")
|
|
290
|
-
yield
|
|
473
|
+
yield ContextMeterFooter(client=self._client)
|
|
474
|
+
yield BindingFooter()
|
|
291
475
|
|
|
292
476
|
async def on_mount(self) -> None:
|
|
293
477
|
# Restore last panel or default to sprint
|
|
@@ -309,8 +493,13 @@ class BikeRackApp(App):
|
|
|
309
493
|
except Exception:
|
|
310
494
|
pass
|
|
311
495
|
|
|
312
|
-
# Set tab bar active state
|
|
496
|
+
# Set tab bar active state and focus initial panel
|
|
313
497
|
self._update_tab_bar(initial)
|
|
498
|
+
try:
|
|
499
|
+
initial_widget = self.query_one(f"#panel-{initial}")
|
|
500
|
+
initial_widget.focus()
|
|
501
|
+
except Exception:
|
|
502
|
+
pass
|
|
314
503
|
|
|
315
504
|
if self._client is not None:
|
|
316
505
|
self._client.on_state_change(self._on_ws_state_change)
|
|
@@ -318,6 +507,15 @@ class BikeRackApp(App):
|
|
|
318
507
|
self._client.subscribe("persona", self._handle_persona_message)
|
|
319
508
|
self.run_worker(self._client.connect(), exclusive=True, name="ws-client")
|
|
320
509
|
|
|
510
|
+
def on_navigate_to_file(self, event: NavigateToFile) -> None:
|
|
511
|
+
"""Handle NavigateToFile — switch to diffs and navigate to file."""
|
|
512
|
+
self.action_switch_panel("diffs")
|
|
513
|
+
try:
|
|
514
|
+
diffs = self.query_one("#panel-diffs", DiffsPanel)
|
|
515
|
+
diffs.navigate_to_file(event.path)
|
|
516
|
+
except Exception:
|
|
517
|
+
pass
|
|
518
|
+
|
|
321
519
|
def action_switch_panel(self, key: str) -> None:
|
|
322
520
|
"""Switch to a panel by key."""
|
|
323
521
|
if key not in _PANEL_KEYS:
|
|
@@ -332,10 +530,11 @@ class BikeRackApp(App):
|
|
|
332
530
|
except Exception:
|
|
333
531
|
pass
|
|
334
532
|
|
|
335
|
-
# Show target panel
|
|
533
|
+
# Show target panel and focus it
|
|
336
534
|
try:
|
|
337
535
|
target = self.query_one(f"#panel-{key}")
|
|
338
536
|
target.display = True
|
|
537
|
+
target.focus()
|
|
339
538
|
except Exception:
|
|
340
539
|
pass
|
|
341
540
|
|
|
@@ -408,18 +607,40 @@ class BikeRackApp(App):
|
|
|
408
607
|
pass
|
|
409
608
|
|
|
410
609
|
def _update_tab_bar(self, panel_key: str) -> None:
|
|
411
|
-
"""Update the tab bar widget with the given panel key.
|
|
610
|
+
"""Update the tab bar widget with the given panel key.
|
|
611
|
+
|
|
612
|
+
Increments _programmatic_tab_count so the async TabActivated
|
|
613
|
+
handler knows to ignore the event (prevents infinite ping-pong).
|
|
614
|
+
"""
|
|
412
615
|
try:
|
|
413
|
-
tab_bar = self.query_one("#tab-bar",
|
|
414
|
-
|
|
616
|
+
tab_bar = self.query_one("#tab-bar", Tabs)
|
|
617
|
+
tab_id = f"tab-{panel_key}"
|
|
618
|
+
if tab_bar.active != tab_id:
|
|
619
|
+
self._programmatic_tab_count += 1
|
|
620
|
+
tab_bar.active = tab_id
|
|
415
621
|
except Exception:
|
|
416
622
|
pass
|
|
417
623
|
|
|
624
|
+
def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
|
|
625
|
+
"""Handle tab activation from the Tabs widget.
|
|
626
|
+
|
|
627
|
+
Programmatic tabs.active changes fire TabActivated asynchronously.
|
|
628
|
+
We use a counter to skip those and only react to genuine user clicks.
|
|
629
|
+
"""
|
|
630
|
+
if self._programmatic_tab_count > 0:
|
|
631
|
+
self._programmatic_tab_count -= 1
|
|
632
|
+
return
|
|
633
|
+
tab_id = event.tab.id or ""
|
|
634
|
+
panel_key = tab_id.removeprefix("tab-")
|
|
635
|
+
if panel_key in _PANEL_KEYS and panel_key != self._focused_panel:
|
|
636
|
+
self.action_switch_panel(panel_key)
|
|
637
|
+
|
|
418
638
|
def _handle_focus_message(self, message: dict[str, Any] | None) -> None:
|
|
419
639
|
"""Handle incoming focus channel messages.
|
|
420
640
|
|
|
421
641
|
Expected format: {type: 'init'|'update', focus: '<panel>'|null}
|
|
422
642
|
Only 'update' messages trigger panel switches (matching React hook).
|
|
643
|
+
Routes through Textual message system via post_message for proper repaint.
|
|
423
644
|
"""
|
|
424
645
|
if message is None or not isinstance(message, dict):
|
|
425
646
|
return
|
|
@@ -427,31 +648,47 @@ class BikeRackApp(App):
|
|
|
427
648
|
return
|
|
428
649
|
if "focus" not in message:
|
|
429
650
|
return
|
|
430
|
-
|
|
431
|
-
focus = message["focus"]
|
|
432
|
-
if focus is not None and focus in _PANEL_KEYS:
|
|
433
|
-
self.action_switch_panel(focus)
|
|
434
|
-
elif focus is not None:
|
|
435
|
-
# Panel exists in display names but not implemented — just update state
|
|
436
|
-
self._previous_panel = self._focused_panel
|
|
437
|
-
self._focused_panel = focus
|
|
438
|
-
save_last_panel(focus, project_dir=None)
|
|
651
|
+
self.post_message(self.FocusUpdate(message["focus"]))
|
|
439
652
|
|
|
440
653
|
def _handle_persona_message(self, message: dict[str, Any] | None) -> None:
|
|
441
|
-
"""Handle incoming persona channel messages.
|
|
654
|
+
"""Handle incoming persona channel messages.
|
|
655
|
+
|
|
656
|
+
Routes through Textual message system via post_message for proper repaint.
|
|
657
|
+
"""
|
|
442
658
|
if message is None or not isinstance(message, dict):
|
|
443
659
|
return
|
|
660
|
+
self.post_message(self.PersonaUpdate(message))
|
|
661
|
+
|
|
662
|
+
def _on_ws_state_change(self, state: ConnectionState) -> None:
|
|
663
|
+
"""Handle WheelHub connection state changes.
|
|
664
|
+
|
|
665
|
+
Routes through Textual message system via post_message for proper repaint.
|
|
666
|
+
"""
|
|
667
|
+
self.post_message(self.WsStateUpdate(state))
|
|
668
|
+
|
|
669
|
+
def on_bike_rack_app_persona_update(self, event: PersonaUpdate) -> None:
|
|
670
|
+
"""Apply persona data in Textual message context."""
|
|
444
671
|
try:
|
|
445
672
|
header = self.query_one("#agent-header", AgentHeader)
|
|
446
|
-
header._apply_persona(
|
|
673
|
+
header._apply_persona(event.data)
|
|
447
674
|
except Exception:
|
|
448
675
|
pass
|
|
449
676
|
|
|
450
|
-
def
|
|
451
|
-
"""
|
|
677
|
+
def on_bike_rack_app_focus_update(self, event: FocusUpdate) -> None:
|
|
678
|
+
"""Apply focus change in Textual message context."""
|
|
679
|
+
focus = event.focus
|
|
680
|
+
if focus is not None and focus in _PANEL_KEYS:
|
|
681
|
+
self.action_switch_panel(focus)
|
|
682
|
+
elif focus is not None:
|
|
683
|
+
self._previous_panel = self._focused_panel
|
|
684
|
+
self._focused_panel = focus
|
|
685
|
+
save_last_panel(focus, project_dir=None)
|
|
686
|
+
|
|
687
|
+
def on_bike_rack_app_ws_state_update(self, event: WsStateUpdate) -> None:
|
|
688
|
+
"""Apply connection state in Textual message context."""
|
|
452
689
|
try:
|
|
453
690
|
widget = self.query_one("#connection-status", ConnectionStatus)
|
|
454
|
-
widget.connection_state = state
|
|
691
|
+
widget.connection_state = event.state
|
|
455
692
|
except Exception:
|
|
456
693
|
pass
|
|
457
694
|
|
|
@@ -466,12 +703,17 @@ def main(
|
|
|
466
703
|
"""Launch BikeRack TUI as a standalone application.
|
|
467
704
|
|
|
468
705
|
Args:
|
|
469
|
-
port: Explicit WheelHub port. If None, reads from .
|
|
706
|
+
port: Explicit WheelHub port. If None, reads from .bikerack-port file.
|
|
470
707
|
project_dir: Project directory for port file discovery. Defaults to cwd.
|
|
471
708
|
"""
|
|
709
|
+
# Detect terminal image protocol BEFORE App.run() claims the terminal
|
|
710
|
+
from pennyfarthing_scripts.bikerack import portrait_resolver
|
|
711
|
+
|
|
712
|
+
portrait_resolver.detect_image_protocol()
|
|
713
|
+
|
|
472
714
|
if port is None:
|
|
473
715
|
if project_dir is not None:
|
|
474
|
-
port_file = project_dir / ".
|
|
716
|
+
port_file = project_dir / ".bikerack-port"
|
|
475
717
|
if port_file.exists():
|
|
476
718
|
try:
|
|
477
719
|
port = int(port_file.read_text().strip())
|
|
@@ -75,14 +75,14 @@ class WheelHubClient:
|
|
|
75
75
|
cb(new_state)
|
|
76
76
|
|
|
77
77
|
def discover_port(self) -> int:
|
|
78
|
-
"""Read port from .
|
|
78
|
+
"""Read port from .bikerack-port file, fallback to DEFAULT_PORT.
|
|
79
79
|
|
|
80
80
|
Priority: explicit port > port file > DEFAULT_PORT.
|
|
81
81
|
"""
|
|
82
82
|
if self._port is not None:
|
|
83
83
|
return self._port
|
|
84
84
|
if self._project_dir is not None:
|
|
85
|
-
port_file = self._project_dir / ".
|
|
85
|
+
port_file = self._project_dir / ".bikerack-port"
|
|
86
86
|
if port_file.exists():
|
|
87
87
|
try:
|
|
88
88
|
return int(port_file.read_text().strip())
|
|
@@ -142,6 +142,11 @@ from pennyfarthing_scripts.consultation.cli import consultation # noqa: E402
|
|
|
142
142
|
|
|
143
143
|
cli.add_command(consultation)
|
|
144
144
|
|
|
145
|
+
# Import and register hooks group
|
|
146
|
+
from pennyfarthing_scripts.hooks.cli import hooks # noqa: E402
|
|
147
|
+
|
|
148
|
+
cli.add_command(hooks)
|
|
149
|
+
|
|
145
150
|
|
|
146
151
|
@cli.group()
|
|
147
152
|
def agent():
|
|
Binary file
|
|
@@ -82,11 +82,38 @@ def load_yaml_config(path: Path) -> dict[str, Any] | None:
|
|
|
82
82
|
return yaml.safe_load(f)
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def load_pennyfarthing_config() -> dict[str, Any]:
|
|
85
|
+
def load_pennyfarthing_config(project_root: Path | None = None) -> dict[str, Any]:
|
|
86
86
|
"""Load .pennyfarthing/config.local.yaml.
|
|
87
87
|
|
|
88
|
+
Args:
|
|
89
|
+
project_root: Project root path (defaults to auto-detect)
|
|
90
|
+
|
|
88
91
|
Returns:
|
|
89
92
|
Config dict, or empty dict if not found
|
|
90
93
|
"""
|
|
91
|
-
|
|
94
|
+
root = project_root or get_project_root()
|
|
95
|
+
config_path = root / ".pennyfarthing" / "config.local.yaml"
|
|
92
96
|
return load_yaml_config(config_path) or {}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def save_pennyfarthing_config_key(
|
|
100
|
+
key: str, value: Any, project_root: Path | None = None
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Set a top-level key in .pennyfarthing/config.local.yaml.
|
|
103
|
+
|
|
104
|
+
Creates the file if it doesn't exist. Preserves existing keys.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
key: Top-level key (e.g., "sprint")
|
|
108
|
+
value: Value to set (dict, str, etc.)
|
|
109
|
+
project_root: Project root path (defaults to auto-detect)
|
|
110
|
+
"""
|
|
111
|
+
root = project_root or get_project_root()
|
|
112
|
+
config_path = root / ".pennyfarthing" / "config.local.yaml"
|
|
113
|
+
|
|
114
|
+
config = load_yaml_config(config_path) or {}
|
|
115
|
+
config[key] = value
|
|
116
|
+
|
|
117
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
with open(config_path, "w") as f:
|
|
119
|
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""PR mode configuration reader.
|
|
2
|
+
|
|
3
|
+
Reads the pr_mode preference from .pennyfarthing/config.local.yaml.
|
|
4
|
+
|
|
5
|
+
Values: draft | ready | none (default: draft)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pennyfarthing_scripts.common.config import load_pennyfarthing_config
|
|
11
|
+
|
|
12
|
+
VALID_PR_MODES = {"draft", "ready", "none"}
|
|
13
|
+
DEFAULT_PR_MODE = "draft"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_pr_mode() -> str:
|
|
17
|
+
"""Read pr_mode from pennyfarthing config.
|
|
18
|
+
|
|
19
|
+
Looks for workflow.pr_mode in .pennyfarthing/config.local.yaml.
|
|
20
|
+
Falls back to 'draft' if not set or invalid.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
One of: 'draft', 'ready', 'none'
|
|
24
|
+
"""
|
|
25
|
+
config = load_pennyfarthing_config()
|
|
26
|
+
workflow = config.get("workflow", {})
|
|
27
|
+
if not isinstance(workflow, dict):
|
|
28
|
+
return DEFAULT_PR_MODE
|
|
29
|
+
|
|
30
|
+
mode = workflow.get("pr_mode", DEFAULT_PR_MODE)
|
|
31
|
+
if mode not in VALID_PR_MODES:
|
|
32
|
+
return DEFAULT_PR_MODE
|
|
33
|
+
|
|
34
|
+
return mode
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
print(get_pr_mode())
|