@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
|
@@ -14,13 +14,10 @@ Usage:
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import json
|
|
17
|
-
import math
|
|
18
17
|
import re
|
|
19
18
|
import subprocess
|
|
20
19
|
import sys
|
|
21
20
|
from dataclasses import dataclass, field
|
|
22
|
-
from pathlib import Path
|
|
23
|
-
|
|
24
21
|
|
|
25
22
|
# ── Section Categories ──────────────────────────────────────────────
|
|
26
23
|
|
|
@@ -204,7 +201,6 @@ def parse_sections(raw_output: str) -> list[Section]:
|
|
|
204
201
|
current_section_lines: list[str] = []
|
|
205
202
|
in_agent_def = False
|
|
206
203
|
in_behavior_guide = False
|
|
207
|
-
in_sidecar = False
|
|
208
204
|
|
|
209
205
|
def _flush_section(end_line: int) -> None:
|
|
210
206
|
"""Emit the current section."""
|
|
@@ -245,42 +241,36 @@ def parse_sections(raw_output: str) -> list[Section]:
|
|
|
245
241
|
current_section_name = "Workflow State"
|
|
246
242
|
in_agent_def = False
|
|
247
243
|
in_behavior_guide = False
|
|
248
|
-
in_sidecar = False
|
|
249
244
|
elif header_text.startswith("Agent Definition"):
|
|
250
245
|
current_component = "agent_definition"
|
|
251
246
|
current_section_category = "identity"
|
|
252
247
|
current_section_name = "Agent Definition"
|
|
253
248
|
in_agent_def = True
|
|
254
249
|
in_behavior_guide = False
|
|
255
|
-
in_sidecar = False
|
|
256
250
|
elif header_text.startswith("Persona:"):
|
|
257
251
|
current_component = "persona"
|
|
258
252
|
current_section_category = "persona"
|
|
259
253
|
current_section_name = header_text
|
|
260
254
|
in_agent_def = False
|
|
261
255
|
in_behavior_guide = False
|
|
262
|
-
in_sidecar = False
|
|
263
256
|
elif header_text.startswith("Agent Behavior Guide"):
|
|
264
257
|
current_component = "behavior_guide"
|
|
265
258
|
current_section_category = "shared"
|
|
266
259
|
current_section_name = "BG: Preamble"
|
|
267
260
|
in_agent_def = False
|
|
268
261
|
in_behavior_guide = True
|
|
269
|
-
in_sidecar = False
|
|
270
262
|
elif header_text.startswith("Sprint Context"):
|
|
271
263
|
current_component = "sprint_context"
|
|
272
264
|
current_section_category = "shared"
|
|
273
265
|
current_section_name = "Sprint Context"
|
|
274
266
|
in_agent_def = False
|
|
275
267
|
in_behavior_guide = False
|
|
276
|
-
in_sidecar = False
|
|
277
268
|
elif header_text.startswith("Repos Topology"):
|
|
278
269
|
current_component = "repos_topology"
|
|
279
270
|
current_section_category = "shared"
|
|
280
271
|
current_section_name = "Repos Topology"
|
|
281
272
|
in_agent_def = False
|
|
282
273
|
in_behavior_guide = False
|
|
283
|
-
in_sidecar = False
|
|
284
274
|
elif header_text.startswith("Agent Sidecar:"):
|
|
285
275
|
current_component = "sidecars"
|
|
286
276
|
current_section_category = "learned"
|
|
@@ -288,14 +278,12 @@ def parse_sections(raw_output: str) -> list[Section]:
|
|
|
288
278
|
current_section_name = f"Sidecar: {sidecar_file.replace('.md', '').title()}"
|
|
289
279
|
in_agent_def = False
|
|
290
280
|
in_behavior_guide = False
|
|
291
|
-
in_sidecar = True
|
|
292
281
|
elif header_text.startswith("Active Session:"):
|
|
293
282
|
current_component = "session"
|
|
294
283
|
current_section_category = "routing"
|
|
295
284
|
current_section_name = "Active Session"
|
|
296
285
|
in_agent_def = False
|
|
297
286
|
in_behavior_guide = False
|
|
298
|
-
in_sidecar = False
|
|
299
287
|
|
|
300
288
|
current_section_start = i
|
|
301
289
|
current_section_lines = [line]
|
|
@@ -347,7 +335,7 @@ def _pretty_tag(tag: str) -> str:
|
|
|
347
335
|
def capture_agent_output(agent_name: str) -> str:
|
|
348
336
|
"""Run pf agent start and capture raw output."""
|
|
349
337
|
result = subprocess.run(
|
|
350
|
-
["
|
|
338
|
+
[sys.executable, "-m", "pennyfarthing_scripts.cli", "agent", "start", agent_name],
|
|
351
339
|
capture_output=True,
|
|
352
340
|
text=True,
|
|
353
341
|
timeout=30,
|
|
@@ -484,10 +472,10 @@ def render_summary(heatmaps: list[AgentHeatmap]) -> str:
|
|
|
484
472
|
comp_labels = ["Route", "Ident", "Guard", "Proced", "Refer", "Perso", "Shared", "Learn"]
|
|
485
473
|
|
|
486
474
|
# Find max per category across all agents
|
|
487
|
-
cat_maxes: dict[str, int] =
|
|
475
|
+
cat_maxes: dict[str, int] = dict.fromkeys(components, 0)
|
|
488
476
|
agent_cats: dict[str, dict[str, int]] = {}
|
|
489
477
|
for hm in heatmaps:
|
|
490
|
-
agent_cats[hm.agent] =
|
|
478
|
+
agent_cats[hm.agent] = dict.fromkeys(components, 0)
|
|
491
479
|
for s in hm.sections:
|
|
492
480
|
agent_cats[hm.agent][s.category] = agent_cats[hm.agent].get(s.category, 0) + s.tokens
|
|
493
481
|
for c in components:
|
|
@@ -104,8 +104,9 @@ def parse_session_header(session_path: Path) -> dict[str, Any]:
|
|
|
104
104
|
|
|
105
105
|
if key == "workflow":
|
|
106
106
|
result["workflow"] = value.lower()
|
|
107
|
-
elif key
|
|
107
|
+
elif key in ("current phase", "phase"):
|
|
108
108
|
# Extract phase name, handling "(APPROVED)" suffix
|
|
109
|
+
# Matches both "**Current Phase:**" and "**Phase:**"
|
|
109
110
|
phase_match = re.match(r"(\w+)(?:\s*\(([^)]+)\))?", value)
|
|
110
111
|
if phase_match:
|
|
111
112
|
result["phase"] = phase_match.group(1).lower()
|
|
@@ -1,305 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
1
|
"""
|
|
3
|
-
|
|
2
|
+
Backward-compatibility shim — schema validation hook moved to hooks/schema_validation.py.
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
Blocks the tool call if validation fails, providing immediate feedback.
|
|
7
|
-
|
|
8
|
-
Usage in settings.json:
|
|
9
|
-
{
|
|
10
|
-
"hooks": {
|
|
11
|
-
"PreToolUse": [{
|
|
12
|
-
"matcher": "Write|Edit",
|
|
13
|
-
"hooks": [{
|
|
14
|
-
"type": "command",
|
|
15
|
-
"command": "python3 -m pennyfarthing_scripts.schema_validation_hook"
|
|
16
|
-
}]
|
|
17
|
-
}]
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
Story: XML Schema Migration Tools
|
|
4
|
+
This file will be removed in a future version.
|
|
22
5
|
"""
|
|
23
6
|
|
|
24
|
-
import
|
|
25
|
-
import sys
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
|
|
28
|
-
# Add parent directory to path for imports
|
|
29
|
-
sys.path.insert(0, str(Path(__file__).parent))
|
|
30
|
-
|
|
31
|
-
from hooks import (
|
|
32
|
-
HookResponse,
|
|
33
|
-
output_hook_response,
|
|
34
|
-
read_stdin_json,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
# =============================================================================
|
|
38
|
-
# File Type Detection
|
|
39
|
-
# =============================================================================
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def is_session_file(file_path: str) -> bool:
|
|
43
|
-
"""Check if path is a session file."""
|
|
44
|
-
return file_path.endswith("-session.md") and ".session/" in file_path
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def is_skill_file(file_path: str) -> bool:
|
|
48
|
-
"""Check if path is a skill SKILL.md file."""
|
|
49
|
-
return file_path.endswith("/SKILL.md") and "/skills/" in file_path
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def is_step_file(file_path: str) -> bool:
|
|
53
|
-
"""Check if path is a workflow step file."""
|
|
54
|
-
path = Path(file_path)
|
|
55
|
-
return (
|
|
56
|
-
path.name.startswith("step-")
|
|
57
|
-
and path.name.endswith(".md")
|
|
58
|
-
and "/workflows/" in file_path
|
|
59
|
-
and "/steps/" in file_path
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def get_file_type(file_path: str) -> str | None:
|
|
64
|
-
"""Detect file type from path."""
|
|
65
|
-
if is_session_file(file_path):
|
|
66
|
-
return "session"
|
|
67
|
-
elif is_skill_file(file_path):
|
|
68
|
-
return "skill"
|
|
69
|
-
elif is_step_file(file_path):
|
|
70
|
-
return "step"
|
|
71
|
-
return None
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
# =============================================================================
|
|
75
|
-
# Content-Based Validation (inline to avoid import issues)
|
|
76
|
-
# =============================================================================
|
|
77
|
-
|
|
78
|
-
# Skill required tags
|
|
79
|
-
SKILL_REQUIRED_TAGS = ["run", "output"]
|
|
80
|
-
|
|
81
|
-
# Workflow step required tags
|
|
82
|
-
STEP_REQUIRED_TAGS = ["purpose", "instructions", "output"]
|
|
83
|
-
|
|
84
|
-
# Step meta required fields
|
|
85
|
-
STEP_META_FIELDS = ["step", "workflow", "agent", "next"]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def _has_tag(content: str, tag: str) -> bool:
|
|
89
|
-
"""Check if content contains a specific XML tag."""
|
|
90
|
-
return f"<{tag}>" in content or f"<{tag} " in content
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def validate_session_content(content: str) -> list[str]:
|
|
94
|
-
"""Validate session file content.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
content: File content to validate
|
|
98
|
-
|
|
99
|
-
Returns:
|
|
100
|
-
List of error messages (empty if valid)
|
|
101
|
-
"""
|
|
102
|
-
errors = []
|
|
103
|
-
|
|
104
|
-
# Check for XML format indicator
|
|
105
|
-
if "<session" not in content:
|
|
106
|
-
# Old markdown format - warn but don't block during migration
|
|
107
|
-
return []
|
|
108
|
-
|
|
109
|
-
# Validate <session> root with attributes
|
|
110
|
-
if not re.search(r'<session\s+story="[^"]+"', content):
|
|
111
|
-
errors.append("Missing story attribute on <session>")
|
|
112
|
-
|
|
113
|
-
if not re.search(r'<session[^>]+workflow="[^"]+"', content):
|
|
114
|
-
errors.append("Missing workflow attribute on <session>")
|
|
115
|
-
|
|
116
|
-
# Validate <meta> section
|
|
117
|
-
if not _has_tag(content, "meta"):
|
|
118
|
-
errors.append("Missing <meta> section")
|
|
119
|
-
else:
|
|
120
|
-
if "<jira>" not in content:
|
|
121
|
-
errors.append("Missing <jira> in <meta>")
|
|
122
|
-
if "<started>" not in content:
|
|
123
|
-
errors.append("Missing <started> in <meta>")
|
|
124
|
-
|
|
125
|
-
# Validate <status> element
|
|
126
|
-
if not _has_tag(content, "status"):
|
|
127
|
-
errors.append("Missing <status> element")
|
|
128
|
-
else:
|
|
129
|
-
if 'phase="' not in content:
|
|
130
|
-
errors.append("Missing phase attribute on <status>")
|
|
131
|
-
|
|
132
|
-
return errors
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def validate_skill_content(content: str) -> list[str]:
|
|
136
|
-
"""Validate skill file content.
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
content: File content to validate
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
List of error messages (empty if valid)
|
|
143
|
-
"""
|
|
144
|
-
errors = []
|
|
145
|
-
|
|
146
|
-
# Check YAML frontmatter
|
|
147
|
-
if not content.startswith("---\n"):
|
|
148
|
-
errors.append("Missing YAML frontmatter")
|
|
149
|
-
else:
|
|
150
|
-
parts = content.split("---", 2)
|
|
151
|
-
if len(parts) >= 2:
|
|
152
|
-
frontmatter = parts[1]
|
|
153
|
-
if "name:" not in frontmatter:
|
|
154
|
-
errors.append("Missing 'name' in frontmatter")
|
|
155
|
-
if "description:" not in frontmatter:
|
|
156
|
-
errors.append("Missing 'description' in frontmatter")
|
|
157
|
-
|
|
158
|
-
# Check required tags
|
|
159
|
-
for tag in SKILL_REQUIRED_TAGS:
|
|
160
|
-
if not _has_tag(content, tag):
|
|
161
|
-
errors.append(f"Missing <{tag}> tag (required)")
|
|
162
|
-
|
|
163
|
-
return errors
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def validate_step_content(content: str) -> list[str]:
|
|
167
|
-
"""Validate workflow step file content.
|
|
168
|
-
|
|
169
|
-
Args:
|
|
170
|
-
content: File content to validate
|
|
171
|
-
|
|
172
|
-
Returns:
|
|
173
|
-
List of error messages (empty if valid)
|
|
174
|
-
"""
|
|
175
|
-
errors = []
|
|
176
|
-
|
|
177
|
-
# Check required tags
|
|
178
|
-
for tag in STEP_REQUIRED_TAGS:
|
|
179
|
-
if not _has_tag(content, tag):
|
|
180
|
-
errors.append(f"Missing <{tag}> tag")
|
|
181
|
-
|
|
182
|
-
# Check step-meta fields if tag exists
|
|
183
|
-
if _has_tag(content, "step-meta"):
|
|
184
|
-
meta_match = re.search(r"<step-meta>(.+?)</step-meta>", content, re.DOTALL)
|
|
185
|
-
if meta_match:
|
|
186
|
-
meta_content = meta_match.group(1)
|
|
187
|
-
for field_name in STEP_META_FIELDS:
|
|
188
|
-
if f"{field_name}:" not in meta_content:
|
|
189
|
-
errors.append(f"Missing '{field_name}' in step-meta")
|
|
190
|
-
|
|
191
|
-
return errors
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def validate_content(file_path: str, content: str) -> list[str]:
|
|
195
|
-
"""Validate content based on file type.
|
|
196
|
-
|
|
197
|
-
Args:
|
|
198
|
-
file_path: Path to file being written
|
|
199
|
-
content: Content being written
|
|
200
|
-
|
|
201
|
-
Returns:
|
|
202
|
-
List of error messages (empty if valid)
|
|
203
|
-
"""
|
|
204
|
-
file_type = get_file_type(file_path)
|
|
205
|
-
|
|
206
|
-
if file_type == "session":
|
|
207
|
-
return validate_session_content(content)
|
|
208
|
-
elif file_type == "skill":
|
|
209
|
-
return validate_skill_content(content)
|
|
210
|
-
elif file_type == "step":
|
|
211
|
-
return validate_step_content(content)
|
|
212
|
-
|
|
213
|
-
return []
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
# =============================================================================
|
|
217
|
-
# Hook Entry Point
|
|
218
|
-
# =============================================================================
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def main() -> None:
|
|
222
|
-
"""Main entry point for schema validation hook."""
|
|
223
|
-
try:
|
|
224
|
-
# Read tool data from Claude Code
|
|
225
|
-
tool_data = read_stdin_json()
|
|
226
|
-
|
|
227
|
-
tool_name = tool_data.get("tool_name", "")
|
|
228
|
-
tool_input = tool_data.get("tool_input", {})
|
|
229
|
-
|
|
230
|
-
# Only process Write and Edit tools
|
|
231
|
-
if tool_name not in ("Write", "Edit"):
|
|
232
|
-
output_hook_response(HookResponse(
|
|
233
|
-
event_name="PreToolUse",
|
|
234
|
-
decision="allow",
|
|
235
|
-
reason="Not a Write/Edit operation",
|
|
236
|
-
))
|
|
237
|
-
sys.exit(0)
|
|
238
|
-
|
|
239
|
-
# Get file path
|
|
240
|
-
file_path = tool_input.get("file_path", "")
|
|
241
|
-
if not file_path:
|
|
242
|
-
output_hook_response(HookResponse(
|
|
243
|
-
event_name="PreToolUse",
|
|
244
|
-
decision="allow",
|
|
245
|
-
reason="No file path",
|
|
246
|
-
))
|
|
247
|
-
sys.exit(0)
|
|
248
|
-
|
|
249
|
-
# Check if this is a file we care about
|
|
250
|
-
file_type = get_file_type(file_path)
|
|
251
|
-
if not file_type:
|
|
252
|
-
output_hook_response(HookResponse(
|
|
253
|
-
event_name="PreToolUse",
|
|
254
|
-
decision="allow",
|
|
255
|
-
reason="Not a session/skill/step file",
|
|
256
|
-
))
|
|
257
|
-
sys.exit(0)
|
|
258
|
-
|
|
259
|
-
# Get content to validate
|
|
260
|
-
# For Write: use 'content'
|
|
261
|
-
# For Edit: we need to simulate the edit result
|
|
262
|
-
if tool_name == "Write":
|
|
263
|
-
content = tool_input.get("content", "")
|
|
264
|
-
else:
|
|
265
|
-
# Edit operation - we can't easily validate without reading the file
|
|
266
|
-
# For now, allow Edits and validate on Write only
|
|
267
|
-
output_hook_response(HookResponse(
|
|
268
|
-
event_name="PreToolUse",
|
|
269
|
-
decision="allow",
|
|
270
|
-
reason="Edit operations validated post-hoc",
|
|
271
|
-
))
|
|
272
|
-
sys.exit(0)
|
|
273
|
-
|
|
274
|
-
# Validate content
|
|
275
|
-
errors = validate_content(file_path, content)
|
|
276
|
-
|
|
277
|
-
if errors:
|
|
278
|
-
# Block the write
|
|
279
|
-
error_msg = f"Schema validation failed for {file_type} file:\n"
|
|
280
|
-
error_msg += "\n".join(f" - {e}" for e in errors)
|
|
281
|
-
error_msg += f"\n\nFile: {file_path}"
|
|
282
|
-
|
|
283
|
-
output_hook_response(HookResponse(
|
|
284
|
-
event_name="PreToolUse",
|
|
285
|
-
decision="deny",
|
|
286
|
-
reason=error_msg,
|
|
287
|
-
))
|
|
288
|
-
sys.exit(0)
|
|
289
|
-
|
|
290
|
-
# Validation passed
|
|
291
|
-
output_hook_response(HookResponse(
|
|
292
|
-
event_name="PreToolUse",
|
|
293
|
-
decision="allow",
|
|
294
|
-
reason=f"Schema validation passed for {file_type} file",
|
|
295
|
-
))
|
|
296
|
-
sys.exit(0)
|
|
297
|
-
|
|
298
|
-
except Exception as e:
|
|
299
|
-
# On error, allow to avoid blocking legitimate work
|
|
300
|
-
print(f"[schema-validation-hook] Error: {e}", file=sys.stderr)
|
|
301
|
-
sys.exit(0)
|
|
302
|
-
|
|
7
|
+
from pennyfarthing_scripts.hooks.schema_validation import main # noqa: F401
|
|
303
8
|
|
|
304
9
|
if __name__ == "__main__":
|
|
305
10
|
main()
|
|
@@ -1,192 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Session start hook — initialize environment for Claude Code session.
|
|
3
|
-
|
|
4
|
-
Handles:
|
|
5
|
-
1. Session directory setup and logging
|
|
6
|
-
2. Checkpoint validation (cross-session drift detection)
|
|
7
|
-
3. WheelHub auto-start (ensure BikeRack server is running)
|
|
8
|
-
4. OTEL auto-configuration via CLAUDE_ENV_FILE
|
|
9
|
-
|
|
10
|
-
Called by Claude Code SessionStart hook via shell wrapper.
|
|
11
1
|
"""
|
|
2
|
+
Backward-compatibility shim — session start hook moved to hooks/session_start.py.
|
|
12
3
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import json
|
|
16
|
-
import os
|
|
17
|
-
import sys
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _read_input() -> dict:
|
|
22
|
-
"""Read JSON from stdin (hook protocol)."""
|
|
23
|
-
raw = sys.stdin.read()
|
|
24
|
-
try:
|
|
25
|
-
return json.loads(raw)
|
|
26
|
-
except (json.JSONDecodeError, ValueError):
|
|
27
|
-
return {}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _setup_session_dir(project_dir: Path, session_id: str, source_type: str) -> None:
|
|
31
|
-
"""Ensure .session directory exists and log session start."""
|
|
32
|
-
session_dir = project_dir / ".session"
|
|
33
|
-
session_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
-
(session_dir / "agents").mkdir(exist_ok=True)
|
|
35
|
-
|
|
36
|
-
from datetime import UTC, datetime
|
|
37
|
-
|
|
38
|
-
timestamp = datetime.now(UTC).isoformat()
|
|
39
|
-
log_file = session_dir / "session-log.txt"
|
|
40
|
-
with open(log_file, "a") as f:
|
|
41
|
-
f.write(f"{timestamp} | Session {source_type}: {session_id}\n")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _validate_checkpoint(project_dir: Path) -> None:
|
|
45
|
-
"""Validate previous session checkpoint for cross-session drift detection."""
|
|
46
|
-
try:
|
|
47
|
-
import subprocess
|
|
48
|
-
|
|
49
|
-
script_dir = Path(__file__).resolve().parent.parent
|
|
50
|
-
checkpoint_lib = script_dir / "pennyfarthing-dist" / "scripts" / "lib" / "checkpoint.sh"
|
|
51
|
-
|
|
52
|
-
if not checkpoint_lib.exists():
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
result = subprocess.run(
|
|
56
|
-
["bash", "-c", f'source "{checkpoint_lib}" && checkpoint_restore session_state'],
|
|
57
|
-
capture_output=True,
|
|
58
|
-
text=True,
|
|
59
|
-
cwd=str(project_dir),
|
|
60
|
-
timeout=5,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
prev_state = result.stdout.strip()
|
|
64
|
-
if not prev_state:
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
# Parse checkpoint data
|
|
68
|
-
fields = {}
|
|
69
|
-
for part in prev_state.split(";"):
|
|
70
|
-
if "=" in part:
|
|
71
|
-
k, v = part.split("=", 1)
|
|
72
|
-
fields[k] = v
|
|
73
|
-
|
|
74
|
-
prev_sha = fields.get("sha", "")
|
|
75
|
-
if not prev_sha:
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
# Get current git SHA
|
|
79
|
-
result = subprocess.run(
|
|
80
|
-
["git", "rev-parse", "--short", "HEAD"],
|
|
81
|
-
capture_output=True,
|
|
82
|
-
text=True,
|
|
83
|
-
cwd=str(project_dir),
|
|
84
|
-
timeout=5,
|
|
85
|
-
)
|
|
86
|
-
current_sha = result.stdout.strip()
|
|
87
|
-
if not current_sha or current_sha == prev_sha:
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
# Log drift
|
|
91
|
-
from datetime import UTC, datetime
|
|
92
|
-
|
|
93
|
-
timestamp = datetime.now(UTC).isoformat()
|
|
94
|
-
session_dir = project_dir / ".session"
|
|
95
|
-
|
|
96
|
-
warning = f"CROSS_SESSION_DRIFT: Git changed (was: {prev_sha}, now: {current_sha})"
|
|
97
|
-
if fields.get("story"):
|
|
98
|
-
warning += f" | Story: {fields['story']}"
|
|
99
|
-
if fields.get("agent"):
|
|
100
|
-
warning += f" | Agent: {fields['agent']}"
|
|
101
|
-
|
|
102
|
-
with open(session_dir / "session-log.txt", "a") as f:
|
|
103
|
-
f.write(f"{timestamp} | {warning}\n")
|
|
104
|
-
|
|
105
|
-
drift_entry = f"{timestamp} | prev_sha={prev_sha} | current_sha={current_sha}"
|
|
106
|
-
for key in ("story", "agent", "phase"):
|
|
107
|
-
if fields.get(key):
|
|
108
|
-
drift_entry += f" | {key}={fields[key]}"
|
|
109
|
-
with open(session_dir / "drift-log.txt", "a") as f:
|
|
110
|
-
f.write(drift_entry + "\n")
|
|
111
|
-
|
|
112
|
-
except Exception:
|
|
113
|
-
pass
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def _ensure_wheelhub(project_dir: Path) -> int | None:
|
|
117
|
-
"""Auto-start WheelHub if not already running. Returns port or None."""
|
|
118
|
-
from pennyfarthing_scripts.bikerack.launcher import (
|
|
119
|
-
is_already_running,
|
|
120
|
-
poll_for_port_file,
|
|
121
|
-
start_wheelhub,
|
|
122
|
-
write_pid_file,
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
# Skip if full Cyclist is running
|
|
126
|
-
cyclist_port_file = project_dir / ".wheelhub-port"
|
|
127
|
-
if cyclist_port_file.exists():
|
|
128
|
-
try:
|
|
129
|
-
return int(cyclist_port_file.read_text().strip())
|
|
130
|
-
except (ValueError, OSError):
|
|
131
|
-
return None
|
|
132
|
-
|
|
133
|
-
# Check if BikeRack WheelHub is already running
|
|
134
|
-
running, _pid, port = is_already_running(project_dir)
|
|
135
|
-
if running:
|
|
136
|
-
return port
|
|
137
|
-
|
|
138
|
-
# Start WheelHub
|
|
139
|
-
try:
|
|
140
|
-
proc = start_wheelhub(project_dir)
|
|
141
|
-
write_pid_file(project_dir, proc.pid)
|
|
142
|
-
return poll_for_port_file(project_dir)
|
|
143
|
-
except Exception:
|
|
144
|
-
return None
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def _write_env_file(project_dir: Path, session_id: str, otel_port: int | None) -> None:
|
|
148
|
-
"""Write environment variables to CLAUDE_ENV_FILE."""
|
|
149
|
-
env_file = os.environ.get("CLAUDE_ENV_FILE")
|
|
150
|
-
if not env_file:
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
lines = [
|
|
154
|
-
"# Pennyfarthing core environment",
|
|
155
|
-
f'export PROJECT_ROOT="{project_dir}"',
|
|
156
|
-
f'export SESSION_ID="{session_id}"',
|
|
157
|
-
]
|
|
158
|
-
|
|
159
|
-
if otel_port is not None:
|
|
160
|
-
lines.extend([
|
|
161
|
-
"# OTEL auto-configuration for Cyclist/WheelHub",
|
|
162
|
-
'export OTEL_EXPORTER_OTLP_PROTOCOL="http/json"',
|
|
163
|
-
f'export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:{otel_port}"',
|
|
164
|
-
])
|
|
165
|
-
|
|
166
|
-
with open(env_file, "a") as f:
|
|
167
|
-
f.write("\n".join(lines) + "\n")
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def main() -> None:
|
|
171
|
-
"""Entry point for SessionStart hook."""
|
|
172
|
-
try:
|
|
173
|
-
input_data = _read_input()
|
|
174
|
-
session_id = input_data.get("session_id", "unknown")
|
|
175
|
-
source_type = input_data.get("source", "unknown")
|
|
176
|
-
|
|
177
|
-
project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()))
|
|
178
|
-
|
|
179
|
-
_setup_session_dir(project_dir, session_id, source_type)
|
|
180
|
-
_validate_checkpoint(project_dir)
|
|
181
|
-
|
|
182
|
-
otel_port = _ensure_wheelhub(project_dir)
|
|
183
|
-
_write_env_file(project_dir, session_id, otel_port)
|
|
184
|
-
|
|
185
|
-
except Exception:
|
|
186
|
-
pass
|
|
187
|
-
|
|
188
|
-
sys.exit(0)
|
|
4
|
+
This file will be removed in a future version.
|
|
5
|
+
"""
|
|
189
6
|
|
|
7
|
+
from pennyfarthing_scripts.hooks.session_start import main # noqa: F401
|
|
190
8
|
|
|
191
9
|
if __name__ == "__main__":
|
|
192
10
|
main()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|