@pennyfarthing/core 11.2.0 → 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 +1 -1
- 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/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/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/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 +6 -10
- package/pennyfarthing-dist/agents/reviewer.md +8 -2
- package/pennyfarthing-dist/agents/sm-finish.md +18 -1
- package/pennyfarthing-dist/commands/pf-git.md +4 -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/bell-mode.md +1 -1
- package/pennyfarthing-dist/guides/hooks.md +28 -28
- package/pennyfarthing-dist/guides/reflector.md +1 -1
- package/pennyfarthing-dist/guides/tandem-protocol.md +3 -3
- package/pennyfarthing-dist/scripts/core/check-context.sh +2 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +5 -87
- package/pennyfarthing-dist/scripts/hooks/README.md +5 -5
- 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/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/templates/settings.local.json.template +19 -10
- package/pennyfarthing-dist/workflows/tdd.yaml +11 -2
- package/pennyfarthing_scripts/CLAUDE.md +19 -10
- 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/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/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/portrait_resolver.py +139 -0
- package/pennyfarthing_scripts/bikerack/sprint_panel.py +373 -142
- 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 +293 -61
- 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 +5 -0
- 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/__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/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/__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_group/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
- 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 +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/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 -432
- 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/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/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_sprint_panel.py +344 -265
- 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/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/cli.py +7 -6
- 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_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
- 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/git/__pycache__/hooks_installer.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/heatmap.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
|
@@ -1,309 +1,25 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
1
|
"""
|
|
3
|
-
|
|
2
|
+
Backward-compatibility shim — bell mode hook moved to hooks/bell_mode.py.
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
injection systems:
|
|
7
|
-
|
|
8
|
-
1. Bell queue (Cyclist only) — injects queued user messages when Cyclist
|
|
9
|
-
is running and bell_mode is enabled. In CLI sessions this is a no-op.
|
|
10
|
-
2. Tandem observations (always active) — injects backseat agent observations
|
|
11
|
-
when tandem observation files exist. No configuration required.
|
|
12
|
-
|
|
13
|
-
Bell queue takes precedence: if a queued message exists, tandem is
|
|
14
|
-
deferred to the next hook invocation.
|
|
15
|
-
|
|
16
|
-
Story: MSSCI-12409 - Hook consistency and WheelHub consolidation
|
|
4
|
+
This file will be removed in a future version.
|
|
17
5
|
"""
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
CYCLIST_PORT_FILE,
|
|
29
|
-
HookResponse,
|
|
30
|
-
find_project_root,
|
|
31
|
-
is_bell_mode_enabled,
|
|
32
|
-
output_hook_response,
|
|
33
|
-
read_port_file,
|
|
34
|
-
send_to_cyclist,
|
|
7
|
+
# Re-export public API from new location for existing tests/imports
|
|
8
|
+
from pennyfarthing_scripts.hooks.bell_mode import ( # noqa: F401
|
|
9
|
+
_check_tandem_files as check_tandem_files,
|
|
10
|
+
_get_latest_observation as get_latest_observation,
|
|
11
|
+
_get_tandem_mtime as get_tandem_mtime,
|
|
12
|
+
_read_bell_queue as read_bell_queue,
|
|
13
|
+
_read_tandem_observations as read_tandem_observations,
|
|
14
|
+
_save_tandem_mtime as save_tandem_mtime,
|
|
15
|
+
main,
|
|
35
16
|
)
|
|
36
17
|
|
|
37
18
|
|
|
38
|
-
def read_bell_queue(project_root: Path) -> list[dict]:
|
|
39
|
-
"""Read the bell message queue.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
project_root: Project root directory
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
List of queued messages, or empty list if none
|
|
46
|
-
"""
|
|
47
|
-
queue_path = project_root / ".pennyfarthing" / "bell-queue.json"
|
|
48
|
-
if not queue_path.exists():
|
|
49
|
-
return []
|
|
50
|
-
|
|
51
|
-
try:
|
|
52
|
-
with open(queue_path) as f:
|
|
53
|
-
queue = json.load(f)
|
|
54
|
-
if isinstance(queue, list):
|
|
55
|
-
return queue
|
|
56
|
-
except (json.JSONDecodeError, OSError):
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
return []
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def dequeue_message(project_root: Path) -> None:
|
|
63
|
-
"""Remove the first message from the queue.
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
project_root: Project root directory
|
|
67
|
-
"""
|
|
68
|
-
queue_path = project_root / ".pennyfarthing" / "bell-queue.json"
|
|
69
|
-
if not queue_path.exists():
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
try:
|
|
73
|
-
with open(queue_path) as f:
|
|
74
|
-
queue = json.load(f)
|
|
75
|
-
|
|
76
|
-
if isinstance(queue, list) and len(queue) > 0:
|
|
77
|
-
queue = queue[1:] # Remove first item
|
|
78
|
-
with open(queue_path, "w") as f:
|
|
79
|
-
json.dump(queue, f)
|
|
80
|
-
except (json.JSONDecodeError, OSError):
|
|
81
|
-
pass
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def notify_cyclist(project_root: Path, message_text: str) -> None:
|
|
85
|
-
"""Notify Cyclist browser that a queued message was consumed.
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
project_root: Project root directory
|
|
89
|
-
message_text: The message text that was consumed
|
|
90
|
-
"""
|
|
91
|
-
try:
|
|
92
|
-
send_to_cyclist(
|
|
93
|
-
endpoint="/api/bell-consumed",
|
|
94
|
-
data={"text": message_text},
|
|
95
|
-
project_root=project_root,
|
|
96
|
-
timeout=5,
|
|
97
|
-
)
|
|
98
|
-
except Exception:
|
|
99
|
-
# Ignore errors - don't block hook
|
|
100
|
-
pass
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
# =============================================================================
|
|
104
|
-
# Tandem Observation Injection (Story 95-7 / MSSCI-14672)
|
|
105
|
-
# =============================================================================
|
|
106
|
-
# Stubs for tandem observation injection. Dev will implement.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def read_tandem_observations(project_root: Path) -> list[Path]:
|
|
110
|
-
"""Find tandem observation files in .session/ directory.
|
|
111
|
-
|
|
112
|
-
Looks for files matching .session/*-tandem-*.md pattern.
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
project_root: Project root directory
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
List of Path objects for tandem observation files
|
|
119
|
-
"""
|
|
120
|
-
session_dir = project_root / ".session"
|
|
121
|
-
if not session_dir.is_dir():
|
|
122
|
-
return []
|
|
123
|
-
return sorted(session_dir.glob("*-tandem-*.md"))
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def get_latest_observation(file_content: str) -> dict | None:
|
|
127
|
-
"""Extract the latest observation entry from a tandem observation file.
|
|
128
|
-
|
|
129
|
-
Parses the markdown format to extract the most recent ## [HH:MM] Observation
|
|
130
|
-
block, including the observer persona name from the file header.
|
|
131
|
-
|
|
132
|
-
Args:
|
|
133
|
-
file_content: Full text content of the tandem observation file
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
Dict with 'persona' and 'text' keys, or None if no observations found
|
|
137
|
-
"""
|
|
138
|
-
# Extract persona from header: **Observer:** agent (Persona Name)
|
|
139
|
-
persona_match = re.search(r"\*\*Observer:\*\*\s*\w+\s*\(([^)]+)\)", file_content)
|
|
140
|
-
persona = persona_match.group(1) if persona_match else "Unknown"
|
|
141
|
-
|
|
142
|
-
# Split on observation headers: ## [HH:MM] Observation
|
|
143
|
-
entries = re.split(r"## \[\d{1,2}:\d{2}\] Observation\n", file_content)
|
|
144
|
-
if len(entries) < 2:
|
|
145
|
-
return None
|
|
146
|
-
|
|
147
|
-
# Last entry is the most recent observation
|
|
148
|
-
last_entry = entries[-1].strip()
|
|
149
|
-
# Remove trailing --- separator
|
|
150
|
-
last_entry = re.sub(r"\n---\s*$", "", last_entry).strip()
|
|
151
|
-
# Remove the **Trigger:** line
|
|
152
|
-
lines = last_entry.split("\n")
|
|
153
|
-
text_lines = [line for line in lines if not line.startswith("**Trigger:**")]
|
|
154
|
-
text = "\n".join(text_lines).strip()
|
|
155
|
-
|
|
156
|
-
if not text:
|
|
157
|
-
return None
|
|
158
|
-
|
|
159
|
-
return {"persona": persona, "text": text}
|
|
160
|
-
|
|
161
|
-
|
|
162
19
|
def format_tandem_message(persona_name: str, observation_text: str) -> str:
|
|
163
|
-
"""Format a tandem observation as a bell mode injection message.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
persona_name: The backseat agent's persona name
|
|
167
|
-
observation_text: The observation summary text
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
Formatted string: [Tandem] {persona_name}: {observation_text}
|
|
171
|
-
"""
|
|
20
|
+
"""Format a tandem observation as a bell mode injection message."""
|
|
172
21
|
return f"[Tandem] {persona_name}: {observation_text}"
|
|
173
22
|
|
|
174
23
|
|
|
175
|
-
def get_tandem_mtime(project_root: Path, agent: str) -> float:
|
|
176
|
-
"""Read the last-checked mtime for a tandem agent's observation file.
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
project_root: Project root directory
|
|
180
|
-
agent: Agent name (e.g. 'reviewer', 'tea')
|
|
181
|
-
|
|
182
|
-
Returns:
|
|
183
|
-
Last-checked mtime as float, or 0.0 if no sidecar exists
|
|
184
|
-
"""
|
|
185
|
-
sidecar = project_root / ".session" / f".tandem-mtime-{agent}"
|
|
186
|
-
if not sidecar.exists():
|
|
187
|
-
return 0.0
|
|
188
|
-
try:
|
|
189
|
-
return float(sidecar.read_text().strip())
|
|
190
|
-
except (ValueError, OSError):
|
|
191
|
-
return 0.0
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def save_tandem_mtime(project_root: Path, agent: str, mtime: float) -> None:
|
|
195
|
-
"""Save the mtime for a tandem agent's observation file.
|
|
196
|
-
|
|
197
|
-
Writes to .session/.tandem-mtime-{agent} sidecar file.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
project_root: Project root directory
|
|
201
|
-
agent: Agent name (e.g. 'reviewer', 'tea')
|
|
202
|
-
mtime: The mtime value to save
|
|
203
|
-
"""
|
|
204
|
-
sidecar = project_root / ".session" / f".tandem-mtime-{agent}"
|
|
205
|
-
try:
|
|
206
|
-
sidecar.write_text(str(mtime))
|
|
207
|
-
except OSError:
|
|
208
|
-
pass
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def check_tandem_files(project_root: Path) -> list[dict]:
|
|
212
|
-
"""Check for new tandem observations and return injection messages.
|
|
213
|
-
|
|
214
|
-
Main entry point for tandem injection in the PostToolUse hook.
|
|
215
|
-
Always active — no configuration required. The presence of tandem
|
|
216
|
-
observation files in .session/ is the only signal needed.
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
project_root: Project root directory
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
List of dicts with 'agent', 'message' keys for each new observation
|
|
223
|
-
"""
|
|
224
|
-
tandem_files = read_tandem_observations(project_root)
|
|
225
|
-
if not tandem_files:
|
|
226
|
-
return []
|
|
227
|
-
|
|
228
|
-
results = []
|
|
229
|
-
for obs_file in tandem_files:
|
|
230
|
-
# Extract agent name from filename: *-tandem-{agent}.md
|
|
231
|
-
agent_match = re.search(r"-tandem-(\w+)\.md$", obs_file.name)
|
|
232
|
-
if not agent_match:
|
|
233
|
-
continue
|
|
234
|
-
agent = agent_match.group(1)
|
|
235
|
-
|
|
236
|
-
# Compare mtime
|
|
237
|
-
try:
|
|
238
|
-
file_mtime = obs_file.stat().st_mtime
|
|
239
|
-
except OSError:
|
|
240
|
-
continue
|
|
241
|
-
saved_mtime = get_tandem_mtime(project_root, agent)
|
|
242
|
-
if file_mtime == saved_mtime:
|
|
243
|
-
continue
|
|
244
|
-
|
|
245
|
-
# Read and parse
|
|
246
|
-
try:
|
|
247
|
-
content = obs_file.read_text()
|
|
248
|
-
except OSError:
|
|
249
|
-
continue
|
|
250
|
-
obs = get_latest_observation(content)
|
|
251
|
-
if not obs:
|
|
252
|
-
# Update mtime even for unparseable files to avoid re-checking
|
|
253
|
-
save_tandem_mtime(project_root, agent, file_mtime)
|
|
254
|
-
continue
|
|
255
|
-
|
|
256
|
-
message = format_tandem_message(obs["persona"], obs["text"])
|
|
257
|
-
results.append({"agent": agent, "message": message})
|
|
258
|
-
|
|
259
|
-
# Update mtime sidecar
|
|
260
|
-
save_tandem_mtime(project_root, agent, file_mtime)
|
|
261
|
-
|
|
262
|
-
return results
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def main() -> None:
|
|
266
|
-
"""Main entry point for PostToolUse hook."""
|
|
267
|
-
try:
|
|
268
|
-
# Read and discard stdin (required by hook protocol)
|
|
269
|
-
sys.stdin.read()
|
|
270
|
-
|
|
271
|
-
# Find project root
|
|
272
|
-
project_root = find_project_root()
|
|
273
|
-
if not project_root:
|
|
274
|
-
sys.exit(0)
|
|
275
|
-
|
|
276
|
-
# --- Bell queue (Cyclist only, requires bell_mode: true) ---
|
|
277
|
-
# Takes precedence over tandem when active.
|
|
278
|
-
is_cyclist = read_port_file(CYCLIST_PORT_FILE, project_root) is not None
|
|
279
|
-
if is_cyclist and is_bell_mode_enabled(project_root):
|
|
280
|
-
queue = read_bell_queue(project_root)
|
|
281
|
-
if queue:
|
|
282
|
-
first_message = queue[0]
|
|
283
|
-
message_text = first_message.get("text", "")
|
|
284
|
-
if message_text:
|
|
285
|
-
output_hook_response(HookResponse(
|
|
286
|
-
event_name="PostToolUse",
|
|
287
|
-
additional_context=f"User feedback: {message_text}",
|
|
288
|
-
))
|
|
289
|
-
dequeue_message(project_root)
|
|
290
|
-
notify_cyclist(project_root, message_text)
|
|
291
|
-
sys.exit(0)
|
|
292
|
-
|
|
293
|
-
# --- Tandem observations (always active, no config required) ---
|
|
294
|
-
tandem_results = check_tandem_files(project_root)
|
|
295
|
-
if tandem_results:
|
|
296
|
-
output_hook_response(HookResponse(
|
|
297
|
-
event_name="PostToolUse",
|
|
298
|
-
additional_context=tandem_results[0]["message"],
|
|
299
|
-
))
|
|
300
|
-
|
|
301
|
-
sys.exit(0)
|
|
302
|
-
|
|
303
|
-
except Exception as e:
|
|
304
|
-
print(f"[bellmode-hook] Error: {e}", file=sys.stderr)
|
|
305
|
-
sys.exit(0)
|
|
306
|
-
|
|
307
|
-
|
|
308
24
|
if __name__ == "__main__":
|
|
309
25
|
main()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""AuditLogPanel — Real-time tool event audit log for BikeRack TUI.
|
|
2
|
+
|
|
3
|
+
Story 110-8: Subscribes to /ws/spans, displays tool events in a native
|
|
4
|
+
Textual DataTable with timestamp, tool name, input excerpt, and result.
|
|
5
|
+
|
|
6
|
+
Uses native Textual widgets (DataTable) instead of Rich renderables.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from textual._context import NoActiveAppError
|
|
16
|
+
from textual.widgets import DataTable
|
|
17
|
+
|
|
18
|
+
from pennyfarthing_scripts.bikerack.base_panel import PANEL_ICONS, BasePanel
|
|
19
|
+
|
|
20
|
+
MAX_INPUT_LENGTH = 80
|
|
21
|
+
|
|
22
|
+
# Shared fallback console for offline column/cell measurement
|
|
23
|
+
_FALLBACK_CONSOLE = Console()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _OfflineDataTable(DataTable):
|
|
27
|
+
"""DataTable subclass that works without an active Textual app.
|
|
28
|
+
|
|
29
|
+
Textual's DataTable.add_columns/add_row need self.app.console for
|
|
30
|
+
column width measurement. This subclass provides a fallback Rich Console
|
|
31
|
+
so the table can be constructed and populated in unit tests.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def app(self):
|
|
36
|
+
try:
|
|
37
|
+
return super().app
|
|
38
|
+
except NoActiveAppError:
|
|
39
|
+
return type("_Stub", (), {"console": _FALLBACK_CONSOLE})()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AuditLogPanel(BasePanel):
|
|
43
|
+
"""Audit log panel — real-time tool event display via DataTable.
|
|
44
|
+
|
|
45
|
+
Subscribes to the 'spans' WebSocket channel and renders tool events
|
|
46
|
+
in a native Textual DataTable with columns: Time, Tool, Input, Result.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
channel: str = "spans"
|
|
50
|
+
panel_name: str = "Audit Log"
|
|
51
|
+
icon: str = PANEL_ICONS["audit-log"][0]
|
|
52
|
+
|
|
53
|
+
def __init__(self, client=None, **kwargs):
|
|
54
|
+
super().__init__(client=client, **kwargs)
|
|
55
|
+
self._table = _OfflineDataTable()
|
|
56
|
+
self._table.add_columns("Time", "Tool", "Input", "Result")
|
|
57
|
+
|
|
58
|
+
def handle_message(self, message: dict[str, Any] | None) -> None:
|
|
59
|
+
"""Handle incoming WebSocket messages for tool events.
|
|
60
|
+
|
|
61
|
+
Overrides BasePanel.handle_message to manipulate DataTable directly
|
|
62
|
+
instead of going through render_panel/post_message.
|
|
63
|
+
"""
|
|
64
|
+
if not self._mounted or message is None:
|
|
65
|
+
return
|
|
66
|
+
if not isinstance(message, dict):
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
msg_type = message.get("type")
|
|
70
|
+
|
|
71
|
+
if msg_type == "init":
|
|
72
|
+
spans = message.get("spans")
|
|
73
|
+
if not isinstance(spans, list):
|
|
74
|
+
return
|
|
75
|
+
self._table.clear()
|
|
76
|
+
for span in spans:
|
|
77
|
+
if isinstance(span, dict):
|
|
78
|
+
self._add_row(span)
|
|
79
|
+
|
|
80
|
+
elif msg_type == "span":
|
|
81
|
+
span = message.get("span")
|
|
82
|
+
if isinstance(span, dict):
|
|
83
|
+
self._add_row(span)
|
|
84
|
+
|
|
85
|
+
def _add_row(self, span: dict[str, Any]) -> None:
|
|
86
|
+
"""Add a single tool event row to the DataTable."""
|
|
87
|
+
timestamp = span.get("timestamp")
|
|
88
|
+
tool_name = span.get("toolName", "")
|
|
89
|
+
input_text = span.get("input", "")
|
|
90
|
+
success = span.get("success")
|
|
91
|
+
|
|
92
|
+
time_str = _format_timestamp(timestamp)
|
|
93
|
+
|
|
94
|
+
if len(input_text) > MAX_INPUT_LENGTH:
|
|
95
|
+
input_text = input_text[: MAX_INPUT_LENGTH - 1] + "\u2026"
|
|
96
|
+
|
|
97
|
+
if success is True:
|
|
98
|
+
result = "\u2713"
|
|
99
|
+
elif success is False:
|
|
100
|
+
result = "\u2717"
|
|
101
|
+
else:
|
|
102
|
+
result = "\u2014"
|
|
103
|
+
|
|
104
|
+
self._table.add_row(time_str, tool_name, input_text, result)
|
|
105
|
+
|
|
106
|
+
def render_panel(self, payload: dict[str, Any]) -> Any:
|
|
107
|
+
"""Not used — handle_message manages DataTable directly."""
|
|
108
|
+
return self._table
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _format_timestamp(ts: Any) -> str:
|
|
112
|
+
"""Format millisecond timestamp to HH:MM:SS."""
|
|
113
|
+
if ts is None:
|
|
114
|
+
return "\u2014"
|
|
115
|
+
try:
|
|
116
|
+
dt = datetime.fromtimestamp(float(ts) / 1000, tz=timezone.utc)
|
|
117
|
+
return dt.strftime("%H:%M:%S")
|
|
118
|
+
except (ValueError, TypeError, OSError):
|
|
119
|
+
return "\u2014"
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
12
|
from rich.text import Text
|
|
13
|
+
from textual.message import Message
|
|
13
14
|
from textual.widgets import Static
|
|
14
15
|
|
|
15
16
|
# Nerd Font icon registry: panel_name → (nerd_font_icon, ascii_fallback)
|
|
@@ -46,7 +47,12 @@ def get_panel_icon(panel_name: str, use_nerd_font: bool = True) -> str:
|
|
|
46
47
|
return entry[0] if use_nerd_font else entry[1]
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
def render_progress_bar(
|
|
50
|
+
def render_progress_bar(
|
|
51
|
+
percent: int | float,
|
|
52
|
+
width: int = 20,
|
|
53
|
+
warn_high: bool = False,
|
|
54
|
+
fill_style: str | None = None,
|
|
55
|
+
) -> Text:
|
|
50
56
|
"""Render a Unicode progress bar with color based on percentage.
|
|
51
57
|
|
|
52
58
|
Args:
|
|
@@ -54,6 +60,7 @@ def render_progress_bar(percent: int | float, width: int = 20, warn_high: bool =
|
|
|
54
60
|
width: Number of bar characters (default 20).
|
|
55
61
|
warn_high: If True, use red at high values (for resource usage).
|
|
56
62
|
If False (default), use blue at 100% (for completion).
|
|
63
|
+
fill_style: Override the computed fill color (e.g. ``"dim green"``).
|
|
57
64
|
|
|
58
65
|
Returns:
|
|
59
66
|
Rich Text like ``[████████░░░░░░░░░░░░] 22%``
|
|
@@ -62,7 +69,9 @@ def render_progress_bar(percent: int | float, width: int = 20, warn_high: bool =
|
|
|
62
69
|
filled = round(width * percent / 100)
|
|
63
70
|
empty = width - filled
|
|
64
71
|
|
|
65
|
-
if
|
|
72
|
+
if fill_style is not None:
|
|
73
|
+
style = fill_style
|
|
74
|
+
elif warn_high:
|
|
66
75
|
if percent < 50:
|
|
67
76
|
style = "green"
|
|
68
77
|
elif percent <= 80:
|
|
@@ -113,6 +122,13 @@ class BasePanel(Static):
|
|
|
113
122
|
``render_panel(payload)`` to return a Rich renderable.
|
|
114
123
|
"""
|
|
115
124
|
|
|
125
|
+
class DataReceived(Message, bubble=False):
|
|
126
|
+
"""WebSocket data received — routed through Textual message system."""
|
|
127
|
+
|
|
128
|
+
def __init__(self, content: Any) -> None:
|
|
129
|
+
super().__init__()
|
|
130
|
+
self.content = content
|
|
131
|
+
|
|
116
132
|
#: WebSocket channel this panel subscribes to (override in subclass)
|
|
117
133
|
channel: str = ""
|
|
118
134
|
|
|
@@ -138,10 +154,17 @@ class BasePanel(Static):
|
|
|
138
154
|
"""Mark panel as unmounted — messages ignored after this."""
|
|
139
155
|
self._mounted = False
|
|
140
156
|
|
|
157
|
+
def on_base_panel_data_received(self, event: DataReceived) -> None:
|
|
158
|
+
"""Process DataReceived in Textual message context — triggers repaint."""
|
|
159
|
+
self.update(event.content)
|
|
160
|
+
|
|
141
161
|
def handle_message(self, message: dict[str, Any] | None) -> None:
|
|
142
162
|
"""Handle incoming WebSocket message.
|
|
143
163
|
|
|
144
|
-
Stores payload, calls render_panel,
|
|
164
|
+
Stores payload, calls render_panel, posts DataReceived message.
|
|
165
|
+
Uses post_message to route through Textual's message system,
|
|
166
|
+
ensuring proper repaint cycle (call_from_thread / direct update
|
|
167
|
+
from async WS tasks does not reliably trigger redraws).
|
|
145
168
|
No-op after unmount or if message is None.
|
|
146
169
|
"""
|
|
147
170
|
if not self._mounted or message is None:
|
|
@@ -149,7 +172,7 @@ class BasePanel(Static):
|
|
|
149
172
|
self._last_payload = message
|
|
150
173
|
rendered = self.render_panel(message)
|
|
151
174
|
try:
|
|
152
|
-
self.
|
|
175
|
+
self.post_message(self.DataReceived(rendered))
|
|
153
176
|
except Exception:
|
|
154
177
|
pass
|
|
155
178
|
|