@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,105 @@
|
|
|
1
|
+
"""Gate file discovery and resolution.
|
|
2
|
+
|
|
3
|
+
Resolves gate file references (e.g., "gates/tests-pass") to actual file paths.
|
|
4
|
+
Resolution order:
|
|
5
|
+
1. .pennyfarthing/gates/{name}.md (project-local override)
|
|
6
|
+
2. pennyfarthing-dist/gates/{name}.md (built-in fallback)
|
|
7
|
+
|
|
8
|
+
Non-existent files return an error result with status "blocked".
|
|
9
|
+
|
|
10
|
+
Story: 106-4 (Gate File Discovery and Resolution)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve_gate_file(
|
|
19
|
+
gate_ref: str,
|
|
20
|
+
project_root: Path | None = None,
|
|
21
|
+
) -> dict:
|
|
22
|
+
"""Resolve a gate file reference to an absolute file path.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
gate_ref: Gate reference string (e.g., "gates/tests-pass" or "tests-pass")
|
|
26
|
+
project_root: Project root path. Auto-detected if None.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
dict with keys:
|
|
30
|
+
status: "found" | "not_found"
|
|
31
|
+
path: str | None (absolute path if found)
|
|
32
|
+
error: str | None (error message if not found)
|
|
33
|
+
"""
|
|
34
|
+
if project_root is None:
|
|
35
|
+
project_root = _find_project_root()
|
|
36
|
+
|
|
37
|
+
name = _sanitize_gate_name(gate_ref)
|
|
38
|
+
if name is None:
|
|
39
|
+
return _result(
|
|
40
|
+
status="not_found",
|
|
41
|
+
error=f"Invalid gate reference: {gate_ref!r}",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Resolution order: local first, built-in fallback
|
|
45
|
+
search_paths = [
|
|
46
|
+
project_root / ".pennyfarthing" / "gates" / f"{name}.md",
|
|
47
|
+
project_root / "pennyfarthing-dist" / "gates" / f"{name}.md",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for candidate in search_paths:
|
|
51
|
+
if candidate.is_file():
|
|
52
|
+
return _result(status="found", path=str(candidate.resolve()))
|
|
53
|
+
|
|
54
|
+
return _result(
|
|
55
|
+
status="not_found",
|
|
56
|
+
error=f"Gate file not found: {name}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _sanitize_gate_name(gate_ref: str) -> str | None:
|
|
61
|
+
"""Extract a clean gate name from a reference string.
|
|
62
|
+
|
|
63
|
+
Strips 'gates/' prefix and '.md' suffix. Rejects empty names
|
|
64
|
+
and path traversal attempts.
|
|
65
|
+
"""
|
|
66
|
+
if not gate_ref:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
name = gate_ref
|
|
70
|
+
# Strip gates/ prefix
|
|
71
|
+
if name.startswith("gates/"):
|
|
72
|
+
name = name[len("gates/"):]
|
|
73
|
+
# Strip .md suffix
|
|
74
|
+
if name.endswith(".md"):
|
|
75
|
+
name = name[: -len(".md")]
|
|
76
|
+
|
|
77
|
+
if not name:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
# Reject path traversal
|
|
81
|
+
if ".." in name or "/" in name:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
return name
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _result(
|
|
88
|
+
status: str,
|
|
89
|
+
path: str | None = None,
|
|
90
|
+
error: str | None = None,
|
|
91
|
+
) -> dict:
|
|
92
|
+
return {
|
|
93
|
+
"status": status,
|
|
94
|
+
"path": path,
|
|
95
|
+
"error": error,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _find_project_root() -> Path:
|
|
100
|
+
"""Walk up from cwd looking for .pennyfarthing/ directory."""
|
|
101
|
+
cwd = Path.cwd()
|
|
102
|
+
for parent in [cwd, *cwd.parents]:
|
|
103
|
+
if (parent / ".pennyfarthing").is_dir():
|
|
104
|
+
return parent
|
|
105
|
+
return cwd
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Gate subagent runner — parse gate files and extract GATE_RESULT.
|
|
2
|
+
|
|
3
|
+
Provides two core functions for the agent exit protocol (step 6):
|
|
4
|
+
1. parse_gate_file(path) — read gate file, extract model/name/content
|
|
5
|
+
2. extract_gate_result(raw_output) — regex-extract GATE_RESULT from subagent output
|
|
6
|
+
|
|
7
|
+
The actual subagent spawning is handled by the Claude agent via Task tool.
|
|
8
|
+
These functions provide the parsing layer around that interaction.
|
|
9
|
+
|
|
10
|
+
Default-deny: missing or unparseable GATE_RESULT always returns fail.
|
|
11
|
+
Gate files are read-only at runtime — never written to.
|
|
12
|
+
|
|
13
|
+
Story: 106-2 (Gate subagent runner with GATE_RESULT contract)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
_DEFAULT_FAIL: dict = {
|
|
22
|
+
"status": "fail",
|
|
23
|
+
"message": "Gate evaluation failed or did not return GATE_RESULT",
|
|
24
|
+
"checks": [],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def parse_gate_file(
|
|
29
|
+
gate_path: str | Path,
|
|
30
|
+
) -> dict:
|
|
31
|
+
"""Parse a gate file and extract its metadata and content.
|
|
32
|
+
|
|
33
|
+
Reads the gate file (read-only), extracts the model attribute and gate name
|
|
34
|
+
from the <gate> tag, and returns the full content for subagent prompting.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
gate_path: Absolute path to the gate file.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
dict with keys:
|
|
41
|
+
status: "ok" | "error"
|
|
42
|
+
name: str | None (gate name from <gate name="...">)
|
|
43
|
+
model: str (model from <gate model="..."> or "haiku")
|
|
44
|
+
content: str | None (full gate file content)
|
|
45
|
+
error: str | None
|
|
46
|
+
"""
|
|
47
|
+
path = Path(gate_path)
|
|
48
|
+
|
|
49
|
+
if not path.exists():
|
|
50
|
+
return {
|
|
51
|
+
"status": "error",
|
|
52
|
+
"name": None,
|
|
53
|
+
"model": "haiku",
|
|
54
|
+
"content": None,
|
|
55
|
+
"error": f"Gate file not found: {path}",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
content = path.read_text()
|
|
59
|
+
|
|
60
|
+
gate_match = re.search(r"<gate\b[^>]*>", content)
|
|
61
|
+
if not gate_match:
|
|
62
|
+
return {
|
|
63
|
+
"status": "error",
|
|
64
|
+
"name": None,
|
|
65
|
+
"model": "haiku",
|
|
66
|
+
"content": None,
|
|
67
|
+
"error": "No <gate> tag found in file",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
gate_tag = gate_match.group(0)
|
|
71
|
+
|
|
72
|
+
name_match = re.search(r'name="([^"]+)"', gate_tag)
|
|
73
|
+
name = name_match.group(1) if name_match else None
|
|
74
|
+
|
|
75
|
+
model_match = re.search(r'model="([^"]+)"', gate_tag)
|
|
76
|
+
model = model_match.group(1) if model_match else "haiku"
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
"status": "ok",
|
|
80
|
+
"name": name,
|
|
81
|
+
"model": model,
|
|
82
|
+
"content": content,
|
|
83
|
+
"error": None,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def extract_gate_result(
|
|
88
|
+
raw_output: str | None,
|
|
89
|
+
) -> dict:
|
|
90
|
+
"""Extract GATE_RESULT from raw subagent output via regex.
|
|
91
|
+
|
|
92
|
+
Parses the subagent's text output to find a GATE_RESULT YAML block.
|
|
93
|
+
Uses regex/grep patterns — NOT a full YAML parser.
|
|
94
|
+
|
|
95
|
+
Default-deny: if GATE_RESULT cannot be extracted, returns fail.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
raw_output: Raw text output from the gate subagent.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
dict with keys:
|
|
102
|
+
status: "pass" | "fail"
|
|
103
|
+
message: str
|
|
104
|
+
checks: list[dict] (each with name, status, detail)
|
|
105
|
+
"""
|
|
106
|
+
if raw_output is None or not raw_output.strip():
|
|
107
|
+
return dict(_DEFAULT_FAIL)
|
|
108
|
+
|
|
109
|
+
# Split on GATE_RESULT: and take the last block (AC7: multiple → last wins)
|
|
110
|
+
blocks = raw_output.split("GATE_RESULT:")
|
|
111
|
+
if len(blocks) < 2:
|
|
112
|
+
return dict(_DEFAULT_FAIL)
|
|
113
|
+
|
|
114
|
+
block = blocks[-1]
|
|
115
|
+
|
|
116
|
+
# Extract status — strict enum: only "pass" or "fail"
|
|
117
|
+
status_match = re.search(r"^\s*status:\s*(pass|fail)\s*$", block, re.MULTILINE)
|
|
118
|
+
if not status_match:
|
|
119
|
+
return dict(_DEFAULT_FAIL)
|
|
120
|
+
|
|
121
|
+
status = status_match.group(1)
|
|
122
|
+
|
|
123
|
+
# Extract message — double-quoted, single-quoted, or unquoted
|
|
124
|
+
message_match = re.search(
|
|
125
|
+
r"""^\s*message:\s*(?:"([^"]*)"|'([^']*)'|(.+?))\s*$""",
|
|
126
|
+
block,
|
|
127
|
+
re.MULTILINE,
|
|
128
|
+
)
|
|
129
|
+
message = ""
|
|
130
|
+
if message_match:
|
|
131
|
+
message = message_match.group(1) or message_match.group(2) or message_match.group(3) or ""
|
|
132
|
+
|
|
133
|
+
# Extract checks list via regex on consecutive name/status/detail lines
|
|
134
|
+
checks: list[dict] = []
|
|
135
|
+
check_pattern = re.compile(
|
|
136
|
+
r"^\s*-\s*name:\s*(?:\"([^\"]*)\"|'([^']*)'|(\S+))\s*\n"
|
|
137
|
+
r"\s*status:\s*(pass|fail)\s*\n"
|
|
138
|
+
r"\s*detail:\s*(?:\"([^\"]*)\"|'([^']*)'|(.+?))\s*$",
|
|
139
|
+
re.MULTILINE,
|
|
140
|
+
)
|
|
141
|
+
for m in check_pattern.finditer(block):
|
|
142
|
+
checks.append({
|
|
143
|
+
"name": m.group(1) or m.group(2) or m.group(3),
|
|
144
|
+
"status": m.group(4),
|
|
145
|
+
"detail": m.group(5) or m.group(6) or m.group(7) or "",
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"status": status,
|
|
150
|
+
"message": message,
|
|
151
|
+
"checks": checks,
|
|
152
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Generate AGENT_COMMAND block for handoff markers.
|
|
2
|
+
|
|
3
|
+
Replaces handoff-marker.sh with a Python implementation that reuses
|
|
4
|
+
the existing context.py module for environment detection.
|
|
5
|
+
|
|
6
|
+
Story: 105-4 (Script-First Handoff)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pennyfarthing_scripts.context import check_context
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def generate_marker(
|
|
15
|
+
next_agent: str | None = None,
|
|
16
|
+
*,
|
|
17
|
+
error: str | None = None,
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Generate an AGENT_COMMAND block for agent handoff.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
next_agent: Agent to hand off to (e.g., "dev", "tea", "reviewer").
|
|
23
|
+
error: If set, generates an error block instead of a handoff.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
YAML-formatted AGENT_COMMAND block string.
|
|
27
|
+
"""
|
|
28
|
+
if error:
|
|
29
|
+
return _block(fallback=error, error=True)
|
|
30
|
+
|
|
31
|
+
if not next_agent:
|
|
32
|
+
return _block(fallback="No next agent specified", error=True)
|
|
33
|
+
|
|
34
|
+
ctx = check_context()
|
|
35
|
+
|
|
36
|
+
cmd = f"/pf-{next_agent}"
|
|
37
|
+
pct = ctx.usable_percent if not ctx.error else "unknown"
|
|
38
|
+
|
|
39
|
+
if pct != "unknown" and pct >= 60:
|
|
40
|
+
context_warning = f" (context: {pct}% - consider /clear before continuing)"
|
|
41
|
+
elif pct != "unknown":
|
|
42
|
+
context_warning = f" (context: {pct}%)"
|
|
43
|
+
else:
|
|
44
|
+
context_warning = ""
|
|
45
|
+
|
|
46
|
+
if not ctx.relay_mode:
|
|
47
|
+
# Relay off — ask for confirmation
|
|
48
|
+
if ctx.is_cyclist:
|
|
49
|
+
return _block(
|
|
50
|
+
marker="<!-- CYCLIST:QUESTION:yesno -->",
|
|
51
|
+
question=f"Ready to hand off to {cmd}?",
|
|
52
|
+
fallback=f"Run `{cmd}` to continue",
|
|
53
|
+
)
|
|
54
|
+
return _block(
|
|
55
|
+
fallback=f"Run `{cmd}` to continue{context_warning}",
|
|
56
|
+
relay_mode=False,
|
|
57
|
+
context_percent=pct,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Relay on — auto-handoff
|
|
61
|
+
# Cyclist uses its feedback loop (QuickActions → slash command injection).
|
|
62
|
+
# Non-Cyclist: we're already in the session, invoke the agent directly.
|
|
63
|
+
marker = None
|
|
64
|
+
if ctx.use_tirepump:
|
|
65
|
+
marker = f"<!-- CYCLIST:CONTEXT_CLEAR:{cmd} -->" if ctx.is_cyclist else None
|
|
66
|
+
else:
|
|
67
|
+
marker = f"<!-- CYCLIST:HANDOFF:{cmd} -->" if ctx.is_cyclist else None
|
|
68
|
+
|
|
69
|
+
if ctx.is_cyclist:
|
|
70
|
+
return _block(
|
|
71
|
+
marker=marker,
|
|
72
|
+
fallback=f"Run `{cmd}` to continue",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Non-Cyclist relay: invoke the next agent directly
|
|
76
|
+
import subprocess
|
|
77
|
+
try:
|
|
78
|
+
result = subprocess.run(
|
|
79
|
+
["pf", "agent", "start", next_agent],
|
|
80
|
+
capture_output=True,
|
|
81
|
+
text=True,
|
|
82
|
+
timeout=30,
|
|
83
|
+
)
|
|
84
|
+
agent_output = result.stdout.strip()
|
|
85
|
+
except Exception as e:
|
|
86
|
+
agent_output = f"Failed to invoke agent: {e}"
|
|
87
|
+
|
|
88
|
+
return _block(
|
|
89
|
+
fallback=f"Run `{cmd}` to continue{context_warning}",
|
|
90
|
+
relay_mode=True,
|
|
91
|
+
context_percent=pct,
|
|
92
|
+
invoke=cmd,
|
|
93
|
+
) + f"\n\n{agent_output}"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _block(**fields: object) -> str:
|
|
97
|
+
"""Format an AGENT_COMMAND YAML block."""
|
|
98
|
+
lines = ["---", "AGENT_COMMAND:"]
|
|
99
|
+
for key, value in fields.items():
|
|
100
|
+
if isinstance(value, bool):
|
|
101
|
+
lines.append(f" {key}: {str(value).lower()}")
|
|
102
|
+
elif isinstance(value, str) and value:
|
|
103
|
+
lines.append(f' {key}: "{value}"')
|
|
104
|
+
elif value == "":
|
|
105
|
+
lines.append(f' {key}: ""')
|
|
106
|
+
else:
|
|
107
|
+
lines.append(f" {key}: {value}")
|
|
108
|
+
lines.append("---")
|
|
109
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Resolve gate for current workflow phase.
|
|
2
|
+
|
|
3
|
+
Reads workflow YAML, finds current phase gate, checks for assessment
|
|
4
|
+
section in session file, and returns a structured RESOLVE_RESULT.
|
|
5
|
+
|
|
6
|
+
Story: 105-1 (Script-First Handoff)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def resolve_gate(
|
|
18
|
+
story_id: str,
|
|
19
|
+
workflow: str,
|
|
20
|
+
phase: str,
|
|
21
|
+
project_root: Path | None = None,
|
|
22
|
+
) -> dict:
|
|
23
|
+
"""Resolve the gate for the current workflow phase.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
story_id: Story identifier (e.g., "105-1")
|
|
27
|
+
workflow: Workflow name (e.g., "tdd", "trivial", "patch")
|
|
28
|
+
phase: Current phase name (e.g., "green", "implement", "fix")
|
|
29
|
+
project_root: Project root path. Auto-detected if None.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
RESOLVE_RESULT dict with keys:
|
|
33
|
+
status: "ready" | "blocked" | "skip"
|
|
34
|
+
gate_type: str | None
|
|
35
|
+
gate_file: str | None
|
|
36
|
+
next_agent: str | None
|
|
37
|
+
next_phase: str | None
|
|
38
|
+
assessment_found: bool
|
|
39
|
+
error: str | None
|
|
40
|
+
"""
|
|
41
|
+
if project_root is None:
|
|
42
|
+
project_root = _find_project_root()
|
|
43
|
+
|
|
44
|
+
workflow_path = _find_workflow_yaml(project_root, workflow)
|
|
45
|
+
if workflow_path is None:
|
|
46
|
+
return _result(status="error", error=f"Workflow '{workflow}' not found")
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
data = yaml.safe_load(workflow_path.read_text())
|
|
50
|
+
phases = data["workflow"]["phases"]
|
|
51
|
+
except Exception as e:
|
|
52
|
+
return _result(status="error", error=f"Failed to parse workflow: {e}")
|
|
53
|
+
|
|
54
|
+
current_idx = None
|
|
55
|
+
current_phase = None
|
|
56
|
+
for i, p in enumerate(phases):
|
|
57
|
+
if p["name"] == phase:
|
|
58
|
+
current_idx = i
|
|
59
|
+
current_phase = p
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
if current_phase is None:
|
|
63
|
+
return _result(
|
|
64
|
+
status="error",
|
|
65
|
+
error=f"Phase '{phase}' not found in workflow '{workflow}'",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
gate = current_phase.get("gate")
|
|
69
|
+
|
|
70
|
+
if current_idx + 1 < len(phases):
|
|
71
|
+
nxt = phases[current_idx + 1]
|
|
72
|
+
next_phase = nxt["name"]
|
|
73
|
+
next_agent = nxt["agent"]
|
|
74
|
+
else:
|
|
75
|
+
next_phase = None
|
|
76
|
+
next_agent = None
|
|
77
|
+
|
|
78
|
+
if not gate:
|
|
79
|
+
return _result(
|
|
80
|
+
status="skip",
|
|
81
|
+
next_agent=next_agent,
|
|
82
|
+
next_phase=next_phase,
|
|
83
|
+
assessment_found=True,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
gate_type = gate.get("type")
|
|
87
|
+
gate_file = gate.get("file")
|
|
88
|
+
|
|
89
|
+
if gate_type == "manual":
|
|
90
|
+
return _result(
|
|
91
|
+
status="skip",
|
|
92
|
+
gate_type="manual",
|
|
93
|
+
next_agent=next_agent,
|
|
94
|
+
next_phase=next_phase,
|
|
95
|
+
assessment_found=True,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
session_path = project_root / ".session" / f"{story_id}-session.md"
|
|
99
|
+
assessment_found = False
|
|
100
|
+
if session_path.exists():
|
|
101
|
+
content = session_path.read_text()
|
|
102
|
+
assessment_found = bool(
|
|
103
|
+
re.search(r"^##\s+.*Assessment", content, re.MULTILINE)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
status = "ready" if assessment_found else "blocked"
|
|
107
|
+
return _result(
|
|
108
|
+
status=status,
|
|
109
|
+
gate_type=gate_type,
|
|
110
|
+
gate_file=gate_file,
|
|
111
|
+
next_agent=next_agent,
|
|
112
|
+
next_phase=next_phase,
|
|
113
|
+
assessment_found=assessment_found,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _result(
|
|
118
|
+
status: str,
|
|
119
|
+
gate_type: str | None = None,
|
|
120
|
+
gate_file: str | None = None,
|
|
121
|
+
next_agent: str | None = None,
|
|
122
|
+
next_phase: str | None = None,
|
|
123
|
+
assessment_found: bool = False,
|
|
124
|
+
error: str | None = None,
|
|
125
|
+
) -> dict:
|
|
126
|
+
return {
|
|
127
|
+
"status": status,
|
|
128
|
+
"gate_type": gate_type,
|
|
129
|
+
"gate_file": gate_file,
|
|
130
|
+
"next_agent": next_agent,
|
|
131
|
+
"next_phase": next_phase,
|
|
132
|
+
"assessment_found": assessment_found,
|
|
133
|
+
"error": error,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _find_workflow_yaml(project_root: Path, workflow: str) -> Path | None:
|
|
138
|
+
flat = project_root / ".pennyfarthing" / "workflows" / f"{workflow}.yaml"
|
|
139
|
+
if flat.exists():
|
|
140
|
+
return flat
|
|
141
|
+
subdir = project_root / ".pennyfarthing" / "workflows" / workflow / "workflow.yaml"
|
|
142
|
+
if subdir.exists():
|
|
143
|
+
return subdir
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _find_project_root() -> Path:
|
|
148
|
+
cwd = Path.cwd()
|
|
149
|
+
for parent in [cwd, *cwd.parents]:
|
|
150
|
+
if (parent / ".pennyfarthing").is_dir():
|
|
151
|
+
return parent
|
|
152
|
+
return cwd
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -275,3 +275,42 @@ def check_redirect(workflow_status: WorkflowStatus, agent_name: str) -> tuple[st
|
|
|
275
275
|
)
|
|
276
276
|
|
|
277
277
|
return None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def get_phase_tandem_config(
|
|
281
|
+
workflow_name: str, phase_name: str, project_root: Path | None = None
|
|
282
|
+
) -> dict[str, Any] | None:
|
|
283
|
+
"""Extract tandem configuration for a specific workflow phase.
|
|
284
|
+
|
|
285
|
+
Reads the workflow YAML and returns the tandem block for the given phase,
|
|
286
|
+
or None if the phase has no tandem configuration.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
workflow_name: Workflow name (tdd-tandem, bdd-tandem, etc.)
|
|
290
|
+
phase_name: Phase name (red, green, review, etc.)
|
|
291
|
+
project_root: Project root path (auto-detected if not provided)
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Dict with tandem config (partner, mode, model, token_budget, triggers, scope)
|
|
295
|
+
or None if no tandem config on this phase.
|
|
296
|
+
"""
|
|
297
|
+
root = project_root or get_project_root()
|
|
298
|
+
workflow_path = root / "pennyfarthing-dist" / "workflows" / f"{workflow_name}.yaml"
|
|
299
|
+
|
|
300
|
+
if not workflow_path.exists():
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
data = yaml.safe_load(workflow_path.read_text())
|
|
305
|
+
phases = data.get("workflow", {}).get("phases", [])
|
|
306
|
+
|
|
307
|
+
for phase in phases:
|
|
308
|
+
if isinstance(phase, dict) and phase.get("name") == phase_name:
|
|
309
|
+
tandem = phase.get("tandem")
|
|
310
|
+
if isinstance(tandem, dict):
|
|
311
|
+
return dict(tandem)
|
|
312
|
+
return None
|
|
313
|
+
|
|
314
|
+
return None
|
|
315
|
+
except Exception:
|
|
316
|
+
return None
|
|
File without changes
|