@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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Tests for Story 86-15 AC4: workflow list shows team-enabled workflows with indicator.
|
|
2
|
+
|
|
3
|
+
Story: 86-15 — Team-enabled workflow templates
|
|
4
|
+
Epic: 86 — Agent Collaboration: Tandem to Teams
|
|
5
|
+
|
|
6
|
+
Acceptance Criteria:
|
|
7
|
+
- [AC4] /workflow list shows team-enabled workflows with indicator
|
|
8
|
+
|
|
9
|
+
The `pf workflow list` command should visually distinguish team-enabled
|
|
10
|
+
workflows from regular and tandem workflows. When a workflow has `team:`
|
|
11
|
+
blocks on any phase, the output must include an indicator.
|
|
12
|
+
|
|
13
|
+
These tests verify the CLI output format. Tests should fail until
|
|
14
|
+
both the workflow templates (tdd-team.yaml, bdd-team.yaml) and the
|
|
15
|
+
workflow list team indicator are implemented.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from unittest.mock import patch
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
import yaml
|
|
25
|
+
from click.testing import CliRunner
|
|
26
|
+
|
|
27
|
+
from pennyfarthing_scripts.cli import cli
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestWorkflowListTeamIndicator:
|
|
31
|
+
"""Tests for team indicator in workflow list output (AC4)."""
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def runner(self) -> CliRunner:
|
|
35
|
+
return CliRunner()
|
|
36
|
+
|
|
37
|
+
def test_workflow_list_shows_team_workflows(self, runner: CliRunner) -> None:
|
|
38
|
+
"""AC4: workflow list should include tdd-team and bdd-team."""
|
|
39
|
+
result = runner.invoke(cli, ["workflow", "list"])
|
|
40
|
+
assert result.exit_code == 0
|
|
41
|
+
assert "tdd-team" in result.output, "tdd-team workflow should appear in list"
|
|
42
|
+
assert "bdd-team" in result.output, "bdd-team workflow should appear in list"
|
|
43
|
+
|
|
44
|
+
def test_workflow_list_has_team_indicator_for_tdd_team(
|
|
45
|
+
self, runner: CliRunner
|
|
46
|
+
) -> None:
|
|
47
|
+
"""AC4: tdd-team should have a team indicator in workflow list."""
|
|
48
|
+
result = runner.invoke(cli, ["workflow", "list"])
|
|
49
|
+
assert result.exit_code == 0
|
|
50
|
+
|
|
51
|
+
# Find the tdd-team row in the markdown table
|
|
52
|
+
tdd_team_line = None
|
|
53
|
+
for line in result.output.splitlines():
|
|
54
|
+
if "tdd-team" in line and "|" in line:
|
|
55
|
+
tdd_team_line = line
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
assert tdd_team_line is not None, "tdd-team should appear as a table row"
|
|
59
|
+
|
|
60
|
+
# The row should have a team indicator — could be a column value,
|
|
61
|
+
# emoji, or tag that distinguishes it from non-team workflows
|
|
62
|
+
line_lower = tdd_team_line.lower()
|
|
63
|
+
assert (
|
|
64
|
+
"team" in line_lower
|
|
65
|
+
), "tdd-team row should contain a 'team' indicator"
|
|
66
|
+
|
|
67
|
+
def test_workflow_list_has_team_indicator_for_bdd_team(
|
|
68
|
+
self, runner: CliRunner
|
|
69
|
+
) -> None:
|
|
70
|
+
"""AC4: bdd-team should have a team indicator in workflow list."""
|
|
71
|
+
result = runner.invoke(cli, ["workflow", "list"])
|
|
72
|
+
assert result.exit_code == 0
|
|
73
|
+
|
|
74
|
+
bdd_team_line = None
|
|
75
|
+
for line in result.output.splitlines():
|
|
76
|
+
if "bdd-team" in line and "|" in line:
|
|
77
|
+
bdd_team_line = line
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
assert bdd_team_line is not None, "bdd-team should appear as a table row"
|
|
81
|
+
|
|
82
|
+
line_lower = bdd_team_line.lower()
|
|
83
|
+
assert (
|
|
84
|
+
"team" in line_lower
|
|
85
|
+
), "bdd-team row should contain a 'team' indicator"
|
|
86
|
+
|
|
87
|
+
def test_non_team_workflows_lack_team_indicator(
|
|
88
|
+
self, runner: CliRunner
|
|
89
|
+
) -> None:
|
|
90
|
+
"""AC4: Regular workflows should NOT have team indicator."""
|
|
91
|
+
result = runner.invoke(cli, ["workflow", "list"])
|
|
92
|
+
assert result.exit_code == 0
|
|
93
|
+
|
|
94
|
+
for line in result.output.splitlines():
|
|
95
|
+
if "|" not in line:
|
|
96
|
+
continue
|
|
97
|
+
# Skip header, separator, and team workflows
|
|
98
|
+
if "tdd-team" in line or "bdd-team" in line:
|
|
99
|
+
continue
|
|
100
|
+
if "Workflow" in line or "---" in line:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Check columns (split by |)
|
|
104
|
+
cols = [c.strip() for c in line.split("|")]
|
|
105
|
+
name_col = cols[1] if len(cols) > 1 else ""
|
|
106
|
+
|
|
107
|
+
# tdd-tandem and bdd-tandem are tandem, not team
|
|
108
|
+
if "tandem" in name_col:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
# Plain workflows like tdd, trivial, bdd should not show team
|
|
112
|
+
# in their mode/indicator columns (but "team" might appear
|
|
113
|
+
# in description text, which is fine — we check indicator columns)
|
|
114
|
+
# The name itself (e.g., "tdd") doesn't contain "team", confirming
|
|
115
|
+
# no false positives in the name column
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestWorkflowListTeamColumnOrTag:
|
|
119
|
+
"""Verify the team indicator mechanism in workflow list."""
|
|
120
|
+
|
|
121
|
+
@pytest.fixture
|
|
122
|
+
def runner(self) -> CliRunner:
|
|
123
|
+
return CliRunner()
|
|
124
|
+
|
|
125
|
+
def test_workflow_list_distinguishes_team_from_tandem(
|
|
126
|
+
self, runner: CliRunner
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Team workflows should be distinguishable from tandem workflows."""
|
|
129
|
+
result = runner.invoke(cli, ["workflow", "list"])
|
|
130
|
+
assert result.exit_code == 0
|
|
131
|
+
|
|
132
|
+
tdd_team_line = None
|
|
133
|
+
tdd_tandem_line = None
|
|
134
|
+
for line in result.output.splitlines():
|
|
135
|
+
if "tdd-team" in line and "|" in line:
|
|
136
|
+
tdd_team_line = line
|
|
137
|
+
elif "tdd-tandem" in line and "|" in line:
|
|
138
|
+
tdd_tandem_line = line
|
|
139
|
+
|
|
140
|
+
assert tdd_team_line is not None, "tdd-team should be in the list"
|
|
141
|
+
assert tdd_tandem_line is not None, "tdd-tandem should be in the list"
|
|
142
|
+
|
|
143
|
+
# They should have different indicators — team vs tandem
|
|
144
|
+
# At minimum, the descriptions should be distinct
|
|
145
|
+
assert tdd_team_line != tdd_tandem_line, (
|
|
146
|
+
"tdd-team and tdd-tandem should have different rows"
|
|
147
|
+
)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Team-mode protocol validator adapter.
|
|
2
|
+
|
|
3
|
+
Validates that agent definitions include proper team-mode sections
|
|
4
|
+
for Claude Code native Agent Teams integration.
|
|
5
|
+
|
|
6
|
+
Story: MSSCI-15109 (86-14)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from pennyfarthing_scripts.validate import ValidateReport
|
|
15
|
+
|
|
16
|
+
# Regex to extract <team-mode> section content
|
|
17
|
+
_TEAM_MODE_RE = re.compile(r"<team-mode>(.*?)</team-mode>", re.DOTALL)
|
|
18
|
+
|
|
19
|
+
# Required topics in the behavior guide's <team-mode> section
|
|
20
|
+
_BEHAVIOR_GUIDE_TOPICS = {
|
|
21
|
+
"team_creation": ["TeamCreate", "team creation", "create team"],
|
|
22
|
+
"spawning": ["spawn", "Task tool", "teammate"],
|
|
23
|
+
"sendmessage": ["SendMessage"],
|
|
24
|
+
"cleanup": ["cleanup", "TeamDelete", "shut down", "shutdown"],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Required lead agent topics
|
|
28
|
+
_LEAD_TOPICS = {
|
|
29
|
+
"phase_entry": ["phase entry", "phase start", "on phase"],
|
|
30
|
+
"spawn_per_yaml": ["spawn", "teammates", "workflow YAML", "YAML"],
|
|
31
|
+
"shutdown_before_exit": ["shut down", "shutdown", "before exit", "before handoff"],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Required teammate awareness topics
|
|
35
|
+
_TEAMMATE_TOPICS = {
|
|
36
|
+
"identity": ["teammate", "not lead", "not the lead"],
|
|
37
|
+
"sendmessage": ["SendMessage"],
|
|
38
|
+
"idle": ["idle", "go idle"],
|
|
39
|
+
"shutdown_response": ["shutdown", "shutdown_response", "shutdown request"],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def extract_team_mode_section(content: str) -> str | None:
|
|
44
|
+
"""Extract content between <team-mode> tags."""
|
|
45
|
+
m = _TEAM_MODE_RE.search(content)
|
|
46
|
+
return m.group(1) if m else None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_behavior_guide_team_mode(
|
|
50
|
+
path: Path,
|
|
51
|
+
) -> tuple[list[str], list[str]]:
|
|
52
|
+
"""Validate the behavior guide has a <team-mode> section with required topics.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
(errors, warnings) — two lists of message strings.
|
|
56
|
+
"""
|
|
57
|
+
errors: list[str] = []
|
|
58
|
+
warnings: list[str] = []
|
|
59
|
+
content = path.read_text()
|
|
60
|
+
section = extract_team_mode_section(content)
|
|
61
|
+
|
|
62
|
+
if section is None:
|
|
63
|
+
errors.append("Missing <team-mode> section in behavior guide")
|
|
64
|
+
return errors, warnings
|
|
65
|
+
|
|
66
|
+
stripped = section.strip()
|
|
67
|
+
if not stripped:
|
|
68
|
+
errors.append("Empty <team-mode> section — no content")
|
|
69
|
+
return errors, warnings
|
|
70
|
+
|
|
71
|
+
# Check required topics
|
|
72
|
+
for topic_name, keywords in _BEHAVIOR_GUIDE_TOPICS.items():
|
|
73
|
+
if not any(kw.lower() in section.lower() for kw in keywords):
|
|
74
|
+
errors.append(
|
|
75
|
+
f"<team-mode> section missing required topic: {topic_name} "
|
|
76
|
+
f"(expected one of: {', '.join(keywords)})"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return errors, warnings
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def validate_lead_agent_team_mode(
|
|
83
|
+
path: Path,
|
|
84
|
+
) -> tuple[list[str], list[str]]:
|
|
85
|
+
"""Validate lead agent has team-mode behavior section.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
(errors, warnings) — two lists of message strings.
|
|
89
|
+
"""
|
|
90
|
+
errors: list[str] = []
|
|
91
|
+
warnings: list[str] = []
|
|
92
|
+
content = path.read_text()
|
|
93
|
+
section = extract_team_mode_section(content)
|
|
94
|
+
|
|
95
|
+
if section is None:
|
|
96
|
+
errors.append(f"Missing <team-mode> section in lead agent {path.name}")
|
|
97
|
+
return errors, warnings
|
|
98
|
+
|
|
99
|
+
stripped = section.strip()
|
|
100
|
+
if not stripped:
|
|
101
|
+
errors.append(f"Empty <team-mode> section in lead agent {path.name}")
|
|
102
|
+
return errors, warnings
|
|
103
|
+
|
|
104
|
+
section_lower = section.lower()
|
|
105
|
+
|
|
106
|
+
# Must reference lead role
|
|
107
|
+
if "lead" not in section_lower:
|
|
108
|
+
errors.append("Lead agent <team-mode> section must reference 'lead' role")
|
|
109
|
+
|
|
110
|
+
# Check lead-specific topics
|
|
111
|
+
for topic_name, keywords in _LEAD_TOPICS.items():
|
|
112
|
+
if not any(kw.lower() in section_lower for kw in keywords):
|
|
113
|
+
warnings.append(
|
|
114
|
+
f"Lead agent <team-mode> missing topic: {topic_name} "
|
|
115
|
+
f"(expected one of: {', '.join(keywords)})"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return errors, warnings
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def validate_teammate_awareness(
|
|
122
|
+
content: str,
|
|
123
|
+
) -> tuple[list[str], list[str]]:
|
|
124
|
+
"""Validate teammate behavior content in team-mode section.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
(errors, warnings) — two lists of message strings.
|
|
128
|
+
"""
|
|
129
|
+
errors: list[str] = []
|
|
130
|
+
warnings: list[str] = []
|
|
131
|
+
section = extract_team_mode_section(content)
|
|
132
|
+
|
|
133
|
+
if section is None:
|
|
134
|
+
errors.append("Missing <team-mode> section for teammate awareness")
|
|
135
|
+
return errors, warnings
|
|
136
|
+
|
|
137
|
+
section_lower = section.lower()
|
|
138
|
+
|
|
139
|
+
# Check teammate-specific topics
|
|
140
|
+
for topic_name, keywords in _TEAMMATE_TOPICS.items():
|
|
141
|
+
if not any(kw.lower() in section_lower for kw in keywords):
|
|
142
|
+
errors.append(
|
|
143
|
+
f"Teammate awareness missing topic: {topic_name} "
|
|
144
|
+
f"(expected one of: {', '.join(keywords)})"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return errors, warnings
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def validate_exit_protocol_team_branch(
|
|
151
|
+
content: str,
|
|
152
|
+
) -> tuple[list[str], list[str]]:
|
|
153
|
+
"""Validate exit protocol has team-mode cleanup branch.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
(errors, warnings) — two lists of message strings.
|
|
157
|
+
"""
|
|
158
|
+
errors: list[str] = []
|
|
159
|
+
warnings: list[str] = []
|
|
160
|
+
|
|
161
|
+
# Look for team cleanup in exit protocol section
|
|
162
|
+
# The exit protocol is in <agent-exit-protocol> tags
|
|
163
|
+
exit_re = re.compile(
|
|
164
|
+
r"<agent-exit-protocol>(.*?)</agent-exit-protocol>", re.DOTALL
|
|
165
|
+
)
|
|
166
|
+
exit_match = exit_re.search(content)
|
|
167
|
+
|
|
168
|
+
if exit_match is None:
|
|
169
|
+
errors.append("Missing <agent-exit-protocol> section")
|
|
170
|
+
return errors, warnings
|
|
171
|
+
|
|
172
|
+
exit_content = exit_match.group(1).lower()
|
|
173
|
+
|
|
174
|
+
# Must reference team cleanup
|
|
175
|
+
team_cleanup_terms = ["team", "teamdelete", "cleanup team", "shut down teammate"]
|
|
176
|
+
if not any(term in exit_content for term in team_cleanup_terms):
|
|
177
|
+
errors.append(
|
|
178
|
+
"Exit protocol missing team-mode branch "
|
|
179
|
+
"(must reference team cleanup before handoff)"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return errors, warnings
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def validate_communication_protocols(
|
|
186
|
+
content: str,
|
|
187
|
+
) -> tuple[list[str], list[str]]:
|
|
188
|
+
"""Validate that reflector markers and SendMessage are properly documented.
|
|
189
|
+
|
|
190
|
+
Reflector markers for inter-phase handoff (unchanged).
|
|
191
|
+
SendMessage for intra-phase teammate communication (new).
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
(errors, warnings) — two lists of message strings.
|
|
195
|
+
"""
|
|
196
|
+
errors: list[str] = []
|
|
197
|
+
warnings: list[str] = []
|
|
198
|
+
|
|
199
|
+
# Reflector section must still exist
|
|
200
|
+
if "<critical>" not in content or "CYCLIST" not in content:
|
|
201
|
+
errors.append("Reflector/CYCLIST marker section missing — must remain for inter-phase handoff")
|
|
202
|
+
|
|
203
|
+
# Team-mode section must reference SendMessage for intra-phase
|
|
204
|
+
section = extract_team_mode_section(content)
|
|
205
|
+
if section is not None:
|
|
206
|
+
if "SendMessage" not in section:
|
|
207
|
+
errors.append(
|
|
208
|
+
"<team-mode> must reference SendMessage for intra-phase communication"
|
|
209
|
+
)
|
|
210
|
+
# Should distinguish inter-phase (markers) from intra-phase (SendMessage)
|
|
211
|
+
if "inter-phase" not in section.lower() and "intra-phase" not in section.lower():
|
|
212
|
+
warnings.append(
|
|
213
|
+
"<team-mode> should distinguish inter-phase (markers) "
|
|
214
|
+
"from intra-phase (SendMessage) communication"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return errors, warnings
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def classify_team_mode_agents(
|
|
221
|
+
agents_dir: Path,
|
|
222
|
+
) -> tuple[list[Path], list[Path]]:
|
|
223
|
+
"""Classify agent files into lead agents and all agents with team-mode.
|
|
224
|
+
|
|
225
|
+
Lead agents are those whose <team-mode> section mentions 'lead'.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
(lead_agents, all_team_mode_agents)
|
|
229
|
+
"""
|
|
230
|
+
leads: list[Path] = []
|
|
231
|
+
all_tm: list[Path] = []
|
|
232
|
+
|
|
233
|
+
for f in sorted(agents_dir.glob("*.md")):
|
|
234
|
+
if f.name == "README.md":
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
content = f.read_text()
|
|
238
|
+
section = extract_team_mode_section(content)
|
|
239
|
+
if section is None:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
all_tm.append(f)
|
|
243
|
+
if "lead" in section.lower():
|
|
244
|
+
leads.append(f)
|
|
245
|
+
|
|
246
|
+
return leads, all_tm
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def run(
|
|
250
|
+
root: Path, *, fix: bool = False, strict: bool = False
|
|
251
|
+
) -> ValidateReport:
|
|
252
|
+
"""Validate agent team-mode protocol sections."""
|
|
253
|
+
report = ValidateReport(validator="team-mode")
|
|
254
|
+
|
|
255
|
+
# Validate behavior guide
|
|
256
|
+
guides_dir = root / "pennyfarthing-dist" / "guides"
|
|
257
|
+
behavior_guide = guides_dir / "agent-behavior.md"
|
|
258
|
+
if behavior_guide.is_file():
|
|
259
|
+
file_errors, file_warnings = validate_behavior_guide_team_mode(behavior_guide)
|
|
260
|
+
for e in file_errors:
|
|
261
|
+
report.errors += 1
|
|
262
|
+
report.details.append(f"[ERROR] agent-behavior.md: {e}")
|
|
263
|
+
for w in file_warnings:
|
|
264
|
+
if strict:
|
|
265
|
+
report.errors += 1
|
|
266
|
+
report.details.append(f"[ERROR] agent-behavior.md: {w}")
|
|
267
|
+
else:
|
|
268
|
+
report.warnings += 1
|
|
269
|
+
report.details.append(f"[WARN] agent-behavior.md: {w}")
|
|
270
|
+
if not file_errors:
|
|
271
|
+
report.passed += 1
|
|
272
|
+
|
|
273
|
+
# Validate exit protocol team branch
|
|
274
|
+
guide_content = behavior_guide.read_text()
|
|
275
|
+
exit_errors, exit_warnings = validate_exit_protocol_team_branch(guide_content)
|
|
276
|
+
for e in exit_errors:
|
|
277
|
+
report.errors += 1
|
|
278
|
+
report.details.append(f"[ERROR] agent-behavior.md exit: {e}")
|
|
279
|
+
for w in exit_warnings:
|
|
280
|
+
if strict:
|
|
281
|
+
report.errors += 1
|
|
282
|
+
report.details.append(f"[ERROR] agent-behavior.md exit: {w}")
|
|
283
|
+
else:
|
|
284
|
+
report.warnings += 1
|
|
285
|
+
report.details.append(f"[WARN] agent-behavior.md exit: {w}")
|
|
286
|
+
|
|
287
|
+
# Validate communication protocol distinction
|
|
288
|
+
comm_errors, comm_warnings = validate_communication_protocols(guide_content)
|
|
289
|
+
for e in comm_errors:
|
|
290
|
+
report.errors += 1
|
|
291
|
+
report.details.append(f"[ERROR] agent-behavior.md comm: {e}")
|
|
292
|
+
for w in comm_warnings:
|
|
293
|
+
if strict:
|
|
294
|
+
report.errors += 1
|
|
295
|
+
report.details.append(f"[ERROR] agent-behavior.md comm: {w}")
|
|
296
|
+
else:
|
|
297
|
+
report.warnings += 1
|
|
298
|
+
report.details.append(f"[WARN] agent-behavior.md comm: {w}")
|
|
299
|
+
else:
|
|
300
|
+
report.errors += 1
|
|
301
|
+
report.details.append("[ERROR] agent-behavior.md guide not found")
|
|
302
|
+
|
|
303
|
+
# Validate lead agents
|
|
304
|
+
agents_dir = root / "pennyfarthing-dist" / "agents"
|
|
305
|
+
if agents_dir.is_dir():
|
|
306
|
+
for agent_name in ("dev", "reviewer"):
|
|
307
|
+
agent_path = agents_dir / f"{agent_name}.md"
|
|
308
|
+
if agent_path.is_file():
|
|
309
|
+
lead_errors, lead_warnings = validate_lead_agent_team_mode(agent_path)
|
|
310
|
+
for e in lead_errors:
|
|
311
|
+
report.errors += 1
|
|
312
|
+
report.details.append(f"[ERROR] {agent_name}.md: {e}")
|
|
313
|
+
for w in lead_warnings:
|
|
314
|
+
if strict:
|
|
315
|
+
report.errors += 1
|
|
316
|
+
report.details.append(f"[ERROR] {agent_name}.md: {w}")
|
|
317
|
+
else:
|
|
318
|
+
report.warnings += 1
|
|
319
|
+
report.details.append(f"[WARN] {agent_name}.md: {w}")
|
|
320
|
+
if not lead_errors:
|
|
321
|
+
report.passed += 1
|
|
322
|
+
|
|
323
|
+
return report
|
|
@@ -131,6 +131,7 @@ def validate_phased(
|
|
|
131
131
|
return errors, warnings
|
|
132
132
|
|
|
133
133
|
seen_names: set[str] = set()
|
|
134
|
+
next_refs: list[tuple[str, str]] = [] # (phase_name, next_target)
|
|
134
135
|
|
|
135
136
|
for i, phase in enumerate(phases):
|
|
136
137
|
if not isinstance(phase, dict):
|
|
@@ -228,6 +229,24 @@ def validate_phased(
|
|
|
228
229
|
f"Phase '{label}' tandem triggers must be a list"
|
|
229
230
|
)
|
|
230
231
|
|
|
232
|
+
# next: directive (optional, must be string)
|
|
233
|
+
next_target = phase.get("next")
|
|
234
|
+
if next_target is not None:
|
|
235
|
+
if not isinstance(next_target, str):
|
|
236
|
+
errors.append(
|
|
237
|
+
f"Phase '{phase_name or i}' next must be a string"
|
|
238
|
+
)
|
|
239
|
+
elif phase_name:
|
|
240
|
+
next_refs.append((phase_name, next_target))
|
|
241
|
+
|
|
242
|
+
# Cross-validate next: references point to existing phase names
|
|
243
|
+
for source_phase, target_phase in next_refs:
|
|
244
|
+
if target_phase not in seen_names:
|
|
245
|
+
errors.append(
|
|
246
|
+
f"Phase '{source_phase}' next references unknown phase: "
|
|
247
|
+
f"'{target_phase}'"
|
|
248
|
+
)
|
|
249
|
+
|
|
231
250
|
return errors, warnings
|
|
232
251
|
|
|
233
252
|
|
|
@@ -1,156 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
1
|
"""
|
|
3
|
-
|
|
2
|
+
Backward-compatibility shim — welcome hook moved to hooks/session_start.py.
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
For CLI: Displays ASCII art of a penny-farthing bicycle
|
|
8
|
-
For Cyclist: Sends WebSocket message to display logo and welcome
|
|
9
|
-
|
|
10
|
-
Called by Claude Code SessionStart hook.
|
|
11
|
-
|
|
12
|
-
Story: MSSCI-12409 - Hook consistency and WheelHub consolidation
|
|
4
|
+
This file will be removed in a future version.
|
|
13
5
|
"""
|
|
14
6
|
|
|
15
|
-
import
|
|
16
|
-
import os
|
|
17
|
-
import sys
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
# Add parent directory to path for imports
|
|
21
|
-
sys.path.insert(0, str(Path(__file__).parent))
|
|
22
|
-
|
|
23
|
-
from hooks import (
|
|
24
|
-
find_project_root,
|
|
25
|
-
is_cyclist_running,
|
|
26
|
-
load_settings,
|
|
27
|
-
send_to_cyclist,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
# Once-per-session guard
|
|
31
|
-
_welcome_shown_file: Path | None = None
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def get_welcome_lock_path(project_root: Path) -> Path:
|
|
35
|
-
"""Get path to welcome shown lock file."""
|
|
36
|
-
session_id = os.environ.get("CLAUDE_SESSION_ID", str(os.getpid()))
|
|
37
|
-
session_dir = project_root / ".session"
|
|
38
|
-
session_dir.mkdir(parents=True, exist_ok=True)
|
|
39
|
-
return session_dir / f".welcome-shown-{session_id}"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def was_welcome_shown(project_root: Path) -> bool:
|
|
43
|
-
"""Check if welcome was already shown for this session."""
|
|
44
|
-
lock_path = get_welcome_lock_path(project_root)
|
|
45
|
-
return lock_path.exists()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def mark_welcome_shown(project_root: Path) -> None:
|
|
49
|
-
"""Mark welcome as shown for this session."""
|
|
50
|
-
lock_path = get_welcome_lock_path(project_root)
|
|
51
|
-
lock_path.touch()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def get_project_name(project_root: Path) -> str:
|
|
55
|
-
"""Get project name from package.json or directory name."""
|
|
56
|
-
package_json = project_root / "package.json"
|
|
57
|
-
if package_json.exists():
|
|
58
|
-
try:
|
|
59
|
-
with open(package_json) as f:
|
|
60
|
-
data = json.load(f)
|
|
61
|
-
name = data.get("name")
|
|
62
|
-
if name:
|
|
63
|
-
return name
|
|
64
|
-
except (json.JSONDecodeError, OSError):
|
|
65
|
-
pass
|
|
66
|
-
|
|
67
|
-
return project_root.name
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def get_theme(project_root: Path) -> str | None:
|
|
71
|
-
"""Get current theme from settings."""
|
|
72
|
-
settings = load_settings(project_root)
|
|
73
|
-
return settings.theme
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def display_cli_welcome(project_name: str, theme: str | None) -> None:
|
|
77
|
-
"""Display ASCII art welcome for CLI mode."""
|
|
78
|
-
print("""
|
|
79
|
-
___
|
|
80
|
-
/ \\
|
|
81
|
-
| | Welcome to
|
|
82
|
-
| | ╔═══════════════════════════════════╗
|
|
83
|
-
\\___/ ║ ╔═╗╔═╗╔╗╔╔╗╔╦═╗╔═╗╔═╗╔═╗╦╔═╗ ║
|
|
84
|
-
║ ║ ╠═╝║╣ ║║║║║║ ╠╣ ╠═╣╠╦╝ ║ ╠═╣ ║
|
|
85
|
-
║ ║ ╩ ╚═╝╝╚╝╝╚╝╩ ╩ ╩╩╚═ ╩ ╩ ╩ ║
|
|
86
|
-
╔═╩═╗ ╚═══════════════════════════════════╝
|
|
87
|
-
/ \\
|
|
88
|
-
│ O │ Agent-powered development with style
|
|
89
|
-
\\ /
|
|
90
|
-
╚═══╝
|
|
91
|
-
""")
|
|
92
|
-
|
|
93
|
-
if project_name:
|
|
94
|
-
print(f" Project: {project_name}")
|
|
95
|
-
if theme:
|
|
96
|
-
print(f" Theme: {theme}")
|
|
97
|
-
print()
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def send_cyclist_welcome(project_root: Path, project_name: str, theme: str | None) -> None:
|
|
101
|
-
"""Send welcome message to Cyclist via WebSocket API."""
|
|
102
|
-
try:
|
|
103
|
-
send_to_cyclist(
|
|
104
|
-
endpoint="/api/welcome",
|
|
105
|
-
data={
|
|
106
|
-
"project": project_name or "",
|
|
107
|
-
"theme": theme or "",
|
|
108
|
-
},
|
|
109
|
-
project_root=project_root,
|
|
110
|
-
timeout=5,
|
|
111
|
-
)
|
|
112
|
-
except Exception:
|
|
113
|
-
# Ignore errors - don't block hook
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def main() -> None:
|
|
118
|
-
"""Main entry point for SessionStart welcome hook."""
|
|
119
|
-
try:
|
|
120
|
-
# Read and discard stdin (required by hook protocol)
|
|
121
|
-
sys.stdin.read()
|
|
122
|
-
|
|
123
|
-
# Find project root
|
|
124
|
-
project_root = find_project_root()
|
|
125
|
-
if not project_root:
|
|
126
|
-
sys.exit(0)
|
|
127
|
-
|
|
128
|
-
# Check if welcome was already shown for this session
|
|
129
|
-
if was_welcome_shown(project_root):
|
|
130
|
-
sys.exit(0)
|
|
131
|
-
|
|
132
|
-
# Mark welcome as shown
|
|
133
|
-
mark_welcome_shown(project_root)
|
|
134
|
-
|
|
135
|
-
# Get project info
|
|
136
|
-
project_name = get_project_name(project_root)
|
|
137
|
-
theme = get_theme(project_root)
|
|
138
|
-
|
|
139
|
-
# Check if running in Cyclist
|
|
140
|
-
if is_cyclist_running(project_root):
|
|
141
|
-
# Send welcome via WebSocket API
|
|
142
|
-
send_cyclist_welcome(project_root, project_name, theme)
|
|
143
|
-
else:
|
|
144
|
-
# CLI mode - display ASCII art
|
|
145
|
-
display_cli_welcome(project_name, theme)
|
|
146
|
-
|
|
147
|
-
sys.exit(0)
|
|
148
|
-
|
|
149
|
-
except Exception as e:
|
|
150
|
-
# On error, exit silently
|
|
151
|
-
print(f"[welcome-hook] Error: {e}", file=sys.stderr)
|
|
152
|
-
sys.exit(0)
|
|
153
|
-
|
|
7
|
+
from pennyfarthing_scripts.hooks.session_start import main # noqa: F401
|
|
154
8
|
|
|
155
9
|
if __name__ == "__main__":
|
|
156
10
|
main()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|