@pennyfarthing/core 11.0.0 → 11.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -23
- package/package.json +1 -1
- package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.d.ts +20 -0
- package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.js +278 -0
- package/packages/core/dist/cli/utils/010-detect-remove-old-packages.test.js.map +1 -0
- package/packages/core/dist/cli/utils/constants.d.ts +8 -2
- package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/constants.js +4 -1
- package/packages/core/dist/cli/utils/constants.js.map +1 -1
- package/packages/core/dist/cli/utils/constants.test.d.ts +10 -0
- package/packages/core/dist/cli/utils/constants.test.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/constants.test.js +38 -0
- package/packages/core/dist/cli/utils/constants.test.js.map +1 -0
- package/packages/core/dist/consultation/consultation-protocol.d.ts +139 -0
- package/packages/core/dist/consultation/consultation-protocol.d.ts.map +1 -0
- package/packages/core/dist/consultation/consultation-protocol.js +178 -0
- package/packages/core/dist/consultation/consultation-protocol.js.map +1 -0
- package/packages/core/dist/consultation/consultation-protocol.test.d.ts +20 -0
- package/packages/core/dist/consultation/consultation-protocol.test.d.ts.map +1 -0
- package/packages/core/dist/consultation/consultation-protocol.test.js +474 -0
- package/packages/core/dist/consultation/consultation-protocol.test.js.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.d.ts +75 -0
- package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.js +334 -0
- package/packages/core/dist/consultation/dialogue-manager.js.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.test.d.ts +19 -0
- package/packages/core/dist/consultation/dialogue-manager.test.d.ts.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.test.js +444 -0
- package/packages/core/dist/consultation/dialogue-manager.test.js.map +1 -0
- package/packages/core/dist/public/js/react/react.js +3 -3
- package/packages/core/dist/scripts/theme-detail.test.d.ts +10 -0
- package/packages/core/dist/scripts/theme-detail.test.js +199 -0
- package/packages/core/dist/server/api/git.d.ts +13 -1
- package/packages/core/dist/server/api/git.d.ts.map +1 -1
- package/packages/core/dist/server/api/git.js +53 -34
- package/packages/core/dist/server/api/git.js.map +1 -1
- package/packages/core/dist/server/api/health-score.d.ts.map +1 -1
- package/packages/core/dist/server/api/health-score.js +25 -1
- package/packages/core/dist/server/api/health-score.js.map +1 -1
- package/packages/core/dist/server/api/settings.d.ts.map +1 -1
- package/packages/core/dist/server/api/settings.js +63 -1
- package/packages/core/dist/server/api/settings.js.map +1 -1
- package/packages/core/dist/server/api/theme-agents.d.ts.map +1 -1
- package/packages/core/dist/server/api/theme-agents.js +61 -0
- package/packages/core/dist/server/api/theme-agents.js.map +1 -1
- package/packages/core/dist/server/server.d.ts.map +1 -1
- package/packages/core/dist/server/server.js +17 -12
- package/packages/core/dist/server/server.js.map +1 -1
- package/packages/core/dist/shared/skill-search.test.js +2 -2
- package/packages/core/dist/workflow/gate-file-validation.d.ts +49 -0
- package/packages/core/dist/workflow/gate-file-validation.d.ts.map +1 -0
- package/packages/core/dist/workflow/gate-file-validation.js +157 -0
- package/packages/core/dist/workflow/gate-file-validation.js.map +1 -0
- package/packages/core/dist/workflow/gate-file-validation.test.d.ts +19 -0
- package/packages/core/dist/workflow/gate-file-validation.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/gate-file-validation.test.js +536 -0
- package/packages/core/dist/workflow/gate-file-validation.test.js.map +1 -0
- package/packages/core/dist/workflow/gate-schema-validation.test.d.ts +14 -0
- package/packages/core/dist/workflow/gate-schema-validation.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/gate-schema-validation.test.js +339 -0
- package/packages/core/dist/workflow/gate-schema-validation.test.js.map +1 -0
- package/packages/core/dist/workflow/handoff.js +2 -2
- package/packages/core/dist/workflow/handoff.js.map +1 -1
- package/packages/core/dist/workflow/handoff.test.js +16 -0
- package/packages/core/dist/workflow/handoff.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.d.ts +4 -2
- package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.js +43 -8
- package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
- package/pennyfarthing-dist/agents/README.md +6 -14
- package/pennyfarthing-dist/agents/architect.md +43 -29
- package/pennyfarthing-dist/agents/ba.md +30 -29
- package/pennyfarthing-dist/agents/dev.md +32 -43
- package/pennyfarthing-dist/agents/devops.md +57 -21
- package/pennyfarthing-dist/agents/orchestrator.md +3 -10
- package/pennyfarthing-dist/agents/pm.md +45 -31
- package/pennyfarthing-dist/agents/reviewer.md +20 -66
- package/pennyfarthing-dist/agents/sm-setup.md +2 -2
- package/pennyfarthing-dist/agents/sm.md +8 -30
- package/pennyfarthing-dist/agents/tea.md +25 -41
- package/pennyfarthing-dist/agents/tech-writer.md +33 -90
- package/pennyfarthing-dist/agents/ux-designer.md +39 -39
- package/pennyfarthing-dist/commands/benchmark-control.md +8 -64
- package/pennyfarthing-dist/commands/benchmark.md +8 -480
- package/pennyfarthing-dist/commands/job-fair.md +8 -97
- package/pennyfarthing-dist/commands/pf-benchmark-control.md +70 -0
- package/pennyfarthing-dist/commands/pf-benchmark.md +486 -0
- package/pennyfarthing-dist/commands/pf-chore.md +4 -4
- package/pennyfarthing-dist/commands/pf-ci.md +40 -0
- package/pennyfarthing-dist/commands/pf-close-epic.md +9 -27
- package/pennyfarthing-dist/commands/pf-continue-session.md +9 -213
- package/pennyfarthing-dist/commands/pf-create-branches-from-story.md +11 -353
- package/pennyfarthing-dist/commands/pf-docs.md +28 -0
- package/pennyfarthing-dist/commands/pf-epic.md +67 -0
- package/pennyfarthing-dist/commands/pf-git-cleanup.md +11 -52
- package/pennyfarthing-dist/commands/pf-git.md +75 -0
- package/pennyfarthing-dist/commands/pf-help.md +110 -128
- package/pennyfarthing-dist/commands/pf-job-fair.md +102 -0
- package/pennyfarthing-dist/commands/pf-new-work.md +9 -18
- package/pennyfarthing-dist/commands/pf-parallel-work.md +6 -66
- package/pennyfarthing-dist/commands/pf-release.md +11 -76
- package/pennyfarthing-dist/commands/pf-repo-status.md +11 -44
- package/pennyfarthing-dist/commands/pf-run-ci.md +8 -111
- package/pennyfarthing-dist/commands/pf-session.md +51 -0
- package/pennyfarthing-dist/commands/pf-solo.md +447 -0
- package/pennyfarthing-dist/commands/pf-sprint-planning.md +8 -104
- package/pennyfarthing-dist/commands/pf-standalone.md +1 -1
- package/pennyfarthing-dist/commands/pf-start-epic.md +9 -163
- package/pennyfarthing-dist/commands/pf-sync-epic-to-jira.md +8 -179
- package/pennyfarthing-dist/commands/pf-sync-work-with-sprint.md +8 -368
- package/pennyfarthing-dist/commands/pf-update-domain-docs.md +8 -78
- package/pennyfarthing-dist/commands/solo.md +8 -442
- package/pennyfarthing-dist/guides/agent-behavior.md +13 -13
- package/pennyfarthing-dist/guides/agent-coordination.md +7 -7
- package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +6 -5
- package/pennyfarthing-dist/guides/bikerack.md +128 -0
- package/pennyfarthing-dist/guides/brownfield-tools.md +133 -0
- package/pennyfarthing-dist/guides/command-tag-taxonomy.md +2 -2
- package/pennyfarthing-dist/guides/gate-schema.md +227 -0
- package/pennyfarthing-dist/guides/gates.md +120 -0
- package/pennyfarthing-dist/guides/handoff-cli.md +116 -0
- package/pennyfarthing-dist/guides/hooks.md +86 -4
- package/pennyfarthing-dist/guides/output-styles.md +65 -0
- package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +5 -5
- package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -4
- package/pennyfarthing-dist/guides/prompt-patterns.md +5 -5
- package/pennyfarthing-dist/guides/reflector.md +4 -4
- package/pennyfarthing-dist/guides/session-artifacts.md +1 -1
- package/pennyfarthing-dist/guides/skill-schema.md +1 -1
- package/pennyfarthing-dist/guides/tandem-protocol.md +13 -1
- package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
- package/pennyfarthing-dist/guides/xml-tags.md +5 -4
- package/pennyfarthing-dist/personas/themes/hogans-heroes.yaml +11 -22
- package/pennyfarthing-dist/personas/themes/stephen-king.yaml +13 -24
- package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +322 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -1
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +19 -14
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +191 -57
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +26 -10
- package/pennyfarthing-dist/skills/pf-changelog/SKILL.md +4 -4
- package/pennyfarthing-dist/skills/pf-sprint/skill.md +1 -1
- package/pennyfarthing-dist/skills/skill-registry.schema.json +4 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +5 -0
- package/pennyfarthing-dist/workflows/2party-tdd.yaml +11 -0
- package/pennyfarthing-dist/workflows/agent-docs.yaml +2 -0
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +4 -0
- package/pennyfarthing-dist/workflows/bdd.yaml +4 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +3 -0
- package/pennyfarthing-dist/workflows/tdd.yaml +3 -0
- package/pennyfarthing-dist/workflows/trivial.yaml +2 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.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__/sprint_panel.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/cli.py +10 -11
- package/pennyfarthing_scripts/bikerack/debug_panel.py +218 -0
- package/pennyfarthing_scripts/bikerack/diffs_panel.py +203 -27
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/cli.py +114 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/epic/__init__.py +0 -0
- package/pennyfarthing_scripts/epic/cli.py +64 -0
- package/pennyfarthing_scripts/gate/__init__.py +1 -0
- package/pennyfarthing_scripts/gate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/gate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/gate/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/gate/cli.py +56 -0
- package/pennyfarthing_scripts/gate/validate.py +266 -0
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__init__.py +0 -0
- package/pennyfarthing_scripts/git_group/cli.py +100 -0
- package/pennyfarthing_scripts/handoff/__init__.py +1 -0
- package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/cli.py +120 -0
- package/pennyfarthing_scripts/handoff/complete_phase.py +155 -0
- package/pennyfarthing_scripts/handoff/gate_file.py +105 -0
- package/pennyfarthing_scripts/handoff/gate_runner.py +152 -0
- package/pennyfarthing_scripts/handoff/marker.py +109 -0
- package/pennyfarthing_scripts/handoff/resolve_gate.py +152 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/launch/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/launch/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/version_sentinel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/workflow.py +39 -0
- package/pennyfarthing_scripts/session/__init__.py +0 -0
- package/pennyfarthing_scripts/session/cli.py +87 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/story_finish.py +14 -0
- package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_108_2_remove_handoff_fallback.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_archive_epic.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_bc.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_bikerack.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_normalization.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_epic_shard_validation.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_panel.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_topology_loader.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tui_focus.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tui_panel_persistence.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_version_sentinel.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_108_1_gate_migration.py +540 -0
- package/pennyfarthing_scripts/tests/test_108_2_remove_handoff_fallback.py +339 -0
- package/pennyfarthing_scripts/tests/test_confidence_sm_evaluation.py +253 -0
- package/pennyfarthing_scripts/tests/test_confidence_sm_gate.py +315 -0
- package/pennyfarthing_scripts/tests/test_gate_file_resolution.py +341 -0
- package/pennyfarthing_scripts/tests/test_gate_runner.py +620 -0
- package/pennyfarthing_scripts/tests/test_handoff_cli.py +929 -0
- package/pennyfarthing_scripts/tests/test_handoff_e2e.py +454 -0
- package/pennyfarthing_scripts/tests/test_resolve_gate_file_field.py +464 -0
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/skill_command.py +200 -0
- package/pennyfarthing_scripts/validate/adapters/workflow.py +64 -0
- package/pennyfarthing_scripts/validate/cli.py +15 -4
- package/packages/core/dist/scripts/benchmark-integration.d.ts +0 -182
- package/packages/core/dist/scripts/benchmark-integration.d.ts.map +0 -1
- package/packages/core/dist/scripts/benchmark-integration.js +0 -691
- package/packages/core/dist/scripts/benchmark-integration.js.map +0 -1
- package/packages/core/dist/scripts/job-fair-aggregator.d.ts +0 -150
- package/packages/core/dist/scripts/job-fair-aggregator.d.ts.map +0 -1
- package/packages/core/dist/scripts/job-fair-aggregator.js +0 -547
- package/packages/core/dist/scripts/job-fair-aggregator.js.map +0 -1
- package/pennyfarthing-dist/agents/handoff.md +0 -250
- package/pennyfarthing-dist/agents/sm-handoff.md +0 -152
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -112
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Gate file validation — schema, depth, cycles, and mandatory elements.
|
|
2
|
+
|
|
3
|
+
Validates a gate file for:
|
|
4
|
+
1. Schema: <gate name="..."> with <purpose>, <pass>, <fail>
|
|
5
|
+
2. Depth: max nesting depth of 3
|
|
6
|
+
3. Cycles: no duplicate gate names
|
|
7
|
+
4. Completeness: all required elements non-empty
|
|
8
|
+
|
|
9
|
+
Reports ALL errors at once (not fail-fast).
|
|
10
|
+
|
|
11
|
+
Story: 107-3 (Gate authoring guide and validation command)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
MAX_DEPTH = 3
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class GateInfo:
|
|
25
|
+
"""Parsed gate metadata."""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
model: str = "haiku"
|
|
29
|
+
depth: int = 0
|
|
30
|
+
children: list[str] = field(default_factory=list)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ValidationResult:
|
|
35
|
+
"""Result of gate file validation."""
|
|
36
|
+
|
|
37
|
+
valid: bool
|
|
38
|
+
gate_name: str | None = None
|
|
39
|
+
model: str | None = None
|
|
40
|
+
depth: int = 0
|
|
41
|
+
child_count: int = 0
|
|
42
|
+
errors: list[str] = field(default_factory=list)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def validate_gate_file(path: str | Path) -> ValidationResult:
|
|
46
|
+
"""Validate a gate file for schema compliance, depth, and cycles.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
path: Path to the gate file.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
ValidationResult with all errors collected.
|
|
53
|
+
"""
|
|
54
|
+
path = Path(path)
|
|
55
|
+
errors: list[str] = []
|
|
56
|
+
|
|
57
|
+
if not path.exists():
|
|
58
|
+
return ValidationResult(valid=False, errors=[f"File not found: {path}"])
|
|
59
|
+
|
|
60
|
+
content = path.read_text()
|
|
61
|
+
if not content.strip():
|
|
62
|
+
return ValidationResult(valid=False, errors=["File is empty"])
|
|
63
|
+
|
|
64
|
+
# Tokenize all gate-related tags
|
|
65
|
+
tokens = _tokenize(content)
|
|
66
|
+
|
|
67
|
+
if not tokens:
|
|
68
|
+
return ValidationResult(
|
|
69
|
+
valid=False,
|
|
70
|
+
errors=["No <gate> tag found in file"],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Parse gate structure and collect errors
|
|
74
|
+
gate_names: list[str] = []
|
|
75
|
+
root_name: str | None = None
|
|
76
|
+
root_model: str | None = None
|
|
77
|
+
max_depth = 0
|
|
78
|
+
depth = 0
|
|
79
|
+
|
|
80
|
+
for token in tokens:
|
|
81
|
+
if token["type"] == "gate_open":
|
|
82
|
+
name = token.get("name")
|
|
83
|
+
model = token.get("model", "haiku")
|
|
84
|
+
|
|
85
|
+
if depth == 0:
|
|
86
|
+
root_name = name
|
|
87
|
+
root_model = model
|
|
88
|
+
|
|
89
|
+
if not name:
|
|
90
|
+
errors.append(
|
|
91
|
+
f"Gate element at depth {depth} missing required 'name' attribute"
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
# Check for duplicate names (cycle indicator)
|
|
95
|
+
if name in gate_names:
|
|
96
|
+
errors.append(
|
|
97
|
+
f"Duplicate gate name '{name}' — names must be unique within a file"
|
|
98
|
+
)
|
|
99
|
+
gate_names.append(name)
|
|
100
|
+
|
|
101
|
+
depth += 1
|
|
102
|
+
if depth - 1 > max_depth:
|
|
103
|
+
max_depth = depth - 1
|
|
104
|
+
|
|
105
|
+
# Depth limit check (depth is 0-indexed, so depth-1 after increment)
|
|
106
|
+
if depth - 1 > MAX_DEPTH:
|
|
107
|
+
gate_label = name or "(unnamed)"
|
|
108
|
+
errors.append(
|
|
109
|
+
f"Gate depth limit exceeded: '{gate_label}' at depth {depth - 1} (max {MAX_DEPTH})"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
elif token["type"] == "gate_close":
|
|
113
|
+
depth -= 1
|
|
114
|
+
|
|
115
|
+
# Validate required child elements for each gate
|
|
116
|
+
_validate_required_elements(content, gate_names, errors)
|
|
117
|
+
|
|
118
|
+
child_count = len(gate_names) - 1 if len(gate_names) > 1 else 0
|
|
119
|
+
|
|
120
|
+
return ValidationResult(
|
|
121
|
+
valid=len(errors) == 0,
|
|
122
|
+
gate_name=root_name,
|
|
123
|
+
model=root_model,
|
|
124
|
+
depth=max_depth,
|
|
125
|
+
child_count=child_count,
|
|
126
|
+
errors=errors,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _tokenize(content: str) -> list[dict]:
|
|
131
|
+
"""Tokenize gate-related XML tags from content."""
|
|
132
|
+
tokens: list[dict] = []
|
|
133
|
+
|
|
134
|
+
# Match opening gate tags with attributes
|
|
135
|
+
open_pattern = re.compile(r"<gate\b([^>]*)>", re.IGNORECASE)
|
|
136
|
+
close_pattern = re.compile(r"</gate\s*>", re.IGNORECASE)
|
|
137
|
+
|
|
138
|
+
# Build a list of all tags with their positions
|
|
139
|
+
events: list[tuple[int, dict]] = []
|
|
140
|
+
|
|
141
|
+
for m in open_pattern.finditer(content):
|
|
142
|
+
attrs = m.group(1)
|
|
143
|
+
name_match = re.search(r'name="([^"]*)"', attrs)
|
|
144
|
+
model_match = re.search(r'model="([^"]*)"', attrs)
|
|
145
|
+
events.append((m.start(), {
|
|
146
|
+
"type": "gate_open",
|
|
147
|
+
"name": name_match.group(1) if name_match else None,
|
|
148
|
+
"model": model_match.group(1) if model_match else "haiku",
|
|
149
|
+
}))
|
|
150
|
+
|
|
151
|
+
for m in close_pattern.finditer(content):
|
|
152
|
+
events.append((m.start(), {"type": "gate_close"}))
|
|
153
|
+
|
|
154
|
+
# Sort by position
|
|
155
|
+
events.sort(key=lambda e: e[0])
|
|
156
|
+
tokens = [e[1] for e in events]
|
|
157
|
+
|
|
158
|
+
return tokens
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _validate_required_elements(
|
|
162
|
+
content: str,
|
|
163
|
+
gate_names: list[str],
|
|
164
|
+
errors: list[str],
|
|
165
|
+
) -> None:
|
|
166
|
+
"""Validate that each gate has <purpose>, <pass>, and <fail> elements.
|
|
167
|
+
|
|
168
|
+
For each gate, checks that required child elements exist and are non-empty
|
|
169
|
+
within the gate's scope.
|
|
170
|
+
"""
|
|
171
|
+
# For simple files with one gate, check globally
|
|
172
|
+
# For nested files, check per-gate scope
|
|
173
|
+
if len(gate_names) <= 1:
|
|
174
|
+
name = gate_names[0] if gate_names else "(unnamed)"
|
|
175
|
+
_check_required_for_gate(content, name, errors)
|
|
176
|
+
else:
|
|
177
|
+
# Split content by gate scopes and validate each
|
|
178
|
+
# Use a stack-based approach to extract each gate's direct content
|
|
179
|
+
_validate_nested_gates(content, errors)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _check_required_for_gate(
|
|
183
|
+
scope_content: str,
|
|
184
|
+
gate_name: str,
|
|
185
|
+
errors: list[str],
|
|
186
|
+
) -> None:
|
|
187
|
+
"""Check that a gate scope contains non-empty <purpose>, <pass>, <fail>."""
|
|
188
|
+
for element in ("purpose", "pass", "fail"):
|
|
189
|
+
pattern = re.compile(
|
|
190
|
+
rf"<{element}>(.*?)</{element}>",
|
|
191
|
+
re.DOTALL | re.IGNORECASE,
|
|
192
|
+
)
|
|
193
|
+
match = pattern.search(scope_content)
|
|
194
|
+
if not match:
|
|
195
|
+
errors.append(f"Gate '{gate_name}' missing required <{element}> block")
|
|
196
|
+
elif not match.group(1).strip():
|
|
197
|
+
errors.append(f"Gate '{gate_name}' has empty <{element}> block")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _validate_nested_gates(content: str, errors: list[str]) -> None:
|
|
201
|
+
"""Validate required elements for each gate in a nested structure."""
|
|
202
|
+
# Extract each gate's scope using a stack-based parser
|
|
203
|
+
open_pattern = re.compile(r"<gate\b([^>]*)>", re.IGNORECASE)
|
|
204
|
+
close_pattern = re.compile(r"</gate\s*>", re.IGNORECASE)
|
|
205
|
+
|
|
206
|
+
events: list[tuple[int, str, str | None]] = []
|
|
207
|
+
|
|
208
|
+
for m in open_pattern.finditer(content):
|
|
209
|
+
name_match = re.search(r'name="([^"]*)"', m.group(1))
|
|
210
|
+
name = name_match.group(1) if name_match else None
|
|
211
|
+
events.append((m.start(), "open", name))
|
|
212
|
+
|
|
213
|
+
for m in close_pattern.finditer(content):
|
|
214
|
+
events.append((m.start(), "close", None))
|
|
215
|
+
|
|
216
|
+
events.sort(key=lambda e: e[0])
|
|
217
|
+
|
|
218
|
+
# Stack-based scope extraction
|
|
219
|
+
stack: list[tuple[str | None, int]] = [] # (name, start_pos)
|
|
220
|
+
|
|
221
|
+
for pos, event_type, name in events:
|
|
222
|
+
if event_type == "open":
|
|
223
|
+
stack.append((name, pos))
|
|
224
|
+
elif event_type == "close" and stack:
|
|
225
|
+
gate_name, start_pos = stack.pop()
|
|
226
|
+
# Extract this gate's direct content (between open and close)
|
|
227
|
+
gate_scope = content[start_pos:pos]
|
|
228
|
+
# Remove nested gate content to check only direct children
|
|
229
|
+
direct_content = _remove_nested_gates(gate_scope)
|
|
230
|
+
label = gate_name or "(unnamed)"
|
|
231
|
+
_check_required_for_gate(direct_content, label, errors)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _remove_nested_gates(scope: str) -> str:
|
|
235
|
+
"""Remove nested <gate>...</gate> blocks from scope content.
|
|
236
|
+
|
|
237
|
+
Keeps the outermost gate's direct content only.
|
|
238
|
+
"""
|
|
239
|
+
depth = 0
|
|
240
|
+
result: list[str] = []
|
|
241
|
+
i = 0
|
|
242
|
+
|
|
243
|
+
open_pattern = re.compile(r"<gate\b[^>]*>", re.IGNORECASE)
|
|
244
|
+
close_pattern = re.compile(r"</gate\s*>", re.IGNORECASE)
|
|
245
|
+
|
|
246
|
+
while i < len(scope):
|
|
247
|
+
open_match = open_pattern.match(scope, i)
|
|
248
|
+
close_match = close_pattern.match(scope, i)
|
|
249
|
+
|
|
250
|
+
if open_match:
|
|
251
|
+
depth += 1
|
|
252
|
+
if depth <= 1:
|
|
253
|
+
# Keep the outermost gate open tag
|
|
254
|
+
result.append(open_match.group())
|
|
255
|
+
i = open_match.end()
|
|
256
|
+
elif close_match:
|
|
257
|
+
if depth <= 1:
|
|
258
|
+
result.append(close_match.group())
|
|
259
|
+
depth -= 1
|
|
260
|
+
i = close_match.end()
|
|
261
|
+
else:
|
|
262
|
+
if depth <= 1:
|
|
263
|
+
result.append(scope[i])
|
|
264
|
+
i += 1
|
|
265
|
+
|
|
266
|
+
return "".join(result)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Git CLI - Click-based CLI for git repository operations.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
pf git [COMMAND] [ARGS]...
|
|
6
|
+
|
|
7
|
+
Commands:
|
|
8
|
+
status Check git status of all project repos
|
|
9
|
+
cleanup Organize changes into proper commits/branches
|
|
10
|
+
branches Create feature branches from a story
|
|
11
|
+
release Interactive release with verification gates
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group()
|
|
20
|
+
def git():
|
|
21
|
+
"""Repository operations across all configured repos.
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
Commands:
|
|
25
|
+
status - Check git status of all repos
|
|
26
|
+
cleanup - Organize changes into commits/branches
|
|
27
|
+
branches - Create feature branches from a story
|
|
28
|
+
release - Interactive release with verification gates
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@git.command()
|
|
34
|
+
@click.option("--brief", is_flag=True, help="One-line-per-repo summary")
|
|
35
|
+
def status(brief: bool):
|
|
36
|
+
"""Check git status of all project repos.
|
|
37
|
+
|
|
38
|
+
Shows branch, uncommitted changes, and ahead/behind status for each repo.
|
|
39
|
+
"""
|
|
40
|
+
import subprocess
|
|
41
|
+
|
|
42
|
+
root = get_project_root()
|
|
43
|
+
script = root / ".pennyfarthing" / "scripts" / "git" / "git-status-all.sh"
|
|
44
|
+
|
|
45
|
+
if not script.is_file():
|
|
46
|
+
click.echo("Error: git-status-all.sh not found", err=True)
|
|
47
|
+
raise SystemExit(1)
|
|
48
|
+
|
|
49
|
+
cmd = [str(script)]
|
|
50
|
+
if brief:
|
|
51
|
+
cmd.append("--brief")
|
|
52
|
+
|
|
53
|
+
result = subprocess.run(cmd, cwd=str(root))
|
|
54
|
+
raise SystemExit(result.returncode)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@git.command()
|
|
58
|
+
def cleanup():
|
|
59
|
+
"""Organize changes into proper commits and branches.
|
|
60
|
+
|
|
61
|
+
Starts the git-cleanup stepped workflow via BikeLane.
|
|
62
|
+
Equivalent to: /pf-workflow start git-cleanup
|
|
63
|
+
"""
|
|
64
|
+
click.echo("Starting git-cleanup workflow...")
|
|
65
|
+
click.echo("Run: /pf-workflow start git-cleanup")
|
|
66
|
+
click.echo("Or: pf workflow start git-cleanup")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@git.command()
|
|
70
|
+
@click.argument("story_id")
|
|
71
|
+
def branches(story_id: str):
|
|
72
|
+
"""Create feature branches in both repos from a story.
|
|
73
|
+
|
|
74
|
+
\b
|
|
75
|
+
Arguments:
|
|
76
|
+
STORY_ID - The story ID to create branches for (e.g., 86-3)
|
|
77
|
+
"""
|
|
78
|
+
import subprocess
|
|
79
|
+
|
|
80
|
+
root = get_project_root()
|
|
81
|
+
script = root / ".pennyfarthing" / "scripts" / "git" / "create-branches.sh"
|
|
82
|
+
|
|
83
|
+
if not script.is_file():
|
|
84
|
+
click.echo("Error: create-branches.sh not found", err=True)
|
|
85
|
+
raise SystemExit(1)
|
|
86
|
+
|
|
87
|
+
result = subprocess.run([str(script), story_id], cwd=str(root))
|
|
88
|
+
raise SystemExit(result.returncode)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@git.command()
|
|
92
|
+
def release():
|
|
93
|
+
"""Interactive release with verification gates.
|
|
94
|
+
|
|
95
|
+
Starts the release stepped workflow via BikeLane.
|
|
96
|
+
Equivalent to: /pf-workflow start release
|
|
97
|
+
"""
|
|
98
|
+
click.echo("Starting release workflow...")
|
|
99
|
+
click.echo("Run: /pf-workflow start release")
|
|
100
|
+
click.echo("Or: pf workflow start release")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Handoff CLI — Phase gate resolution and atomic session transitions."""
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Handoff CLI — Phase gate resolution, session transitions, and marker generation.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
pf handoff resolve-gate STORY_ID WORKFLOW PHASE
|
|
5
|
+
pf handoff complete-phase STORY_ID WORKFLOW FROM_PHASE TO_PHASE GATE_TYPE
|
|
6
|
+
pf handoff marker NEXT_AGENT [--error MESSAGE]
|
|
7
|
+
|
|
8
|
+
Stories: 105-1, 105-4 (Script-First Handoff)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def handoff():
|
|
18
|
+
"""Phase gate resolution and session transitions.
|
|
19
|
+
|
|
20
|
+
\b
|
|
21
|
+
Commands:
|
|
22
|
+
resolve-gate - Resolve gate for current phase
|
|
23
|
+
complete-phase - Complete phase transition atomically
|
|
24
|
+
marker - Generate AGENT_COMMAND handoff marker
|
|
25
|
+
"""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@handoff.command("resolve-gate")
|
|
30
|
+
@click.argument("story_id")
|
|
31
|
+
@click.argument("workflow")
|
|
32
|
+
@click.argument("phase")
|
|
33
|
+
def resolve_gate_cmd(story_id: str, workflow: str, phase: str):
|
|
34
|
+
"""Resolve the gate for the current workflow phase.
|
|
35
|
+
|
|
36
|
+
Reads workflow YAML, checks assessment, returns RESOLVE_RESULT.
|
|
37
|
+
|
|
38
|
+
\b
|
|
39
|
+
Arguments:
|
|
40
|
+
STORY_ID - Story identifier (e.g., 105-1)
|
|
41
|
+
WORKFLOW - Workflow name (e.g., tdd, trivial, patch)
|
|
42
|
+
PHASE - Current phase name (e.g., green, implement, fix)
|
|
43
|
+
"""
|
|
44
|
+
from pennyfarthing_scripts.handoff.resolve_gate import resolve_gate
|
|
45
|
+
|
|
46
|
+
result = resolve_gate(story_id, workflow, phase)
|
|
47
|
+
|
|
48
|
+
import yaml
|
|
49
|
+
|
|
50
|
+
click.echo(yaml.dump({"RESOLVE_RESULT": result}, default_flow_style=False).rstrip())
|
|
51
|
+
|
|
52
|
+
if result.get("status") == "blocked":
|
|
53
|
+
raise SystemExit(1)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@handoff.command("complete-phase")
|
|
57
|
+
@click.argument("story_id")
|
|
58
|
+
@click.argument("workflow")
|
|
59
|
+
@click.argument("from_phase")
|
|
60
|
+
@click.argument("to_phase")
|
|
61
|
+
@click.argument("gate_type")
|
|
62
|
+
def complete_phase_cmd(
|
|
63
|
+
story_id: str,
|
|
64
|
+
workflow: str,
|
|
65
|
+
from_phase: str,
|
|
66
|
+
to_phase: str,
|
|
67
|
+
gate_type: str,
|
|
68
|
+
):
|
|
69
|
+
"""Complete a phase transition with atomic session update.
|
|
70
|
+
|
|
71
|
+
Updates session file: phase line, timestamps, history tables.
|
|
72
|
+
|
|
73
|
+
\b
|
|
74
|
+
Arguments:
|
|
75
|
+
STORY_ID - Story identifier (e.g., 105-1)
|
|
76
|
+
WORKFLOW - Workflow name (e.g., tdd, trivial)
|
|
77
|
+
FROM_PHASE - Phase being completed (e.g., green)
|
|
78
|
+
TO_PHASE - Phase being entered (e.g., review)
|
|
79
|
+
GATE_TYPE - Gate type that was passed (e.g., tests_pass)
|
|
80
|
+
"""
|
|
81
|
+
from pennyfarthing_scripts.handoff.complete_phase import complete_phase
|
|
82
|
+
|
|
83
|
+
result = complete_phase(story_id, workflow, from_phase, to_phase, gate_type)
|
|
84
|
+
|
|
85
|
+
import yaml
|
|
86
|
+
|
|
87
|
+
click.echo(yaml.dump({"COMPLETE_RESULT": result}, default_flow_style=False).rstrip())
|
|
88
|
+
|
|
89
|
+
if result.get("status") == "error":
|
|
90
|
+
raise SystemExit(1)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@handoff.command("marker")
|
|
94
|
+
@click.argument("next_agent", required=False, default=None)
|
|
95
|
+
@click.option("--error", "error_msg", default=None, help="Generate error marker")
|
|
96
|
+
def marker_cmd(next_agent: str | None, error_msg: str | None):
|
|
97
|
+
"""Generate AGENT_COMMAND handoff marker block.
|
|
98
|
+
|
|
99
|
+
Environment-aware marker generation. Detects Cyclist, relay mode,
|
|
100
|
+
and context usage to choose the appropriate marker type.
|
|
101
|
+
|
|
102
|
+
\b
|
|
103
|
+
Arguments:
|
|
104
|
+
NEXT_AGENT - Agent to hand off to (e.g., dev, tea, reviewer)
|
|
105
|
+
|
|
106
|
+
\b
|
|
107
|
+
Options:
|
|
108
|
+
--error MSG - Generate an error marker instead of a handoff
|
|
109
|
+
"""
|
|
110
|
+
from pennyfarthing_scripts.handoff.marker import generate_marker
|
|
111
|
+
|
|
112
|
+
if not next_agent and not error_msg:
|
|
113
|
+
raise click.UsageError(
|
|
114
|
+
"Provide NEXT_AGENT or --error MESSAGE.\n\n"
|
|
115
|
+
"Examples:\n"
|
|
116
|
+
" pf handoff marker dev\n"
|
|
117
|
+
" pf handoff marker --error 'Tests failing'"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
click.echo(generate_marker(next_agent, error=error_msg))
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Complete phase transition with atomic session update.
|
|
2
|
+
|
|
3
|
+
Atomically updates the session file (temp + mv) with phase transition,
|
|
4
|
+
timestamps, and history table entries.
|
|
5
|
+
|
|
6
|
+
Story: 105-1 (Script-First Handoff)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import tempfile
|
|
14
|
+
from datetime import UTC, datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def complete_phase(
|
|
21
|
+
story_id: str,
|
|
22
|
+
workflow: str,
|
|
23
|
+
from_phase: str,
|
|
24
|
+
to_phase: str,
|
|
25
|
+
gate_type: str,
|
|
26
|
+
project_root: Path | None = None,
|
|
27
|
+
) -> dict:
|
|
28
|
+
"""Complete a phase transition with atomic session file update.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
story_id: Story identifier (e.g., "105-1")
|
|
32
|
+
workflow: Workflow name (e.g., "tdd", "trivial")
|
|
33
|
+
from_phase: Phase being completed (e.g., "green")
|
|
34
|
+
to_phase: Phase being entered (e.g., "review")
|
|
35
|
+
gate_type: Gate type that was passed (e.g., "tests_pass")
|
|
36
|
+
project_root: Project root path. Auto-detected if None.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
COMPLETE_RESULT dict with keys:
|
|
40
|
+
status: "success" | "error"
|
|
41
|
+
session_file: str (path to session file)
|
|
42
|
+
error: str | None
|
|
43
|
+
"""
|
|
44
|
+
if project_root is None:
|
|
45
|
+
project_root = _find_project_root()
|
|
46
|
+
|
|
47
|
+
session_path = project_root / ".session" / f"{story_id}-session.md"
|
|
48
|
+
if not session_path.exists():
|
|
49
|
+
return {
|
|
50
|
+
"status": "error",
|
|
51
|
+
"session_file": None,
|
|
52
|
+
"error": "Session file not found",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
content = session_path.read_text()
|
|
56
|
+
now = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
57
|
+
|
|
58
|
+
from_agent = _get_phase_agent(project_root, workflow, from_phase)
|
|
59
|
+
to_agent = _get_phase_agent(project_root, workflow, to_phase)
|
|
60
|
+
|
|
61
|
+
# Update all **Phase:** lines to new phase
|
|
62
|
+
content = re.sub(r"(\*\*Phase:\*\*) \S+", rf"\1 {to_phase}", content)
|
|
63
|
+
|
|
64
|
+
# Update all **Phase Started:** lines to now
|
|
65
|
+
content = re.sub(r"(\*\*Phase Started:\*\*) \S+", rf"\1 {now}", content)
|
|
66
|
+
|
|
67
|
+
# Update Phase History: fill Ended/Duration for from_phase, add new row
|
|
68
|
+
lines = content.splitlines()
|
|
69
|
+
result_lines = []
|
|
70
|
+
for line in lines:
|
|
71
|
+
if line.strip().startswith(f"| {from_phase}"):
|
|
72
|
+
cols = [c.strip() for c in line.split("|") if c.strip()]
|
|
73
|
+
if len(cols) >= 4 and cols[2] == "-":
|
|
74
|
+
started_str = cols[1]
|
|
75
|
+
duration = _calc_duration(started_str, now)
|
|
76
|
+
result_lines.append(
|
|
77
|
+
f"| {from_phase} | {started_str} | {now} | {duration} |"
|
|
78
|
+
)
|
|
79
|
+
result_lines.append(f"| {to_phase} | {now} | - | - |")
|
|
80
|
+
continue
|
|
81
|
+
result_lines.append(line)
|
|
82
|
+
content = "\n".join(result_lines)
|
|
83
|
+
|
|
84
|
+
# Add Handoff History row at end of table
|
|
85
|
+
handoff_row = (
|
|
86
|
+
f"| {from_phase} ({from_agent}) | {to_phase} ({to_agent}) "
|
|
87
|
+
f"| {gate_type} | PASSED | {now} |"
|
|
88
|
+
)
|
|
89
|
+
lines = content.splitlines()
|
|
90
|
+
insert_after = None
|
|
91
|
+
in_handoff = False
|
|
92
|
+
for i, line in enumerate(lines):
|
|
93
|
+
if "### Handoff History" in line:
|
|
94
|
+
in_handoff = True
|
|
95
|
+
if in_handoff and line.strip().startswith("|"):
|
|
96
|
+
insert_after = i
|
|
97
|
+
if insert_after is not None:
|
|
98
|
+
lines.insert(insert_after + 1, handoff_row)
|
|
99
|
+
content = "\n".join(lines)
|
|
100
|
+
|
|
101
|
+
# Atomic write: temp file in same directory + rename
|
|
102
|
+
temp_fd, temp_path_str = tempfile.mkstemp(
|
|
103
|
+
dir=str(session_path.parent), suffix=".tmp"
|
|
104
|
+
)
|
|
105
|
+
os.close(temp_fd)
|
|
106
|
+
temp_path = Path(temp_path_str)
|
|
107
|
+
try:
|
|
108
|
+
temp_path.write_text(content)
|
|
109
|
+
temp_path.rename(session_path)
|
|
110
|
+
except Exception:
|
|
111
|
+
temp_path.unlink(missing_ok=True)
|
|
112
|
+
raise
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"status": "success",
|
|
116
|
+
"session_file": f".session/{story_id}-session.md",
|
|
117
|
+
"error": None,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _calc_duration(started_str: str, ended_str: str) -> str:
|
|
122
|
+
started = datetime.fromisoformat(started_str.replace("Z", "+00:00"))
|
|
123
|
+
ended = datetime.fromisoformat(ended_str.replace("Z", "+00:00"))
|
|
124
|
+
total_seconds = int((ended - started).total_seconds())
|
|
125
|
+
if total_seconds < 60:
|
|
126
|
+
return f"{total_seconds}s"
|
|
127
|
+
minutes = total_seconds // 60
|
|
128
|
+
seconds = total_seconds % 60
|
|
129
|
+
if total_seconds < 3600:
|
|
130
|
+
return f"{minutes}m {seconds}s" if seconds else f"{minutes}m"
|
|
131
|
+
hours = total_seconds // 3600
|
|
132
|
+
rem_minutes = (total_seconds % 3600) // 60
|
|
133
|
+
return f"{hours}h {rem_minutes}m" if rem_minutes else f"{hours}h"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _get_phase_agent(project_root: Path, workflow: str, phase: str) -> str:
|
|
137
|
+
for name in [f"{workflow}.yaml", f"{workflow}/workflow.yaml"]:
|
|
138
|
+
path = project_root / ".pennyfarthing" / "workflows" / name
|
|
139
|
+
if path.exists():
|
|
140
|
+
try:
|
|
141
|
+
data = yaml.safe_load(path.read_text())
|
|
142
|
+
for p in data["workflow"]["phases"]:
|
|
143
|
+
if p["name"] == phase:
|
|
144
|
+
return p.get("agent", phase)
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
return phase
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _find_project_root() -> Path:
|
|
151
|
+
cwd = Path.cwd()
|
|
152
|
+
for parent in [cwd, *cwd.parents]:
|
|
153
|
+
if (parent / ".pennyfarthing").is_dir():
|
|
154
|
+
return parent
|
|
155
|
+
return cwd
|