@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
|
@@ -1,437 +1,32 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Backward-compatibility shim — hooks utilities moved to hooks/ subpackage.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- Settings loading (relay_mode, permission_mode)
|
|
8
|
-
- Context state checking
|
|
9
|
-
- HTTP communication with Cyclist
|
|
4
|
+
All exports are re-imported from pennyfarthing_scripts.hooks (the package).
|
|
5
|
+
Existing code that does `from hooks import ...` or
|
|
6
|
+
`from pennyfarthing_scripts.hooks import ...` continues to work.
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Story: MSSCI-12409 - Hook consistency and relay mode compatibility
|
|
8
|
+
This file will be removed in a future version.
|
|
14
9
|
"""
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# HTTP timeout for Cyclist communication
|
|
39
|
-
HTTP_TIMEOUT_SECONDS = 120
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# =============================================================================
|
|
43
|
-
# Project Root Detection
|
|
44
|
-
# =============================================================================
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def find_project_root(start_dir: Path | None = None) -> Path | None:
|
|
48
|
-
"""Find the project root by looking for marker files.
|
|
49
|
-
|
|
50
|
-
Searches for (in order):
|
|
51
|
-
1. .wheelhub-port (WheelHub is running)
|
|
52
|
-
2. .pennyfarthing directory
|
|
53
|
-
3. .claude directory
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
start_dir: Directory to start search from (defaults to cwd)
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
Path to project root, or None if not found
|
|
60
|
-
"""
|
|
61
|
-
current = Path(start_dir) if start_dir else Path.cwd()
|
|
62
|
-
current = current.resolve()
|
|
63
|
-
|
|
64
|
-
while current != current.parent:
|
|
65
|
-
# Check for Cyclist port files first (indicates Cyclist is running)
|
|
66
|
-
if (current / CYCLIST_PORT_FILE).exists():
|
|
67
|
-
return current
|
|
68
|
-
# Fall back to directory markers
|
|
69
|
-
if (current / ".pennyfarthing").is_dir():
|
|
70
|
-
return current
|
|
71
|
-
if (current / ".claude").is_dir():
|
|
72
|
-
return current
|
|
73
|
-
current = current.parent
|
|
74
|
-
|
|
75
|
-
return None
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# =============================================================================
|
|
79
|
-
# Port File Reading
|
|
80
|
-
# =============================================================================
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def read_port_file(file_name: str, project_root: Path | None = None) -> int | None:
|
|
84
|
-
"""Read a port number from a Cyclist port file.
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
file_name: Name of the port file (e.g. .wheelhub-port)
|
|
88
|
-
project_root: Project root directory (auto-detected if not provided)
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
Port number, or None if file not found or invalid
|
|
92
|
-
"""
|
|
93
|
-
root = project_root or find_project_root()
|
|
94
|
-
if not root:
|
|
95
|
-
return None
|
|
96
|
-
|
|
97
|
-
port_file = root / file_name
|
|
98
|
-
if not port_file.exists():
|
|
99
|
-
return None
|
|
100
|
-
|
|
101
|
-
try:
|
|
102
|
-
content = port_file.read_text().strip()
|
|
103
|
-
port = int(content)
|
|
104
|
-
if 0 < port < 65536:
|
|
105
|
-
return port
|
|
106
|
-
except (ValueError, OSError):
|
|
107
|
-
pass
|
|
108
|
-
|
|
109
|
-
return None
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def get_cyclist_port(project_root: Path | None = None) -> int:
|
|
113
|
-
"""Get the WheelHub server port.
|
|
114
|
-
|
|
115
|
-
WheelHub is the central coordination server for all Cyclist communication,
|
|
116
|
-
including hook requests, OTEL, REST APIs, and WebSocket.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
project_root: Project root directory (auto-detected if not provided)
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
Port number (default if file not found)
|
|
123
|
-
"""
|
|
124
|
-
port = read_port_file(CYCLIST_PORT_FILE, project_root)
|
|
125
|
-
if port:
|
|
126
|
-
return port
|
|
127
|
-
|
|
128
|
-
return DEFAULT_CYCLIST_PORT
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
# =============================================================================
|
|
132
|
-
# Settings Loading
|
|
133
|
-
# =============================================================================
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@dataclass
|
|
137
|
-
class CyclistSettings:
|
|
138
|
-
"""Cyclist workflow settings from config.local.yaml."""
|
|
139
|
-
|
|
140
|
-
permission_mode: str = "manual" # plan, manual, accept
|
|
141
|
-
relay_mode: bool = False
|
|
142
|
-
bell_mode: bool = False
|
|
143
|
-
theme: str | None = None
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def load_settings(project_root: Path | None = None) -> CyclistSettings:
|
|
147
|
-
"""Load Cyclist settings from .pennyfarthing/config.local.yaml.
|
|
148
|
-
|
|
149
|
-
Handles legacy setting migrations:
|
|
150
|
-
- permission_mode: 'turbo' -> 'accept' + relay_mode: True
|
|
151
|
-
- handoff_mode: 'auto' -> relay_mode: True
|
|
152
|
-
- auto_handoff: True -> relay_mode: True
|
|
153
|
-
|
|
154
|
-
Args:
|
|
155
|
-
project_root: Project root directory (auto-detected if not provided)
|
|
156
|
-
|
|
157
|
-
Returns:
|
|
158
|
-
CyclistSettings with current configuration
|
|
159
|
-
"""
|
|
160
|
-
settings = CyclistSettings()
|
|
161
|
-
|
|
162
|
-
root = project_root or find_project_root()
|
|
163
|
-
if not root:
|
|
164
|
-
return settings
|
|
165
|
-
|
|
166
|
-
config_path = root / ".pennyfarthing" / "config.local.yaml"
|
|
167
|
-
if not config_path.exists():
|
|
168
|
-
return settings
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
with open(config_path) as f:
|
|
172
|
-
config = yaml.safe_load(f) or {}
|
|
173
|
-
except (OSError, yaml.YAMLError):
|
|
174
|
-
return settings
|
|
175
|
-
|
|
176
|
-
# Extract theme
|
|
177
|
-
settings.theme = config.get("theme")
|
|
178
|
-
|
|
179
|
-
# Extract workflow settings
|
|
180
|
-
workflow = config.get("workflow", {})
|
|
181
|
-
if not isinstance(workflow, dict):
|
|
182
|
-
return settings
|
|
183
|
-
|
|
184
|
-
# Handle permission_mode
|
|
185
|
-
mode = workflow.get("permission_mode", "manual")
|
|
186
|
-
if mode == "turbo":
|
|
187
|
-
# Migrate turbo -> accept + relay_mode
|
|
188
|
-
settings.permission_mode = "accept"
|
|
189
|
-
settings.relay_mode = True
|
|
190
|
-
elif mode in ("plan", "manual", "accept"):
|
|
191
|
-
settings.permission_mode = mode
|
|
192
|
-
else:
|
|
193
|
-
settings.permission_mode = "manual"
|
|
194
|
-
|
|
195
|
-
# Handle explicit relay_mode (overrides migration)
|
|
196
|
-
if "relay_mode" in workflow and isinstance(workflow["relay_mode"], bool):
|
|
197
|
-
settings.relay_mode = workflow["relay_mode"]
|
|
198
|
-
elif not settings.relay_mode:
|
|
199
|
-
# Check legacy settings
|
|
200
|
-
if workflow.get("handoff_mode") == "auto":
|
|
201
|
-
settings.relay_mode = True
|
|
202
|
-
elif workflow.get("auto_handoff") is True:
|
|
203
|
-
settings.relay_mode = True
|
|
204
|
-
|
|
205
|
-
# Handle bell_mode
|
|
206
|
-
if "bell_mode" in workflow and isinstance(workflow["bell_mode"], bool):
|
|
207
|
-
settings.bell_mode = workflow["bell_mode"]
|
|
208
|
-
|
|
209
|
-
return settings
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def is_relay_mode_enabled(project_root: Path | None = None) -> bool:
|
|
213
|
-
"""Check if relay mode (auto-handoff) is enabled.
|
|
214
|
-
|
|
215
|
-
Args:
|
|
216
|
-
project_root: Project root directory (auto-detected if not provided)
|
|
217
|
-
|
|
218
|
-
Returns:
|
|
219
|
-
True if relay mode is enabled
|
|
220
|
-
"""
|
|
221
|
-
return load_settings(project_root).relay_mode
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def is_bell_mode_enabled(project_root: Path | None = None) -> bool:
|
|
225
|
-
"""Check if bell mode is enabled.
|
|
226
|
-
|
|
227
|
-
Args:
|
|
228
|
-
project_root: Project root directory (auto-detected if not provided)
|
|
229
|
-
|
|
230
|
-
Returns:
|
|
231
|
-
True if bell mode is enabled
|
|
232
|
-
"""
|
|
233
|
-
return load_settings(project_root).bell_mode
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# =============================================================================
|
|
237
|
-
# Context State
|
|
238
|
-
# =============================================================================
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
@dataclass
|
|
242
|
-
class ContextState:
|
|
243
|
-
"""Current context usage state."""
|
|
244
|
-
|
|
245
|
-
used_tokens: int = 0
|
|
246
|
-
max_tokens: int = 200000
|
|
247
|
-
percentage: float = 0.0
|
|
248
|
-
is_high: bool = False # > 60%
|
|
249
|
-
is_critical: bool = False # > 80%
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def get_context_state(project_root: Path | None = None) -> ContextState:
|
|
253
|
-
"""Get current context usage from Cyclist API.
|
|
254
|
-
|
|
255
|
-
Calls Cyclist's /api/context endpoint which runs check-context.sh.
|
|
256
|
-
|
|
257
|
-
Args:
|
|
258
|
-
project_root: Project root directory (auto-detected if not provided)
|
|
259
|
-
|
|
260
|
-
Returns:
|
|
261
|
-
ContextState with current usage (defaults if Cyclist not running)
|
|
262
|
-
"""
|
|
263
|
-
state = ContextState()
|
|
264
|
-
|
|
265
|
-
port = get_cyclist_port(project_root)
|
|
266
|
-
url = f"http://127.0.0.1:{port}/api/context"
|
|
267
|
-
|
|
268
|
-
try:
|
|
269
|
-
with urllib.request.urlopen(url, timeout=5) as response:
|
|
270
|
-
data = json.loads(response.read().decode())
|
|
271
|
-
state.used_tokens = data.get("used_tokens", 0)
|
|
272
|
-
state.max_tokens = data.get("max_tokens", 200000)
|
|
273
|
-
if state.max_tokens > 0:
|
|
274
|
-
state.percentage = (state.used_tokens / state.max_tokens) * 100
|
|
275
|
-
state.is_high = state.percentage > 60
|
|
276
|
-
state.is_critical = state.percentage > 80
|
|
277
|
-
except (urllib.error.URLError, json.JSONDecodeError, OSError):
|
|
278
|
-
# Cyclist not running or error - return defaults
|
|
279
|
-
pass
|
|
280
|
-
|
|
281
|
-
return state
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
# =============================================================================
|
|
285
|
-
# Cyclist HTTP Communication
|
|
286
|
-
# =============================================================================
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def send_to_cyclist(
|
|
290
|
-
endpoint: str,
|
|
291
|
-
data: dict[str, Any],
|
|
292
|
-
port: int | None = None,
|
|
293
|
-
project_root: Path | None = None,
|
|
294
|
-
timeout: int = HTTP_TIMEOUT_SECONDS,
|
|
295
|
-
) -> dict[str, Any] | None:
|
|
296
|
-
"""Send a POST request to WheelHub (Cyclist's central coordination server).
|
|
297
|
-
|
|
298
|
-
All endpoints go through WheelHub per ADR-0004.
|
|
299
|
-
|
|
300
|
-
Args:
|
|
301
|
-
endpoint: API endpoint path (e.g., "/api/hook-request")
|
|
302
|
-
data: JSON data to send
|
|
303
|
-
port: Port to use (auto-detected if not provided)
|
|
304
|
-
project_root: Project root for port discovery
|
|
305
|
-
timeout: Request timeout in seconds
|
|
306
|
-
|
|
307
|
-
Returns:
|
|
308
|
-
Response JSON as dict, or None on error
|
|
309
|
-
"""
|
|
310
|
-
if port is None:
|
|
311
|
-
port = get_cyclist_port(project_root)
|
|
312
|
-
|
|
313
|
-
url = f"http://127.0.0.1:{port}{endpoint}"
|
|
314
|
-
json_data = json.dumps(data).encode("utf-8")
|
|
315
|
-
|
|
316
|
-
request = urllib.request.Request(
|
|
317
|
-
url,
|
|
318
|
-
data=json_data,
|
|
319
|
-
headers={"Content-Type": "application/json"},
|
|
320
|
-
method="POST",
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
try:
|
|
324
|
-
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
325
|
-
return json.loads(response.read().decode())
|
|
326
|
-
except urllib.error.URLError as e:
|
|
327
|
-
# Connection refused means Cyclist isn't running
|
|
328
|
-
if "Connection refused" in str(e):
|
|
329
|
-
return None
|
|
330
|
-
raise
|
|
331
|
-
except (json.JSONDecodeError, OSError):
|
|
332
|
-
return None
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
# =============================================================================
|
|
336
|
-
# Hook Response Formatting
|
|
337
|
-
# =============================================================================
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
@dataclass
|
|
341
|
-
class HookResponse:
|
|
342
|
-
"""Standard hook response for Claude Code."""
|
|
343
|
-
|
|
344
|
-
event_name: str
|
|
345
|
-
decision: str | None = None # allow, deny, ask (for PreToolUse)
|
|
346
|
-
reason: str | None = None
|
|
347
|
-
updated_input: dict[str, Any] | None = None
|
|
348
|
-
additional_context: str | None = None # For PostToolUse context injection
|
|
349
|
-
|
|
350
|
-
def to_json(self) -> str:
|
|
351
|
-
"""Format as Claude Code hook JSON output."""
|
|
352
|
-
output: dict[str, Any] = {
|
|
353
|
-
"hookSpecificOutput": {
|
|
354
|
-
"hookEventName": self.event_name,
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
hook_output = output["hookSpecificOutput"]
|
|
359
|
-
|
|
360
|
-
if self.decision:
|
|
361
|
-
hook_output["permissionDecision"] = self.decision
|
|
362
|
-
if self.reason:
|
|
363
|
-
hook_output["permissionDecisionReason"] = self.reason
|
|
364
|
-
if self.updated_input:
|
|
365
|
-
hook_output["updatedInput"] = self.updated_input
|
|
366
|
-
if self.additional_context:
|
|
367
|
-
hook_output["additionalContext"] = self.additional_context
|
|
368
|
-
|
|
369
|
-
return json.dumps(output)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
def output_hook_response(response: HookResponse) -> None:
|
|
373
|
-
"""Output hook response to stdout for Claude Code."""
|
|
374
|
-
print(response.to_json())
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
def read_stdin_json() -> dict[str, Any]:
|
|
378
|
-
"""Read JSON from stdin (hook input from Claude Code).
|
|
379
|
-
|
|
380
|
-
Returns:
|
|
381
|
-
Parsed JSON as dict
|
|
382
|
-
|
|
383
|
-
Raises:
|
|
384
|
-
ValueError: If input is not valid JSON
|
|
385
|
-
"""
|
|
386
|
-
data = sys.stdin.read()
|
|
387
|
-
try:
|
|
388
|
-
return json.loads(data)
|
|
389
|
-
except json.JSONDecodeError as e:
|
|
390
|
-
raise ValueError(f"Invalid JSON input: {e}") from e
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
# =============================================================================
|
|
394
|
-
# Hook Execution Utilities
|
|
395
|
-
# =============================================================================
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
def is_cyclist_running(project_root: Path | None = None) -> bool:
|
|
399
|
-
"""Check if Cyclist server is running.
|
|
400
|
-
|
|
401
|
-
Checks the CYCLIST environment variable set by ClaudeService when
|
|
402
|
-
spawning Claude inside Cyclist. No file I/O, no HTTP, no signals —
|
|
403
|
-
this runs on every tool invocation and must be instant.
|
|
404
|
-
|
|
405
|
-
The project_root parameter is kept for backward compatibility but
|
|
406
|
-
is no longer used.
|
|
407
|
-
|
|
408
|
-
Returns:
|
|
409
|
-
True if running inside a Cyclist-spawned Claude process
|
|
410
|
-
"""
|
|
411
|
-
return os.environ.get("CYCLIST") == "1"
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
def should_auto_approve(settings: CyclistSettings) -> bool:
|
|
415
|
-
"""Check if requests should be auto-approved based on settings.
|
|
416
|
-
|
|
417
|
-
Auto-approve when permission_mode is 'accept' (formerly turbo).
|
|
418
|
-
|
|
419
|
-
Args:
|
|
420
|
-
settings: Current Cyclist settings
|
|
421
|
-
|
|
422
|
-
Returns:
|
|
423
|
-
True if auto-approval is enabled
|
|
424
|
-
"""
|
|
425
|
-
return settings.permission_mode == "accept"
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
def should_auto_handoff(settings: CyclistSettings) -> bool:
|
|
429
|
-
"""Check if handoffs should be automatic based on settings.
|
|
430
|
-
|
|
431
|
-
Args:
|
|
432
|
-
settings: Current Cyclist settings
|
|
433
|
-
|
|
434
|
-
Returns:
|
|
435
|
-
True if relay_mode is enabled
|
|
436
|
-
"""
|
|
437
|
-
return settings.relay_mode
|
|
11
|
+
# Re-export everything from the hooks package
|
|
12
|
+
from pennyfarthing_scripts.hooks import ( # noqa: F401
|
|
13
|
+
CYCLIST_PORT_FILE,
|
|
14
|
+
DEFAULT_CYCLIST_PORT,
|
|
15
|
+
HTTP_TIMEOUT_SECONDS,
|
|
16
|
+
ContextState,
|
|
17
|
+
CyclistSettings,
|
|
18
|
+
HookResponse,
|
|
19
|
+
find_project_root,
|
|
20
|
+
get_context_state,
|
|
21
|
+
get_cyclist_port,
|
|
22
|
+
is_bell_mode_enabled,
|
|
23
|
+
is_cyclist_running,
|
|
24
|
+
is_relay_mode_enabled,
|
|
25
|
+
load_settings,
|
|
26
|
+
output_hook_response,
|
|
27
|
+
read_port_file,
|
|
28
|
+
read_stdin_json,
|
|
29
|
+
send_to_cyclist,
|
|
30
|
+
should_auto_approve,
|
|
31
|
+
should_auto_handoff,
|
|
32
|
+
)
|
|
@@ -1,192 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
1
|
"""
|
|
3
|
-
|
|
2
|
+
Backward-compatibility shim — pretooluse hook moved to hooks/cyclist_pretooluse.py.
|
|
4
3
|
|
|
5
|
-
This
|
|
6
|
-
It communicates with WheelHub (Cyclist's central coordination server)
|
|
7
|
-
via HTTP to get approval decisions.
|
|
8
|
-
|
|
9
|
-
Flow:
|
|
10
|
-
1. Claude Code calls this script with tool info via stdin (JSON)
|
|
11
|
-
2. Script reads port from .wheelhub-port in project directory
|
|
12
|
-
3. Script sends request to WheelHub's /api/hook-request endpoint
|
|
13
|
-
4. WheelHub shows approval modal, user decides
|
|
14
|
-
5. Script receives response, outputs JSON decision to stdout
|
|
15
|
-
6. Claude Code proceeds or blocks based on decision
|
|
16
|
-
|
|
17
|
-
Per ADR-0004: All communication converges through WheelHub.
|
|
18
|
-
|
|
19
|
-
Story: MSSCI-12409 - Hook consistency and WheelHub consolidation
|
|
20
|
-
|
|
21
|
-
Usage:
|
|
22
|
-
Install in ~/.claude/settings.json or project .claude/settings.json:
|
|
23
|
-
{
|
|
24
|
-
"hooks": {
|
|
25
|
-
"PreToolUse": [{
|
|
26
|
-
"matcher": "Bash",
|
|
27
|
-
"hooks": [{
|
|
28
|
-
"type": "command",
|
|
29
|
-
"command": "python3 /path/to/pretooluse_hook.py"
|
|
30
|
-
}]
|
|
31
|
-
}]
|
|
32
|
-
}
|
|
33
|
-
}
|
|
4
|
+
This file will be removed in a future version.
|
|
34
5
|
"""
|
|
35
6
|
|
|
36
|
-
import
|
|
37
|
-
from pathlib import Path
|
|
38
|
-
|
|
39
|
-
# Add parent directory to path for imports
|
|
40
|
-
sys.path.insert(0, str(Path(__file__).parent))
|
|
41
|
-
|
|
42
|
-
from hooks import (
|
|
43
|
-
HookResponse,
|
|
44
|
-
find_project_root,
|
|
45
|
-
get_context_state,
|
|
46
|
-
is_cyclist_running,
|
|
47
|
-
load_settings,
|
|
48
|
-
output_hook_response,
|
|
49
|
-
read_stdin_json,
|
|
50
|
-
send_to_cyclist,
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _resolve_agent(session_id: str | None, project_root: Path | None) -> str | None:
|
|
55
|
-
"""Resolve agent name from session file.
|
|
56
|
-
|
|
57
|
-
Looks up .session/agents/{session_id} to find the active agent name.
|
|
58
|
-
Falls back to the most recently modified agent file if session_id
|
|
59
|
-
doesn't match.
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
session_id: Claude Code session ID
|
|
63
|
-
project_root: Project root directory
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
Agent name string, or None if not found
|
|
67
|
-
"""
|
|
68
|
-
if not project_root:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
agents_dir = project_root / ".session" / "agents"
|
|
72
|
-
if not agents_dir.is_dir():
|
|
73
|
-
return None
|
|
74
|
-
|
|
75
|
-
# Try exact session_id match first
|
|
76
|
-
if session_id:
|
|
77
|
-
agent_file = agents_dir / session_id
|
|
78
|
-
if agent_file.is_file():
|
|
79
|
-
try:
|
|
80
|
-
return agent_file.read_text().strip() or None
|
|
81
|
-
except OSError:
|
|
82
|
-
pass
|
|
83
|
-
|
|
84
|
-
# Fallback: most recently modified agent file
|
|
85
|
-
try:
|
|
86
|
-
agent_files = sorted(
|
|
87
|
-
(f for f in agents_dir.iterdir() if f.is_file()),
|
|
88
|
-
key=lambda f: f.stat().st_mtime,
|
|
89
|
-
reverse=True,
|
|
90
|
-
)
|
|
91
|
-
if agent_files:
|
|
92
|
-
return agent_files[0].read_text().strip() or None
|
|
93
|
-
except OSError:
|
|
94
|
-
pass
|
|
95
|
-
|
|
96
|
-
return None
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def main() -> None:
|
|
100
|
-
"""Main entry point for PreToolUse hook."""
|
|
101
|
-
try:
|
|
102
|
-
# Read tool data from Claude Code
|
|
103
|
-
tool_data = read_stdin_json()
|
|
104
|
-
|
|
105
|
-
# Extract relevant fields
|
|
106
|
-
tool_name = tool_data.get("tool_name", "")
|
|
107
|
-
tool_id = tool_data.get("tool_use_id", "")
|
|
108
|
-
tool_input = tool_data.get("tool_input", {})
|
|
109
|
-
session_id = tool_data.get("session_id")
|
|
110
|
-
|
|
111
|
-
# Find project root
|
|
112
|
-
project_root = find_project_root()
|
|
113
|
-
|
|
114
|
-
# Resolve agent name from session file (MSSCI-14392)
|
|
115
|
-
agent_name = _resolve_agent(session_id, project_root)
|
|
116
|
-
|
|
117
|
-
# Check if Cyclist is running
|
|
118
|
-
if not is_cyclist_running(project_root):
|
|
119
|
-
# No Cyclist - pass through to Claude Code's built-in permissions
|
|
120
|
-
# Using "allow" so the hook doesn't override Claude Code's own
|
|
121
|
-
# permission system (settings.json allow lists still apply)
|
|
122
|
-
sys.exit(0)
|
|
123
|
-
|
|
124
|
-
# Load settings to check for auto-approval mode
|
|
125
|
-
settings = load_settings(project_root)
|
|
126
|
-
if settings.permission_mode == "accept":
|
|
127
|
-
# Auto-accept mode - approve everything
|
|
128
|
-
output_hook_response(HookResponse(
|
|
129
|
-
event_name="PreToolUse",
|
|
130
|
-
decision="allow",
|
|
131
|
-
reason="Auto-accept mode enabled",
|
|
132
|
-
))
|
|
133
|
-
sys.exit(0)
|
|
134
|
-
|
|
135
|
-
# Get context state for inclusion in request
|
|
136
|
-
context = get_context_state(project_root)
|
|
137
|
-
|
|
138
|
-
# Build request data
|
|
139
|
-
request_data = {
|
|
140
|
-
"toolName": tool_name,
|
|
141
|
-
"toolId": tool_id,
|
|
142
|
-
"input": tool_input,
|
|
143
|
-
"sessionId": session_id,
|
|
144
|
-
"context": {
|
|
145
|
-
"percentage": context.percentage,
|
|
146
|
-
"isHigh": context.is_high,
|
|
147
|
-
"isCritical": context.is_critical,
|
|
148
|
-
},
|
|
149
|
-
}
|
|
150
|
-
# Include agent identity if resolved (MSSCI-14392)
|
|
151
|
-
if agent_name:
|
|
152
|
-
request_data["agent"] = agent_name
|
|
153
|
-
|
|
154
|
-
# Send approval request to WheelHub with context info
|
|
155
|
-
response = send_to_cyclist(
|
|
156
|
-
endpoint="/api/hook-request",
|
|
157
|
-
data=request_data,
|
|
158
|
-
project_root=project_root,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
if response is None:
|
|
162
|
-
# Connection failed - defer to Claude Code
|
|
163
|
-
output_hook_response(HookResponse(
|
|
164
|
-
event_name="PreToolUse",
|
|
165
|
-
decision="ask",
|
|
166
|
-
reason="Could not connect to WheelHub",
|
|
167
|
-
))
|
|
168
|
-
sys.exit(0)
|
|
169
|
-
|
|
170
|
-
# Extract decision from response
|
|
171
|
-
decision = response.get("decision", "ask")
|
|
172
|
-
reason = response.get("reason", "")
|
|
173
|
-
data = response.get("data")
|
|
174
|
-
|
|
175
|
-
# Output decision
|
|
176
|
-
output_hook_response(HookResponse(
|
|
177
|
-
event_name="PreToolUse",
|
|
178
|
-
decision=decision,
|
|
179
|
-
reason=reason,
|
|
180
|
-
updated_input=data,
|
|
181
|
-
))
|
|
182
|
-
sys.exit(0)
|
|
183
|
-
|
|
184
|
-
except Exception as e:
|
|
185
|
-
# On error, output to stderr and exit with code 0 (allow)
|
|
186
|
-
# We don't want hook failures to block the user
|
|
187
|
-
print(f"[pretooluse-hook] Error: {e}", file=sys.stderr)
|
|
188
|
-
sys.exit(0)
|
|
189
|
-
|
|
7
|
+
from pennyfarthing_scripts.hooks.cyclist_pretooluse import main # noqa: F401
|
|
190
8
|
|
|
191
9
|
if __name__ == "__main__":
|
|
192
10
|
main()
|
|
Binary file
|